From f0501885dbee72c3dcc49d2f23d92a6fd02af508 Mon Sep 17 00:00:00 2001 From: HoonHee Lee Date: Thu, 26 Dec 2019 05:49:00 +0000 Subject: [PATCH] LG changes for OLED CX --- configure.ac | 14 + ext/gtk/gtkgstglwidget.c | 15 +- ext/raw1394/gstdv1394src.c | 2 +- ext/raw1394/gsthdv1394src.c | 2 +- ext/soup/gstsouphttpsrc.c | 791 +- ext/soup/gstsouphttpsrc.h | 17 +- ext/taglib/gstid3v2mux.cc | 2 +- ext/wavpack/Makefile.am | 11 +- ext/wavpack/gstwavpack.c | 4 + ext/wavpack/gstwavpackdec.c | 39 +- ext/wavpack/gstwavpackdec.h | 1 + ext/wavpack/gstwavpackstreamreader.c | 4 +- ext/wavpack/gstwavpackstreamreader.h | 1 + ext/wavpack/gstwvccombiner.c | 266 + ext/wavpack/gstwvccombiner.h | 52 + ext/wavpack/gstwvcmeta.c | 126 + ext/wavpack/gstwvcmeta.h | 46 + ext/wavpack/gstwvfilesrc.c | 503 + ext/wavpack/gstwvfilesrc.h | 77 + ext/wavpack/meson.build | 3 + fix_sysroot.sh | 27 + gst/audiofx/gstscaletempo.c | 14 +- gst/audioparsers/Makefile.am | 8 +- gst/audioparsers/gstaacparse.c | 34 +- gst/audioparsers/gstac3parse.c | 18 +- gst/audioparsers/gstac3parse.h | 1 + gst/audioparsers/gstac4parse.c | 620 + gst/audioparsers/gstac4parse.h | 86 + gst/audioparsers/gstflacparse.c | 14 + gst/audioparsers/gstmpegaudioparse.c | 112 +- gst/audioparsers/gstmpegaudioparse.h | 1 + gst/audioparsers/gstwavpackparse.c | 6 +- gst/audioparsers/meson.build | 2 +- gst/audioparsers/plugin.c | 6 +- gst/autodetect/gstautodetect.c | 10 +- gst/avi/avi-ids.h | 1 + gst/avi/gstavidemux.c | 1378 +- gst/avi/gstavidemux.h | 188 +- gst/deinterlace/gstdeinterlacemethod.c | 4 + gst/flv/gstflvdemux.c | 649 +- gst/flv/gstflvdemux.h | 46 +- gst/isomp4/atoms.c | 166 +- gst/isomp4/atoms.h | 42 +- gst/isomp4/fourcc.h | 33 +- gst/isomp4/gstqtmux-doc.h | 1 + gst/isomp4/gstqtmux.c | 1033 +- gst/isomp4/gstqtmux.h | 40 + gst/isomp4/gstqtmuxmap.c | 24 +- gst/isomp4/gstqtmuxmap.h | 3 +- gst/isomp4/qtdemux.c | 3001 ++- gst/isomp4/qtdemux.h | 45 +- gst/isomp4/qtdemux_dump.c | 136 + gst/isomp4/qtdemux_dump.h | 4 + gst/isomp4/qtdemux_types.c | 11 +- gst/isomp4_1_8/LEGAL | 10 + gst/isomp4_1_8/Makefile.am | 32 + gst/isomp4_1_8/descriptors.c | 457 + gst/isomp4_1_8/descriptors.h | 151 + gst/isomp4_1_8/fourcc.h | 396 + gst/isomp4_1_8/gstisoff.c | 200 + gst/isomp4_1_8/gstisoff.h | 100 + gst/isomp4_1_8/isomp4-plugin.c | 52 + gst/isomp4_1_8/meson.build | 29 + gst/isomp4_1_8/properties.c | 210 + gst/isomp4_1_8/properties.h | 87 + gst/isomp4_1_8/qtatomparser.h | 139 + gst/isomp4_1_8/qtdemux.c | 16458 ++++++++++++++++ gst/isomp4_1_8/qtdemux.h | 251 + gst/isomp4_1_8/qtdemux_dump.c | 955 + gst/isomp4_1_8/qtdemux_dump.h | 92 + gst/isomp4_1_8/qtdemux_lang.c | 205 + gst/isomp4_1_8/qtdemux_lang.h | 31 + gst/isomp4_1_8/qtdemux_types.c | 227 + gst/isomp4_1_8/qtdemux_types.h | 83 + gst/isomp4_1_8/qtpalette.h | 137 + gst/matroska/matroska-demux.c | 1497 +- gst/matroska/matroska-demux.h | 14 + gst/matroska/matroska-ids.c | 25 + gst/matroska/matroska-ids.h | 59 + gst/matroska/matroska-mux.c | 87 - gst/meson.build | 1 + gst/rtp/gstrtpmp4gpay.c | 19 +- gst/rtp/gstrtpvrawpay.c | 1 + gst/rtpmanager/gstrtpbin.c | 9 +- gst/rtpmanager/gstrtpjitterbuffer.c | 11 +- gst/rtpmanager/gstrtpsession.c | 9 +- gst/rtpmanager/rtpstats.h | 9 + gst/rtsp/gstrtspsrc.c | 422 +- gst/rtsp/gstrtspsrc.h | 10 + gst/udp/gstudpsrc.c | 44 +- gst/udp/gstudpsrc.h | 1 + gst/wavparse/gstwavparse.c | 23 +- meson.build | 1 + sys/v4l2/Makefile.am | 12 +- sys/v4l2/gstv4l2.c | 5 + sys/v4l2/gstv4l2allocator.c | 16 + sys/v4l2/gstv4l2bufferpool.c | 16 + sys/v4l2/gstv4l2object.c | 25 +- sys/v4l2/gstv4l2scalerobject.c | 586 + sys/v4l2/gstv4l2scalerobject.h | 67 + sys/v4l2/gstv4l2scalersrc.c | 1304 ++ sys/v4l2/gstv4l2scalersrc.h | 84 + sys/v4l2/gstv4l2src.c | 25 +- sys/v4l2/meson.build | 20 + tests/check/elements/qtdemux.c | 471 + tests/check/elements/qtdemux.h | 786 + tests/check/elements/qtmux.c | 15 +- tests/examples/v4l2/Makefile.am | 6 +- tests/examples/v4l2/meson.build | 6 + .../v4l2/v4l2scalersrc-negotiate-dmabuf.c | 152 + 110 files changed, 33966 insertions(+), 1914 deletions(-) create mode 100644 ext/wavpack/gstwvccombiner.c create mode 100644 ext/wavpack/gstwvccombiner.h create mode 100644 ext/wavpack/gstwvcmeta.c create mode 100644 ext/wavpack/gstwvcmeta.h create mode 100644 ext/wavpack/gstwvfilesrc.c create mode 100644 ext/wavpack/gstwvfilesrc.h create mode 100755 fix_sysroot.sh create mode 100644 gst/audioparsers/gstac4parse.c create mode 100644 gst/audioparsers/gstac4parse.h create mode 100644 gst/isomp4_1_8/LEGAL create mode 100644 gst/isomp4_1_8/Makefile.am create mode 100644 gst/isomp4_1_8/descriptors.c create mode 100644 gst/isomp4_1_8/descriptors.h create mode 100644 gst/isomp4_1_8/fourcc.h create mode 100644 gst/isomp4_1_8/gstisoff.c create mode 100644 gst/isomp4_1_8/gstisoff.h create mode 100644 gst/isomp4_1_8/isomp4-plugin.c create mode 100644 gst/isomp4_1_8/meson.build create mode 100644 gst/isomp4_1_8/properties.c create mode 100644 gst/isomp4_1_8/properties.h create mode 100644 gst/isomp4_1_8/qtatomparser.h create mode 100644 gst/isomp4_1_8/qtdemux.c create mode 100644 gst/isomp4_1_8/qtdemux.h create mode 100644 gst/isomp4_1_8/qtdemux_dump.c create mode 100644 gst/isomp4_1_8/qtdemux_dump.h create mode 100644 gst/isomp4_1_8/qtdemux_lang.c create mode 100644 gst/isomp4_1_8/qtdemux_lang.h create mode 100644 gst/isomp4_1_8/qtdemux_types.c create mode 100644 gst/isomp4_1_8/qtdemux_types.h create mode 100644 gst/isomp4_1_8/qtpalette.h create mode 100644 sys/v4l2/gstv4l2scalerobject.c create mode 100644 sys/v4l2/gstv4l2scalerobject.h create mode 100644 sys/v4l2/gstv4l2scalersrc.c create mode 100644 sys/v4l2/gstv4l2scalersrc.h create mode 100644 tests/examples/v4l2/v4l2scalersrc-negotiate-dmabuf.c diff --git a/configure.ac b/configure.ac index deb74f105a..7c6e436924 100644 --- a/configure.ac +++ b/configure.ac @@ -395,6 +395,7 @@ AG_GST_CHECK_PLUGIN(id3demux) AG_GST_CHECK_PLUGIN(imagefreeze) AG_GST_CHECK_PLUGIN(interleave) AG_GST_CHECK_PLUGIN(isomp4) +AG_GST_CHECK_PLUGIN(isomp4_1_8) AG_GST_CHECK_PLUGIN(law) AG_GST_CHECK_PLUGIN(level) AG_GST_CHECK_PLUGIN(matroska) @@ -560,6 +561,18 @@ AG_GST_CHECK_FEATURE(GST_V4L2, [Video 4 Linux 2], video4linux2, [ fi ]) +HAVE_LINUX_EXT=no +if test x$HAVE_GST_V4L2 = xyes; then + AC_CHECK_HEADER(linux/v4l2-ext/videodev2-ext.h, [ HAVE_LINUX_EXT=yes ], + [ HAVE_LINUX_EXT=no ]) + if test x$HAVE_LINUX_EXT = xyes; then + AC_DEFINE(HAVE_LINUX_EXT, 1, [Define if linuxtv-ext-header is available]) + AM_CONDITIONAL(USE_LINUX_EXT, true) + else + AM_CONDITIONAL(USE_LINUX_EXT, false) + fi +fi + # Optional gudev for device probing AC_ARG_WITH([gudev], AS_HELP_STRING([--with-gudev],[device detection with gudev]), @@ -1238,6 +1251,7 @@ gst/icydemux/Makefile gst/imagefreeze/Makefile gst/interleave/Makefile gst/isomp4/Makefile +gst/isomp4_1_8/Makefile gst/law/Makefile gst/level/Makefile gst/matroska/Makefile diff --git a/ext/gtk/gtkgstglwidget.c b/ext/gtk/gtkgstglwidget.c index 76c46c9c8c..2a8beab20b 100644 --- a/ext/gtk/gtkgstglwidget.c +++ b/ext/gtk/gtkgstglwidget.c @@ -51,13 +51,6 @@ #define GST_CAT_DEFAULT gtk_gst_gl_widget_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); -G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA, - GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0, - "Gtk Gst GL Widget");); - -#define GTK_GST_GL_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ - GTK_TYPE_GST_GL_WIDGET, GtkGstGLWidgetPrivate)) - struct _GtkGstGLWidgetPrivate { gboolean initted; @@ -82,6 +75,11 @@ static const GLfloat vertices[] = { 1.0f, -1.0f, 0.0f, 1.0f, 1.0f }; +G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA, + G_ADD_PRIVATE (GtkGstGLWidget) + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0, + "Gtk Gst GL Widget");); + static void gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget) { @@ -365,7 +363,6 @@ gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass) GObjectClass *gobject_klass = (GObjectClass *) klass; GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass; - g_type_class_add_private (klass, sizeof (GtkGstGLWidgetPrivate)); gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass)); gobject_klass->finalize = gtk_gst_gl_widget_finalize; @@ -381,7 +378,7 @@ gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget) gtk_gst_base_widget_init (base_widget); - gst_widget->priv = priv = GTK_GST_GL_WIDGET_GET_PRIVATE (gst_widget); + gst_widget->priv = priv = gtk_gst_gl_widget_get_instance_private (gst_widget); display = gdk_display_get_default (); diff --git a/ext/raw1394/gstdv1394src.c b/ext/raw1394/gstdv1394src.c index bee1af130b..30c443ac37 100644 --- a/ext/raw1394/gstdv1394src.c +++ b/ext/raw1394/gstdv1394src.c @@ -37,7 +37,7 @@ #include "config.h" #endif #include -#include +#include #include #include #include diff --git a/ext/raw1394/gsthdv1394src.c b/ext/raw1394/gsthdv1394src.c index 04ceb4f3c0..e9e39805ad 100644 --- a/ext/raw1394/gsthdv1394src.c +++ b/ext/raw1394/gsthdv1394src.c @@ -36,7 +36,7 @@ #include "config.h" #endif #include -#include +#include #include #include #include diff --git a/ext/soup/gstsouphttpsrc.c b/ext/soup/gstsouphttpsrc.c index 6e64a57ae7..eff50bf8dc 100644 --- a/ext/soup/gstsouphttpsrc.c +++ b/ext/soup/gstsouphttpsrc.c @@ -74,6 +74,7 @@ #include #ifdef HAVE_STDLIB_H #include /* atoi() */ +#include /* sscanf() */ #endif #include #include @@ -93,6 +94,14 @@ static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); +enum +{ + /* signals */ + GOT_HEADERS_SIGNAL = 0, + GOT_CHUNK_SIGNAL, + LAST_SIGNAL +}; + enum { PROP_0, @@ -119,29 +128,56 @@ enum PROP_RETRIES, PROP_METHOD, PROP_TLS_INTERACTION, + PROP_DLNA_CONTENTLENGTH, + PROP_DLNA_OPVAL, + PROP_DLNA_FLAGVAL, + PROP_IS_DTCP, + PROP_CURRENT_POSITION, + PROP_START_OFFSET, + PROP_END_OFFSET, + PROP_LAST }; -#define DEFAULT_USER_AGENT "GStreamer souphttpsrc " PACKAGE_VERSION " " +#define DEFAULT_USER_AGENT "GStreamer souphttpsrc (compatible; LG NetCast.TV-2013) " +#define DEFAULT_BLOCKSIZE (24 * 1024) #define DEFAULT_IRADIO_MODE TRUE #define DEFAULT_SOUP_LOG_LEVEL SOUP_LOGGER_LOG_HEADERS #define DEFAULT_COMPRESS FALSE -#define DEFAULT_KEEP_ALIVE TRUE -#define DEFAULT_SSL_STRICT TRUE +#define DEFAULT_KEEP_ALIVE FALSE +#define DEFAULT_SSL_STRICT FALSE #define DEFAULT_SSL_CA_FILE NULL #define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE #define DEFAULT_TLS_DATABASE NULL #define DEFAULT_TLS_INTERACTION NULL #define DEFAULT_TIMEOUT 15 -#define DEFAULT_RETRIES 3 +#define DEFAULT_RETRIES 2 #define DEFAULT_SOUP_METHOD NULL -#define GROW_BLOCKSIZE_LIMIT 1 -#define GROW_BLOCKSIZE_COUNT 1 -#define GROW_BLOCKSIZE_FACTOR 2 -#define REDUCE_BLOCKSIZE_LIMIT 0.20 -#define REDUCE_BLOCKSIZE_COUNT 2 -#define REDUCE_BLOCKSIZE_FACTOR 0.5 +static guint soup_http_src_signals[LAST_SIGNAL] = { 0, }; +#define SOCK_POLLING_TIMEOUT 180 + +/** + * GST_NPT_TIME_FORMAT: + * + * A npt time format for DLNA + */ +#define GST_NPT_TIME_FORMAT "u:%02u:%02u.%03u" +/** + * GST_NPT_TIME_ARGS: + * @t: a #GstClockTime + * + * Format @t for the GST_NPT_TIME_FORMAT format string. + */ +#define GST_NPT_TIME_ARGS(t) \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) (((GstClockTime)(t)) / (GST_SECOND * 60 * 60)) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) ((((GstClockTime)(t)) / (GST_SECOND * 60)) % 60) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) ((((GstClockTime)(t)) / GST_SECOND) % 60) : 99, \ + GST_CLOCK_TIME_IS_VALID (t) ? \ + (guint) ((((GstClockTime)(t)) % GST_SECOND)/1000000) : 999 static void gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data); static void gst_soup_http_src_finalize (GObject * gobject); @@ -186,6 +222,14 @@ static GstFlowReturn gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, static void gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg, SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src); +static void gst_soup_http_src_duration_set_n_post (GstSoupHTTPSrc * src); +static gboolean gst_soup_http_src_add_time_seek_range_header (GstSoupHTTPSrc * + src, guint64 offset); +static gboolean gst_soup_http_src_add_cleartext_range_header (GstSoupHTTPSrc * + src, guint64 offset); +static gboolean gst_soup_http_src_handle_custom_query (GstSoupHTTPSrc * src, + GstQuery * query); +static gboolean gst_soup_http_src_query_dtcp_seekable (GstBaseSrc * bsrc); #define gst_soup_http_src_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstSoupHTTPSrc, gst_soup_http_src, GST_TYPE_PUSH_SRC, @@ -256,7 +300,7 @@ gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass) g_object_class_install_property (gobject_class, PROP_TIMEOUT, g_param_spec_uint ("timeout", "timeout", "Value in seconds to timeout a blocking I/O (0 = No timeout).", 0, - 3600, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + SOCK_POLLING_TIMEOUT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS, g_param_spec_boxed ("extra-headers", "Extra Headers", "Extra headers to append to the HTTP request", @@ -420,6 +464,46 @@ gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass) gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_soup_http_src_set_context); + /* dlna stuff */ + g_object_class_install_property (gobject_class, PROP_IS_DTCP, + g_param_spec_boolean ("is-dtcp", "DTCP-IP", "is DTCP-IP content?", + FALSE, G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_class, PROP_CURRENT_POSITION, + g_param_spec_uint64 ("current-position", "Current Position", + "A Position where to read from the URL", + 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_START_OFFSET, + g_param_spec_uint64 ("start-offset", "start offset", + "First byte of a byte range (0 = From beginning).", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_END_OFFSET, + g_param_spec_uint64 ("end-offset", "end offset", + "Last byte of a byte range (0 = Till the end).", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * SoupHTTPsrc::got-headers: + * + * Notify when the response message headers are received. + */ + soup_http_src_signals[GOT_HEADERS_SIGNAL] = + g_signal_new ("got-headers", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstSoupHTTPSrcClass, got_headers), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, + G_TYPE_POINTER); + +/** + * SoupHTTPsrc::got-chunk: + * + * Notify when a new data chunk is received. + */ + soup_http_src_signals[GOT_CHUNK_SIGNAL] = + g_signal_new ("got-chunk", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstSoupHTTPSrcClass, got_chunk), + NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start); gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop); gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock); @@ -441,6 +525,7 @@ static void gst_soup_http_src_reset (GstSoupHTTPSrc * src) { src->retry_count = 0; + src->cancel = FALSE; src->have_size = FALSE; src->got_headers = FALSE; src->seekable = FALSE; @@ -450,9 +535,6 @@ gst_soup_http_src_reset (GstSoupHTTPSrc * src) src->content_size = 0; src->have_body = FALSE; - src->reduce_blocksize_count = 0; - src->increase_blocksize_count = 0; - g_cancellable_reset (src->cancellable); g_mutex_lock (&src->mutex); if (src->input_stream) { @@ -461,6 +543,15 @@ gst_soup_http_src_reset (GstSoupHTTPSrc * src) } g_mutex_unlock (&src->mutex); + src->dlna_mode = FALSE; + src->opval = 0x111; + src->flagval = 0x111; + src->is_dtcp = FALSE; + src->request_cb_position = 0; + + src->time_seek_flag = FALSE; + src->request_time = GST_CLOCK_TIME_NONE; + gst_caps_replace (&src->src_caps, NULL); g_free (src->iradio_name); src->iradio_name = NULL; @@ -502,7 +593,10 @@ gst_soup_http_src_init (GstSoupHTTPSrc * src) src->tls_interaction = DEFAULT_TLS_INTERACTION; src->max_retries = DEFAULT_RETRIES; src->method = DEFAULT_SOUP_METHOD; - src->minimum_blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src)); + src->timeout = SOCK_POLLING_TIMEOUT; + src->opval = 0x111; + src->flagval = 0x111; + proxy = g_getenv ("http_proxy"); if (!gst_soup_http_src_set_proxy (src, proxy)) { GST_WARNING_OBJECT (src, @@ -510,6 +604,7 @@ gst_soup_http_src_init (GstSoupHTTPSrc * src) proxy); } + gst_base_src_set_blocksize (GST_BASE_SRC (src), DEFAULT_BLOCKSIZE); gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE); gst_soup_http_src_reset (src); @@ -641,6 +736,12 @@ gst_soup_http_src_set_property (GObject * object, guint prop_id, case PROP_TIMEOUT: src->timeout = g_value_get_uint (value); break; + case PROP_START_OFFSET: + src->start_offset = g_value_get_uint64 (value); + break; + case PROP_END_OFFSET: + src->end_offset = g_value_get_uint64 (value); + break; case PROP_EXTRA_HEADERS:{ const GstStructure *s = gst_value_get_structure (value); @@ -680,6 +781,9 @@ gst_soup_http_src_set_property (GObject * object, guint prop_id, case PROP_RETRIES: src->max_retries = g_value_get_int (value); break; + case PROP_IS_DTCP: + src->is_dtcp = g_value_get_boolean (value); + break; case PROP_METHOD: g_free (src->method); src->method = g_value_dup_string (value); @@ -742,6 +846,27 @@ gst_soup_http_src_get_property (GObject * object, guint prop_id, case PROP_TIMEOUT: g_value_set_uint (value, src->timeout); break; + case PROP_DLNA_CONTENTLENGTH: + g_value_set_uint64 (value, src->content_size); + break; + case PROP_DLNA_OPVAL: + g_value_set_uint (value, src->opval); + break; + case PROP_DLNA_FLAGVAL: + g_value_set_uint (value, src->flagval); + break; + case PROP_IS_DTCP: + g_value_set_boolean (value, src->is_dtcp); + break; + case PROP_CURRENT_POSITION: + g_value_set_uint64 (value, src->read_position); + break; + case PROP_START_OFFSET: + g_value_set_uint64 (value, src->start_offset); + break; + case PROP_END_OFFSET: + g_value_set_uint64 (value, src->end_offset); + break; case PROP_EXTRA_HEADERS: gst_value_set_structure (value, src->extra_headers); break; @@ -803,22 +928,65 @@ gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset, guint64 stop_offset) { gchar buf[64]; - gint rc; + gint rc, buf_size; + + if (src->is_dtcp) + return gst_soup_http_src_add_cleartext_range_header (src, + src->request_cb_position); + + buf_size = (gint) sizeof (buf); soup_message_headers_remove (src->msg->request_headers, "Range"); - if (offset || stop_offset != -1) { + /* TODO: support position requests with byte ranges */ + if (offset == 0 && (src->start_offset > 0 || src->end_offset > 0)) { + /* we have a valid byte range */ + if (src->end_offset == 0) { + /* src->start_offset > 0, src->end_offset == 0 ==> read till the end of file */ + rc = g_snprintf (buf, buf_size, "bytes=%" G_GUINT64_FORMAT "-", + src->start_offset); + } else { + /* src->start_offset >= 0, src->end_offset > 0 */ + if (src->start_offset > src->end_offset) { + GST_WARNING_OBJECT (src, + "Invalid byte range requested: start_offset %" G_GUINT64_FORMAT + " > end_offset %" G_GUINT64_FORMAT, src->start_offset, + src->end_offset); + return FALSE; + } + rc = g_snprintf (buf, buf_size, "bytes=%" G_GUINT64_FORMAT + "-%" G_GUINT64_FORMAT, src->start_offset, src->end_offset); + } + + if (rc > buf_size || rc < 0) { + GST_WARNING_OBJECT (src, + "Byte range string length %d exceeds the maximum length allowed %d", + rc, buf_size); + return FALSE; + } + GST_DEBUG_OBJECT (src, "Appending byte range header %s", buf); + soup_message_headers_append (src->msg->request_headers, "Range", buf); + } + /* In case that content size is unknown under DLNA bytes seek, + set Range header starting zero */ + else if (!src->content_size && (src->opval == 0x01 || src->opval == 0x11)) { + rc = g_snprintf (buf, buf_size, "bytes=0-"); + if (rc > buf_size || rc < 0) + return FALSE; + GST_DEBUG_OBJECT (src, "Appending byte range header %s", buf); + soup_message_headers_append (src->msg->request_headers, "range", buf); + } else { if (stop_offset != -1) { g_assert (offset != stop_offset); - rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-%" + rc = g_snprintf (buf, buf_size, "bytes=%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, offset, (stop_offset > 0) ? stop_offset - 1 : stop_offset); } else { - rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-", - offset); + rc = g_snprintf (buf, buf_size, "bytes=%" G_GUINT64_FORMAT "-", offset); } - if (rc > sizeof (buf) || rc < 0) + if (rc > buf_size || rc < 0) return FALSE; + GST_DEBUG_OBJECT (src, "Appending byte range header %s", buf); soup_message_headers_append (src->msg->request_headers, "Range", buf); } src->read_position = offset; @@ -903,6 +1071,9 @@ gst_soup_http_src_add_extra_headers (GstSoupHTTPSrc * src) static gboolean gst_soup_http_src_session_open (GstSoupHTTPSrc * src) { + const GValue *value; + GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src); + if (src->session) { GST_DEBUG_OBJECT (src, "Session is already open"); return TRUE; @@ -914,6 +1085,71 @@ gst_soup_http_src_session_open (GstSoupHTTPSrc * src) return FALSE; } + if (basesrc->smart_prop) { + /* Initialize variables from smart-properties of basesrc */ + if ((value = gst_structure_get_value (basesrc->smart_prop, + "dlna-contentlength")) != 0) { + src->content_size = g_value_get_uint64 (value); + src->dlna_mode = TRUE; + gst_base_src_set_automatic_eos (GST_BASE_SRC (src), TRUE); + + if (src->content_size == (guint64) - 1) + src->content_size = 0; + GST_DEBUG_OBJECT (src, + "set automatic_eos TRUE, dlna content-length to size = %" + G_GUINT64_FORMAT, src->content_size); + } + + if (gst_structure_get_uint (basesrc->smart_prop, "dlna-opval", &src->opval)) + GST_DEBUG_OBJECT (src, "set opval= %" G_GUINT32_FORMAT, src->opval); + + if (gst_structure_get_uint (basesrc->smart_prop, "dlna-flagval", + &src->flagval)) { + GST_DEBUG_OBJECT (src, "set flagval= %" G_GUINT32_FORMAT, src->flagval); + } + } + if (!src->dlna_mode) + src->opval = 0x111; + + /* opval + 0x00 = non seekable + 0x01 = byteseek + 0x10 = timeseek + 0x11 = byteseek & timeseek + 0x111 = no dlna + */ + + GST_DEBUG_OBJECT (src, "dlna opval = %d, flagval=%d, src->is_dtcp:%d", + src->opval, src->flagval, src->is_dtcp); + if (src->is_dtcp) { + src->seekable = + gst_soup_http_src_query_dtcp_seekable (GST_BASE_SRC_CAST (src)); + GST_DEBUG_OBJECT (src, "DTCP-IP content - seekable (%s)", + src->seekable ? "TRUE" : "FALSE"); + } else { + switch (src->opval) { + case 0x111: + GST_DEBUG_OBJECT (src, "no dlna"); + break; + case 0x00: + GST_DEBUG_OBJECT (src, "dlna - non seekable"); + src->seekable = FALSE; + break; + case 0x01: + GST_DEBUG_OBJECT (src, "dlna - byte seekable"); + src->seekable = TRUE; + break; + case 0x10: + GST_DEBUG_OBJECT (src, "dlna - time seekable"); + src->seekable = TRUE; + break; + case 0x11: + GST_DEBUG_OBJECT (src, "dlna - byte & time seekable"); + src->seekable = TRUE; + break; + + } + } if (!src->session) { GstQuery *query; gboolean can_share = (src->timeout == DEFAULT_TIMEOUT) @@ -971,7 +1207,12 @@ gst_soup_http_src_session_open (GstSoupHTTPSrc * src) GST_ELEMENT (src)); soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER); - soup_session_add_feature_by_type (src->session, SOUP_TYPE_COOKIE_JAR); + /* FIXME: Check proper usage of SOUP_TYPE_COOKIE_JAR feature */ + if (src->cookies) { + GST_DEBUG_OBJECT (src, "Cookies are set using cookies property."); + } else { + soup_session_add_feature_by_type (src->session, SOUP_TYPE_COOKIE_JAR); + } if (can_share) { GstContext *context; @@ -1028,6 +1269,16 @@ gst_soup_http_src_session_open (GstSoupHTTPSrc * src) GST_DEBUG_OBJECT (src, "Re-using session"); } + if (src->compress) + soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER); + else + soup_session_remove_feature_by_type (src->session, + SOUP_TYPE_CONTENT_DECODER); + + if (src->opval == 0x10) { + GST_DEBUG_OBJECT (src, "Set basesrc format : GST_FORMAT_TIME"); + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + } return TRUE; } @@ -1112,6 +1363,65 @@ insert_http_header (const gchar * name, const gchar * value, gpointer user_data) } } +static gboolean +gst_soup_http_src_parse_byte_range (const gchar * val, goffset * start, + goffset * end, goffset * total_length, gpointer src) +{ + gchar *header_value = NULL; + + const gchar *byteHdr = "bytes"; + gint ret_code = 0; + guint64 startBytePos = 0; + guint64 endBytePos = 0; + guint64 totalLen = 0; + + val = strstr (val, byteHdr); + if (val) + header_value = strstr (val, "="); + if (val && !header_value) + header_value = strstr (val, " "); + if (header_value) + header_value++; + else { + GST_DEBUG_OBJECT (src, + "Bytes not included in header from HEAD response field header value: %s", + val); + return FALSE; + } + + if (strstr (header_value, "/") && !strstr (header_value, "*")) { + /* Extract start and end and total_length BYTES */ + if ((ret_code = + sscanf (header_value, + "%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT "/%" + G_GUINT64_FORMAT, &startBytePos, &endBytePos, + &totalLen)) != 3) { + GST_DEBUG_OBJECT (src, + "Problems parsing BYTES from response header %s, value: %s, retcode: %d, BytesPos: %" + G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT, val, + header_value, ret_code, startBytePos, endBytePos, totalLen); + return FALSE; + } + } else { + /* Extract start and end (there is no total) BYTES */ + if ((ret_code = + sscanf (header_value, + "%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, &startBytePos, + &endBytePos)) != 2) { + GST_DEBUG_OBJECT (src, + "Problems parsing BYTES from response header %s, value: %s, retcode: %d, BytesPos: %" + G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT, val, header_value, + ret_code, startBytePos, endBytePos); + return FALSE; + } + } + + *start = startBytePos; + *end = endBytePos; + *total_length = totalLen; + return TRUE; +} + static GstFlowReturn gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, SoupMessage * msg) { @@ -1123,6 +1433,8 @@ gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, SoupMessage * msg) GstEvent *http_headers_event; GstStructure *http_headers, *headers; const gchar *accept_ranges; + goffset start = -1, end = -1, total_length = -1; + GArray *out_val_array = g_array_sized_new (FALSE, FALSE, sizeof (goffset), 4); GST_INFO_OBJECT (src, "got headers"); @@ -1172,29 +1484,97 @@ gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, SoupMessage * msg) /* Parse Content-Length. */ if (soup_message_headers_get_encoding (msg->response_headers) == SOUP_ENCODING_CONTENT_LENGTH) { + /* try to find byte position in case of DLNA time based seek */ + if (src->content_size && src->opval == 0x10) { + goffset content_length = + soup_message_headers_get_content_length (msg->response_headers); + + if (src->content_size > content_length) + src->request_position = src->content_size - content_length; + else + src->request_position = 0; + + src->read_position = src->request_position; + gst_soup_http_src_duration_set_n_post (src); + } newsize = src->request_position + soup_message_headers_get_content_length (msg->response_headers); + if (!src->have_size || (src->content_size != newsize)) { src->content_size = newsize; src->have_size = TRUE; - src->seekable = TRUE; + /* content length is not a suitable way to device whether server is seekable */ + if (src->opval != 0x00) + src->seekable = TRUE; + GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size); basesrc = GST_BASE_SRC_CAST (src); - basesrc->segment.duration = src->content_size; + gst_soup_http_src_duration_set_n_post (src); + if (src->opval != 0x10) /* No need to update for TimeSeek header. */ + basesrc->segment.duration = src->content_size; gst_element_post_message (GST_ELEMENT (src), gst_message_new_duration_changed (GST_OBJECT (src))); } + } else if (soup_message_headers_get_encoding (msg->response_headers) == + SOUP_ENCODING_CHUNKED) { + if (src->dlna_mode) { + if ((value = soup_message_headers_get_one (msg->response_headers, + "TimeSeekRange.dlna.org")) != NULL) { + if (gst_soup_http_src_parse_byte_range (value, &start, + &end, &total_length, src)) { + if (src->content_size > start) + src->request_position = start; + else + src->request_position = 0; + src->read_position = src->request_position; + } + } + gst_soup_http_src_duration_set_n_post (src); + } } + /* Parse Content-Range. */ + if (soup_message_headers_get_content_range (msg->response_headers, &start, + &end, &total_length)) { + GST_DEBUG_OBJECT (src, + "range = %" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, + start, end, total_length); + if (src->opval != 0x00) + src->seekable = TRUE; + + /* In case that DLNA mode but content size is unknown */ + if (src->dlna_mode && !src->content_size) { + if (total_length != -1) { + src->content_size = total_length; + GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size); + gst_soup_http_src_duration_set_n_post (src); + } + } + } + + /* report that we got headers along with content length; + * when using byte-ranges the content length is the size of the range + * we requested, not the full entity + */ + g_array_insert_val (out_val_array, 0, src->content_size); + g_array_insert_val (out_val_array, 1, start); + g_array_insert_val (out_val_array, 2, end); + g_array_insert_val (out_val_array, 3, total_length); + g_signal_emit (G_OBJECT (src), soup_http_src_signals[GOT_HEADERS_SIGNAL], 0, + out_val_array); + /* when the signal returns, free the array */ + g_array_free (out_val_array, TRUE); /* If the server reports Accept-Ranges: none we don't have to try * doing range requests at all */ - if ((accept_ranges = - soup_message_headers_get_one (msg->response_headers, - "Accept-Ranges"))) { - if (g_ascii_strcasecmp (accept_ranges, "none") == 0) - src->seekable = FALSE; + if (src->opval == 0x111) { + if ((accept_ranges = + soup_message_headers_get_one (msg->response_headers, + "Accept-Ranges"))) { + if (g_ascii_strcasecmp (accept_ranges, "none") == 0) + src->seekable = FALSE; + } } /* Icecast stuff */ @@ -1513,8 +1893,12 @@ gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method) G_CALLBACK (gst_soup_http_src_restarted_cb), src); } - gst_soup_http_src_add_range_header (src, src->request_position, - src->stop_position); + if (src->time_seek_flag) { + gst_soup_http_src_add_time_seek_range_header (src, src->request_time); + } else { + gst_soup_http_src_add_range_header (src, src->request_position, + src->stop_position); + } gst_soup_http_src_add_extra_headers (src); @@ -1584,14 +1968,14 @@ gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method) if (src->msg && src->request_position > 0) { gst_soup_http_src_add_range_header (src, src->request_position, src->stop_position); - } else if (src->msg && src->request_position == 0) + } else if (src->msg && src->request_position == 0) { soup_message_headers_remove (src->msg->request_headers, "Range"); + src->read_position = src->request_position; + } - /* add_range_header() has the side effect of setting read_position to - * the requested position. This *needs* to be set regardless of having - * a message or not. Failure to do so would result in calculation being - * done with stale/wrong read position */ - src->read_position = src->request_position; + if (src->msg && src->time_seek_flag) { + gst_soup_http_src_add_time_seek_range_header (src, src->request_time); + } if (!src->msg) { if (!gst_soup_http_src_build_message (src, method)) { @@ -1606,9 +1990,30 @@ gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method) ret = gst_soup_http_src_send_message (src); + if (src->request_time != GST_CLOCK_TIME_NONE) { + if (ret == GST_FLOW_CUSTOM_ERROR && + src->request_time && + src->msg->status_code != SOUP_STATUS_OK && + src->msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) { + src->seekable = FALSE; + GST_ELEMENT_ERROR (src, RESOURCE, SEEK, + (_("Server does not support DLNA time-based seeking.")), + ("Server does not accept TimeSeekRange.dlna.org HTTP header, URL: %s", + src->location)); + ret = GST_FLOW_ERROR; + } + + src->request_time = GST_CLOCK_TIME_NONE; + } + /* Check if Range header was respected. */ if (ret == GST_FLOW_OK && src->request_position > 0 && src->msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) { + /* DTCP-IP DMS sets status code as SOUP_STATUS_OK + * better than SOUP_STATUS_PARTIAL_CONTENT. */ + if (src->is_dtcp && src->msg->status_code == SOUP_STATUS_OK) + return ret; + src->seekable = FALSE; GST_ELEMENT_ERROR_WITH_DETAILS (src, RESOURCE, SEEK, (_("Server does not support seeking.")), @@ -1623,46 +2028,6 @@ gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method) return ret; } -/* - * Check if the bytes_read is above a certain threshold of the blocksize, if - * that happens a few times in a row, increase the blocksize; Do the same in - * the opposite direction to reduce the blocksize. - */ -static void -gst_soup_http_src_check_update_blocksize (GstSoupHTTPSrc * src, - gint64 bytes_read) -{ - guint blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src)); - - GST_LOG_OBJECT (src, "Checking to update blocksize. Read:%" G_GINT64_FORMAT - " blocksize:%u", bytes_read, blocksize); - - if (bytes_read >= blocksize * GROW_BLOCKSIZE_LIMIT) { - src->reduce_blocksize_count = 0; - src->increase_blocksize_count++; - - if (src->increase_blocksize_count >= GROW_BLOCKSIZE_COUNT) { - blocksize *= GROW_BLOCKSIZE_FACTOR; - GST_DEBUG_OBJECT (src, "Increased blocksize to %u", blocksize); - gst_base_src_set_blocksize (GST_BASE_SRC_CAST (src), blocksize); - src->increase_blocksize_count = 0; - } - } else if (bytes_read < blocksize * REDUCE_BLOCKSIZE_LIMIT) { - src->reduce_blocksize_count++; - src->increase_blocksize_count = 0; - - if (src->reduce_blocksize_count >= REDUCE_BLOCKSIZE_COUNT) { - blocksize *= REDUCE_BLOCKSIZE_FACTOR; - blocksize = MAX (blocksize, src->minimum_blocksize); - GST_DEBUG_OBJECT (src, "Decreased blocksize to %u", blocksize); - gst_base_src_set_blocksize (GST_BASE_SRC_CAST (src), blocksize); - src->reduce_blocksize_count = 0; - } - } else { - src->reduce_blocksize_count = src->increase_blocksize_count = 0; - } -} - static void gst_soup_http_src_update_position (GstSoupHTTPSrc * src, gint64 bytes_read) { @@ -1674,7 +2039,7 @@ gst_soup_http_src_update_position (GstSoupHTTPSrc * src, gint64 bytes_read) src->request_position = new_position; src->read_position = new_position; - if (src->have_size) { + if (src->have_size && src->content_size != 0) { if (new_position > src->content_size) { GST_DEBUG_OBJECT (src, "Got position previous estimated content size " "(%" G_GINT64_FORMAT " > %" G_GINT64_FORMAT ")", new_position, @@ -1687,15 +2052,19 @@ gst_soup_http_src_update_position (GstSoupHTTPSrc * src, gint64 bytes_read) GST_DEBUG_OBJECT (src, "We're EOS now"); } } + + g_signal_emit (G_OBJECT (src), soup_http_src_signals[GOT_CHUNK_SIGNAL], 0, + (gsize) bytes_read); } static GstFlowReturn gst_soup_http_src_read_buffer (GstSoupHTTPSrc * src, GstBuffer ** outbuf) { - gssize read_bytes; + gsize read_bytes; GstMapInfo mapinfo; GstBaseSrc *bsrc; GstFlowReturn ret; + gboolean read_ret; bsrc = GST_BASE_SRC_CAST (src); @@ -1710,10 +2079,10 @@ gst_soup_http_src_read_buffer (GstSoupHTTPSrc * src, GstBuffer ** outbuf) return GST_FLOW_ERROR; } - read_bytes = - g_input_stream_read (src->input_stream, mapinfo.data, mapinfo.size, - src->cancellable, NULL); - GST_DEBUG_OBJECT (src, "Read %" G_GSSIZE_FORMAT " bytes from http input", + read_ret = + g_input_stream_read_all (src->input_stream, mapinfo.data, mapinfo.size, + &read_bytes, src->cancellable, NULL); + GST_DEBUG_OBJECT (src, "Read %" G_GSIZE_FORMAT " bytes from http input", read_bytes); g_mutex_lock (&src->mutex); @@ -1727,15 +2096,21 @@ gst_soup_http_src_read_buffer (GstSoupHTTPSrc * src, GstBuffer ** outbuf) gst_buffer_unmap (*outbuf, &mapinfo); if (read_bytes > 0) { gst_buffer_set_size (*outbuf, read_bytes); - GST_BUFFER_OFFSET (*outbuf) = bsrc->segment.position; + + if (bsrc->segment.format == GST_FORMAT_TIME) { + GST_BUFFER_OFFSET (*outbuf) = src->read_position; + GST_LOG_OBJECT (src, "read position %" G_GUINT64_FORMAT, + src->read_position); + } else { + GST_BUFFER_OFFSET (*outbuf) = bsrc->segment.position; + } + ret = GST_FLOW_OK; gst_soup_http_src_update_position (src, read_bytes); /* Got some data, reset retry counter */ src->retry_count = 0; - gst_soup_http_src_check_update_blocksize (src, read_bytes); - /* If we're at the end of a range request, read again to let libsoup * finalize the request. This allows to reuse the connection again later, * otherwise we would have to cancel the message and close the connection @@ -1743,23 +2118,24 @@ gst_soup_http_src_read_buffer (GstSoupHTTPSrc * src, GstBuffer ** outbuf) if (bsrc->segment.stop != -1 && bsrc->segment.position + read_bytes >= bsrc->segment.stop) { guint8 tmp[128]; + gssize remaining_bytes; g_object_unref (src->msg); src->msg = NULL; src->have_body = TRUE; /* This should return immediately as we're at the end of the range */ - read_bytes = + remaining_bytes = g_input_stream_read (src->input_stream, tmp, sizeof (tmp), src->cancellable, NULL); - if (read_bytes > 0) + if (remaining_bytes > 0) GST_ERROR_OBJECT (src, - "Read %" G_GSIZE_FORMAT " bytes after end of range", read_bytes); + "Read %" G_GSSIZE_FORMAT " bytes after end of range", + remaining_bytes); } } else { gst_buffer_unref (*outbuf); - if (read_bytes < 0 || - (src->have_size && src->read_position < src->content_size)) { + if (!read_ret || (src->have_size && src->read_position < src->content_size)) { /* Maybe the server disconnected, retry */ ret = GST_FLOW_CUSTOM_ERROR; } else { @@ -1787,7 +2163,7 @@ gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf) g_mutex_lock (&src->mutex); /* Check for pending position change */ - if (src->request_position != src->read_position) { + if (src->request_position != src->read_position || src->time_seek_flag) { if (src->input_stream) { g_input_stream_close (src->input_stream, src->cancellable, NULL); g_object_unref (src->input_stream); @@ -1999,6 +2375,8 @@ gst_soup_http_src_is_seekable (GstBaseSrc * bsrc) gst_soup_http_src_check_seekable (src); + GST_INFO_OBJECT (src, "seekable : %d", src->seekable); + return src->seekable; } @@ -2009,6 +2387,20 @@ gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", segment->start, segment->stop); + + if ((segment->format == GST_FORMAT_TIME) && ((src->opval == 0x10) + || (src->opval == 0x11))) { + if (src->read_position == 0 && segment->start == 0) { + GST_DEBUG_OBJECT (src, "Ignore initial zero time seek"); + return TRUE; + } + + src->time_seek_flag = TRUE; + src->request_time = segment->start; + + goto end; + } + if (src->read_position == segment->start && src->request_position == src->read_position && src->stop_position == segment->stop) { @@ -2021,12 +2413,25 @@ gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) /* If we have no headers we don't know yet if it is seekable or not. * Store the start position and error out later if it isn't */ - if (src->got_headers && !src->seekable) { + if (src->got_headers && (!src->seekable || src->opval == 0x00)) { GST_WARNING_OBJECT (src, "Not seekable"); return FALSE; } - if (segment->rate < 0.0 || segment->format != GST_FORMAT_BYTES) { + if (src->is_dtcp) { + if (!(src->flagval & 0x100)) { + GST_WARNING_OBJECT (src, "Not supported Cleartext-Byte seek."); + return FALSE; + } + } else { + if ((src->opval == 0x00) || (src->opval == 0x10)) { + GST_WARNING_OBJECT (src, "Not Accepted seek segment, opval:0x%02x", + src->opval); + return FALSE; + } + } + /* In the case of DLNA, should support negative rate for seek. */ + if (segment->format != GST_FORMAT_BYTES) { GST_WARNING_OBJECT (src, "Invalid seek segment"); return FALSE; } @@ -2040,6 +2445,9 @@ gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment) src->request_position = segment->start; src->stop_position = segment->stop; +end: + src->last_seek_format = segment->format; + return TRUE; } @@ -2061,6 +2469,33 @@ gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query) } ret = TRUE; break; + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 duration = (gint64) src->content_size; + + gst_query_parse_duration (query, &format, NULL); + + if (format != GST_FORMAT_BYTES || !duration) { + GST_WARNING_OBJECT (src, + "duration query: false (format %s, duration %" G_GINT64_FORMAT ")", + gst_format_get_name (format), duration); + + ret = FALSE; + } else { + GST_DEBUG_OBJECT (src, "duration query: true (duration %" + G_GINT64_FORMAT ")", duration); + + gst_query_set_duration (query, format, duration); + + ret = TRUE; + } + + return ret; + } + case GST_QUERY_CUSTOM: + ret = gst_soup_http_src_handle_custom_query (src, query); + break; default: ret = FALSE; break; @@ -2180,3 +2615,171 @@ gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data) iface->get_uri = gst_soup_http_src_uri_get_uri; iface->set_uri = gst_soup_http_src_uri_set_uri; } + +static void +gst_soup_http_src_duration_set_n_post (GstSoupHTTPSrc * src) +{ + GstBaseSrc *bsrc; + + if (!src->content_size) { + GST_DEBUG_OBJECT (src, "invalid: content size is zero\n"); + return; + } + + bsrc = GST_BASE_SRC_CAST (src); + + if (bsrc->segment.format != GST_FORMAT_BYTES + && bsrc->segment.format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (src, + "invalid: src format. src is not bytes and not time format\n"); + return; + } + if (bsrc->segment.format == GST_FORMAT_TIME) { + bsrc->segment.duration = -1; + /* This change is required for MP4(qtdemux) */ + bsrc->segment.base = src->request_position; + } else { + bsrc->segment.duration = src->content_size; + src->have_size = TRUE; + } + gst_element_post_message (GST_ELEMENT (src), + gst_message_new_duration_changed (GST_OBJECT (src))); +} + +static gboolean +gst_soup_http_src_add_time_seek_range_header (GstSoupHTTPSrc * src, + guint64 offset) +{ + gchar buf[64]; + + gint rc; + + soup_message_headers_remove (src->msg->request_headers, + "TimeSeekRange.dlna.org"); + if (offset != GST_CLOCK_TIME_NONE) { + rc = g_snprintf (buf, sizeof (buf), "npt=%" GST_NPT_TIME_FORMAT "-", + GST_NPT_TIME_ARGS (offset)); + if (rc > sizeof (buf) || rc < 0) + return FALSE; + soup_message_headers_append (src->msg->request_headers, + "TimeSeekRange.dlna.org", buf); + } + src->time_seek_flag = FALSE; + return TRUE; +} + +static gboolean +gst_soup_http_src_add_cleartext_range_header (GstSoupHTTPSrc * src, + guint64 offset) +{ + const gchar *range_header = "Range.dtcp.com"; + gchar buf[64]; + gint rc; + + soup_message_headers_remove (src->msg->request_headers, range_header); + if (offset) { + rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-", offset); + if (rc > sizeof (buf) || rc < 0) + return FALSE; + soup_message_headers_append (src->msg->request_headers, range_header, buf); + } + src->read_position = src->request_position; + return TRUE; +} + +static gboolean +gst_soup_http_src_handle_custom_query (GstSoupHTTPSrc * src, GstQuery * query) +{ + SoupSession *session; + SoupMessage *msg = NULL; + GstStructure *structure; + gchar *range; + goffset content_length = 0; + const gchar *content_range = ""; + + structure = (GstStructure *) gst_query_get_structure (query); + + /* Validate query */ + if ((!gst_structure_has_name (structure, "smart-properties")) && + (!gst_structure_has_name (structure, "vdec-buffer-ts")) && + (!gst_structure_has_name (structure, "CleartextSeekInfo"))) { + GST_WARNING_OBJECT (src, "Unknown custom query (%s)", + gst_structure_get_name (structure)); + return FALSE; + } + + if (src->is_dtcp && !(src->flagval & 0x100)) { + GST_WARNING_OBJECT (src, + "This source does not support Cleartext-Byte seek."); + return FALSE; + } + + if (!gst_structure_get (structure, + "Range.dtcp.com", G_TYPE_STRING, &range, NULL)) + return FALSE; + + /* Get requested Cleartext-Byte seek potision */ + if (!g_str_has_prefix (range, "bytes=")) + return FALSE; + sscanf (range + strlen ("bytes="), "%" G_GUINT64_FORMAT "%*c%*u", + &src->request_cb_position); + + /* Prepare HTTP HEAD message */ + msg = soup_message_new (SOUP_METHOD_HEAD, src->location); + + soup_message_headers_append (msg->request_headers, "Connection", "close"); + soup_message_headers_append (msg->request_headers, "Range.dtcp.com", range); + + /* Send HTTP HEAD message */ + session = soup_session_sync_new_with_options (SOUP_SESSION_TIMEOUT, 3, NULL); + + /* Set the flag for cancelling current transfer */ + src->cancel = TRUE; + /* This function will block until received response */ + soup_session_send_message (session, msg); + src->cancel = FALSE; + + g_object_unref (session); + + if (msg->status_code != SOUP_STATUS_OK) { + g_object_unref (msg); + return FALSE; + } + + /* Collect information from the response */ + content_length = + soup_message_headers_get_content_length (msg->response_headers); + content_range = + soup_message_headers_get_one (msg->response_headers, + "Content-Range.dtcp.com"); + + /* Make query reply */ + gst_structure_set (structure, + "CONTENT-LENGTH", G_TYPE_UINT64, content_length, + "Content-Range.dtcp.com", G_TYPE_STRING, content_range, NULL); + + g_object_unref (msg); + return TRUE; +} + +static gboolean +gst_soup_http_src_query_dtcp_seekable (GstBaseSrc * bsrc) +{ + GstQuery *query; + gboolean byte_seekable = FALSE; + gboolean time_seekable = FALSE; + + /* check byte seekable */ + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (gst_pad_peer_query (bsrc->srcpad, query)) + gst_query_parse_seeking (query, NULL, &byte_seekable, NULL, NULL); + gst_query_unref (query); + + /* check time seekable */ + query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_pad_peer_query (bsrc->srcpad, query)) + gst_query_parse_seeking (query, NULL, &time_seekable, NULL, NULL); + gst_query_unref (query); + + return (byte_seekable || time_seekable); +} diff --git a/ext/soup/gstsouphttpsrc.h b/ext/soup/gstsouphttpsrc.h index 06851ddfa2..7ef61f8b39 100644 --- a/ext/soup/gstsouphttpsrc.h +++ b/ext/soup/gstsouphttpsrc.h @@ -67,6 +67,7 @@ struct _GstSoupHTTPSrc { gint retry_count; /* Number of retries since we received data */ gint max_retries; /* Maximum number of retries */ gchar *method; /* HTTP method */ + gboolean cancel; /* Cancel current message. */ gboolean got_headers; /* Already received headers from the server */ gboolean have_size; /* Received and parsed Content-Length @@ -75,6 +76,11 @@ struct _GstSoupHTTPSrc { guint64 read_position; /* Current position. */ gboolean seekable; /* FALSE if the server does not support Range. */ + guint32 opval; + guint32 flagval; + gboolean dlna_mode; + gboolean is_dtcp; + guint64 request_position; /* Seek to this position. */ guint64 stop_position; /* Stop at this position. */ gboolean have_body; /* Indicates if it has just been signaled the @@ -88,13 +94,16 @@ struct _GstSoupHTTPSrc { gboolean ssl_use_system_ca_file; GTlsDatabase *tls_database; GTlsInteraction *tls_interaction; + GstFormat last_seek_format; + gboolean time_seek_flag; + guint64 request_time; /* Seek to this position. */ + guint64 request_cb_position; /* Cleartext seek position */ GCancellable *cancellable; GInputStream *input_stream; - gint reduce_blocksize_count; - gint increase_blocksize_count; - guint minimum_blocksize; + guint64 start_offset; /* First byte of a byte range */ + guint64 end_offset; /* Last byte of a byte range */ /* Shoutcast/icecast metadata extraction handling. */ gboolean iradio_mode; @@ -119,6 +128,8 @@ struct _GstSoupHTTPSrc { struct _GstSoupHTTPSrcClass { GstPushSrcClass parent_class; + void (*got_headers) (GstSoupHTTPSrc * soupHTTPsrc, GArray * out_val_array); + void (*got_chunk) (GstElement * element, gsize size); }; GType gst_soup_http_src_get_type (void); diff --git a/ext/taglib/gstid3v2mux.cc b/ext/taglib/gstid3v2mux.cc index 3ec9116540..634c6ef2ba 100644 --- a/ext/taglib/gstid3v2mux.cc +++ b/ext/taglib/gstid3v2mux.cc @@ -463,7 +463,7 @@ add_image_tag (ID3v2::Tag * id3v2tag, const GstTagList * list, if (info_struct) { if (gst_structure_get (info_struct, "image-type", - GST_TYPE_TAG_IMAGE_TYPE, &image_type, NULL)) { + GST_TYPE_TAG_IMAGE_TYPE, &image_type, (void *) NULL)) { if (image_type > 0 && image_type <= 18) { image_type += 2; } else { diff --git a/ext/wavpack/Makefile.am b/ext/wavpack/Makefile.am index e6ed6d48f6..0bc8ef53d9 100644 --- a/ext/wavpack/Makefile.am +++ b/ext/wavpack/Makefile.am @@ -5,7 +5,10 @@ libgstwavpack_la_SOURCES = \ gstwavpackcommon.c \ gstwavpackdec.c \ gstwavpackenc.c \ - gstwavpackstreamreader.c + gstwavpackstreamreader.c \ + gstwvccombiner.c \ + gstwvcmeta.c \ + gstwvfilesrc.c libgstwavpack_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(WAVPACK_CFLAGS) @@ -17,5 +20,7 @@ noinst_HEADERS = \ gstwavpackdec.h \ gstwavpackenc.h \ gstwavpackcommon.h \ - gstwavpackstreamreader.h - + gstwavpackstreamreader.h \ + gstwvccombiner.h \ + gstwvcmeta.h \ + gstwvfilesrc.h diff --git a/ext/wavpack/gstwavpack.c b/ext/wavpack/gstwavpack.c index 1609aa2e1e..ee1cad34af 100644 --- a/ext/wavpack/gstwavpack.c +++ b/ext/wavpack/gstwavpack.c @@ -27,6 +27,8 @@ #include "gstwavpackdec.h" #include "gstwavpackenc.h" +#include "gstwvccombiner.h" +#include "gstwvfilesrc.h" /* debug category for common code */ GST_DEBUG_CATEGORY (wavpack_debug); @@ -44,6 +46,8 @@ plugin_init (GstPlugin * plugin) #endif return (gst_wavpack_dec_plugin_init (plugin) + && gst_wvc_combiner_plugin_init (plugin) + && gst_wv_file_src_plugin_init (plugin) && gst_wavpack_enc_plugin_init (plugin)); } diff --git a/ext/wavpack/gstwavpackdec.c b/ext/wavpack/gstwavpackdec.c index 7cce543226..baea147408 100644 --- a/ext/wavpack/gstwavpackdec.c +++ b/ext/wavpack/gstwavpackdec.c @@ -52,7 +52,7 @@ #include "gstwavpackdec.h" #include "gstwavpackcommon.h" #include "gstwavpackstreamreader.h" - +#include "gstwvcmeta.h" GST_DEBUG_CATEGORY_STATIC (gst_wavpack_dec_debug); #define GST_CAT_DEFAULT gst_wavpack_dec_debug @@ -61,6 +61,10 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-wavpack, " + "depth = (int) [ 1, 32 ], " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true;" + "audio/x-wavpack(meta:GstWVCorrection), " "depth = (int) [ 1, 32 ], " "channels = (int) [ 1, 8 ], " "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true") @@ -124,9 +128,15 @@ gst_wavpack_dec_class_init (GstWavpackDecClass * klass) static void gst_wavpack_dec_reset (GstWavpackDec * dec) { + dec->wv_id.name = "wv"; dec->wv_id.buffer = NULL; dec->wv_id.position = dec->wv_id.length = 0; + dec->wvc_id.name = "wvc"; + dec->wvc_id.buffer = NULL; + dec->wvc_id.position = 0; + dec->wvc_id.length = 0; + dec->channels = 0; dec->channel_mask = 0; dec->sample_rate = 0; @@ -272,6 +282,7 @@ static GstFlowReturn gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) { GstWavpackDec *dec; + GstWVCMeta *wvc_meta = NULL; GstBuffer *outbuf = NULL; GstFlowReturn ret = GST_FLOW_OK; WavpackHeader wph; @@ -280,6 +291,7 @@ gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) gint width, depth, i, j, max; gint32 *dec_data = NULL; guint8 *out_data; + GstMapInfo wvc_map = GST_MAP_INFO_INIT; GstMapInfo map, omap; dec = GST_WAVPACK_DEC (bdec); @@ -305,6 +317,17 @@ gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) dec->wv_id.length = map.size; dec->wv_id.position = 0; + wvc_meta = gst_buffer_get_wvc_meta (buf); + if (wvc_meta && gst_buffer_map (wvc_meta->wvc_buf, &wvc_map, GST_MAP_READ)) { + dec->wvc_id.buffer = wvc_map.data; + dec->wvc_id.length = wvc_map.size; + dec->wvc_id.position = 0; + } else { + dec->wvc_id.buffer = NULL; + dec->wvc_id.length = 0; + dec->wvc_id.position = 0; + } + /* create a new wavpack context if there is none yet but if there * was already one (i.e. caps were set on the srcpad) check whether * the new one has the same caps */ @@ -312,7 +335,7 @@ gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) gchar error_msg[80]; dec->context = WavpackOpenFileInputEx (dec->stream_reader, - &dec->wv_id, NULL, error_msg, OPEN_STREAMING, 0); + &dec->wv_id, &dec->wvc_id, error_msg, OPEN_STREAMING, 0); /* expect this to work */ if (!dec->context) { @@ -406,6 +429,11 @@ gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) g_assert_not_reached (); } + if (wvc_meta != NULL && wvc_map.data != NULL) { + gst_buffer_unmap (wvc_meta->wvc_buf, &wvc_map); + wvc_meta = NULL; + } + gst_buffer_unmap (outbuf, &omap); gst_buffer_unmap (buf, &map); buf = NULL; @@ -415,8 +443,13 @@ gst_wavpack_dec_handle_frame (GstAudioDecoder * bdec, GstBuffer * buf) ret = gst_audio_decoder_finish_frame (bdec, outbuf, 1); out: - if (buf) + + if (buf) { + if (wvc_meta != NULL && wvc_map.data != NULL) { + gst_buffer_unmap (wvc_meta->wvc_buf, &wvc_map); + } gst_buffer_unmap (buf, &map); + } if (G_UNLIKELY (ret != GST_FLOW_OK)) { GST_DEBUG_OBJECT (dec, "flow: %s", gst_flow_get_name (ret)); diff --git a/ext/wavpack/gstwavpackdec.h b/ext/wavpack/gstwavpackdec.h index 8a002b4bf6..6667e80144 100644 --- a/ext/wavpack/gstwavpackdec.h +++ b/ext/wavpack/gstwavpackdec.h @@ -54,6 +54,7 @@ struct _GstWavpackDec WavpackStreamReader *stream_reader; read_id wv_id; + read_id wvc_id; gint sample_rate; gint depth; diff --git a/ext/wavpack/gstwavpackstreamreader.c b/ext/wavpack/gstwavpackstreamreader.c index dd29a37280..2abd45ead3 100644 --- a/ext/wavpack/gstwavpackstreamreader.c +++ b/ext/wavpack/gstwavpackstreamreader.c @@ -35,8 +35,8 @@ gst_wavpack_stream_reader_read_bytes (void *id, void *data, int32_t bcount) uint32_t left = rid->length - rid->position; uint32_t to_read = MIN (left, bcount); - GST_DEBUG ("Trying to read %d of %d bytes from position %d", bcount, - rid->length, rid->position); + GST_DEBUG ("%s: Trying to read %d of %d bytes from position %d", rid->name, + bcount, rid->length, rid->position); if (to_read > 0) { memmove (data, rid->buffer + rid->position, to_read); diff --git a/ext/wavpack/gstwavpackstreamreader.h b/ext/wavpack/gstwavpackstreamreader.h index 17412cc6c3..76f9e7b9d6 100644 --- a/ext/wavpack/gstwavpackstreamreader.h +++ b/ext/wavpack/gstwavpackstreamreader.h @@ -26,6 +26,7 @@ typedef struct { + const char *name; /* for logging */ guint8 *buffer; uint32_t length; uint32_t position; diff --git a/ext/wavpack/gstwvccombiner.c b/ext/wavpack/gstwvccombiner.c new file mode 100644 index 0000000000..418bf4f60f --- /dev/null +++ b/ext/wavpack/gstwvccombiner.c @@ -0,0 +1,266 @@ +/* GStreamer Wavpack correction stream combiner + * Copyright (c) 2018 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-wvccombiner + * + * This element combines a lossily encoded WavPack stream with the matching + * correction file so that the decoder can restore the original lossless output. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=test_suite/hybrid_bitrates/128kbps.wv + * ! wavpackparse ! wvccombiner name=combiner ! wavpackdec ! pulsesink + * filesrc location=test_suite/hybrid_bitrates/128kbps.wvc ! wavpackparse ! combiner.wvc_sink + * ]| This pipeline combines the correction data from the .wvc file with the + * lossily compressed wavpack data from the .wv file and feeds both to the + * wavpack decoder which will decode the stream and restore the original + * lossless audio data using the correction data on top of the lossily + * compressed audio data. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstwvccombiner.h" +#include "gstwvcmeta.h" + +/* we only extract the fields we need here */ +typedef struct +{ + guint16 version; + guint64 index; + guint32 samples; /* 0 = non-audio block */ + enum + { + MODE_LOSSLESS, + MODE_HYBRID + } mode; +} BlockHeader; + +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack(meta:GstWVCorrection), " + "depth = (int) [ 1, 32 ], " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true;")); + +static GstStaticPadTemplate wv_sink_template = + GST_STATIC_PAD_TEMPLATE ("wv_sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-wavpack, " + "depth = (int) [ 1, 32 ], " + "channels = (int) [ 1, 8 ], " + "rate = (int) [ 6000, 192000 ], " "framed = (boolean) true;")); + +static GstStaticPadTemplate wvc_sink_template = + GST_STATIC_PAD_TEMPLATE ("wvc_sink", GST_PAD_SINK, GST_PAD_REQUEST, + GST_STATIC_CAPS ("audio/x-wavpack-correction, framed = (boolean) true;")); + +GST_DEBUG_CATEGORY_STATIC (gst_wvc_combiner_debug); +#define GST_CAT_DEFAULT gst_wvc_combiner_debug + +G_DEFINE_TYPE (GstWvcCombiner, gst_wvc_combiner, GST_TYPE_AGGREGATOR); + +static GstFlowReturn gst_wvc_combiner_aggregate (GstAggregator * agg, + gboolean timeout); +static GstAggregatorPad *gst_wvc_combiner_create_new_pad (GstAggregator * agg, + GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps); + +static void +gst_wvc_combiner_class_init (GstWvcCombinerClass * klass) +{ + GstAggregatorClass *aggregator_class = (GstAggregatorClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + gst_element_class_add_static_pad_template_with_gtype (element_class, + &src_template, GST_TYPE_AGGREGATOR_PAD); + + gst_element_class_add_static_pad_template (element_class, &wv_sink_template); + gst_element_class_add_static_pad_template (element_class, &wvc_sink_template); + + gst_element_class_set_static_metadata (element_class, "WavPack Combiner", + "Codec/Combiner/Audio", "WavPack Correction Stream Combiner", + "Tim-Philipp Müller "); + + aggregator_class->aggregate = gst_wvc_combiner_aggregate; + aggregator_class->create_new_pad = gst_wvc_combiner_create_new_pad; +} + +static void +gst_wvc_combiner_init (GstWvcCombiner * combiner) +{ + GstAggregator *aggregator = GST_AGGREGATOR_CAST (combiner); + GstPadTemplate *templ = gst_static_pad_template_get (&wv_sink_template); + + combiner->wv_sink = g_object_new (GST_TYPE_AGGREGATOR_PAD, + "name", "wv_sink", "direction", GST_PAD_SINK, "template", templ, NULL); + gst_object_unref (templ); + + gst_element_add_pad (GST_ELEMENT (combiner), combiner->wv_sink); + + gst_segment_init (&GST_AGGREGATOR_PAD (aggregator->srcpad)->segment, + GST_FORMAT_TIME); +} + +static GstAggregatorPad * +gst_wvc_combiner_create_new_pad (GstAggregator * aggregator, + GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) +{ + GstWvcCombiner *combiner = GST_WVC_COMBINER (aggregator); + const gchar *templ_name = GST_PAD_TEMPLATE_NAME_TEMPLATE (templ); + + if (g_strcmp0 (templ_name, "wvc_sink") != 0) { + GST_ERROR_OBJECT (combiner, "Unexpected pad template %s", templ_name); + return NULL; + } + + GST_OBJECT_LOCK (combiner); + + if (combiner->wvc_sink != NULL) { + GST_ERROR_OBJECT (combiner, "Pad for template %s already exists, can only " + "have one", templ_name); + GST_OBJECT_UNLOCK (combiner); + return NULL; + } + + combiner->wvc_sink = g_object_new (GST_TYPE_AGGREGATOR_PAD, + "name", templ_name, "direction", GST_PAD_SINK, "template", templ, NULL); + + GST_OBJECT_UNLOCK (combiner); + + return GST_AGGREGATOR_PAD_CAST (combiner->wvc_sink); +} + +static gboolean +gst_wvc_combiner_parse_block_header (GstAggregatorPad * pad, BlockHeader * hdr, + GstBuffer * buf) +{ + GstMapInfo map = GST_MAP_INFO_INIT; + GstByteReader br; + gboolean ret = FALSE; + guint32 u32, flags; + guint8 u8; + + if (!gst_buffer_map (buf, &map, GST_MAP_READ)) + return FALSE; + + if (map.size < 32) + goto out; + + gst_byte_reader_init (&br, map.data, map.size); + gst_byte_reader_skip (&br, 4 + 4); /* id + size */ + hdr->version = gst_byte_reader_get_uint16_le_unchecked (&br); + u8 = gst_byte_reader_get_uint8_unchecked (&br); + gst_byte_reader_skip (&br, 1 + 4); /* total_samples */ + u32 = gst_byte_reader_get_uint32_le_unchecked (&br); + hdr->index = (((guint64) u8) << 32) | u32; + hdr->samples = gst_byte_reader_get_uint32_le_unchecked (&br); + flags = gst_byte_reader_get_uint32_le_unchecked (&br); + hdr->mode = ((flags & 0x08)) ? MODE_HYBRID : MODE_LOSSLESS; + + GST_LOG_OBJECT (pad, "Block: index %" G_GINT64_MODIFIER "u, " + "samples %u, mode %s, flags 0x%08x", hdr->index, hdr->samples, + hdr->mode == MODE_HYBRID ? "hybrid" : "lossless", flags); + + ret = TRUE; + +out: + + gst_buffer_unmap (buf, &map); + return ret; +} + +static GstFlowReturn +gst_wvc_combiner_aggregate (GstAggregator * aggregator, gboolean timeout) +{ + GstWvcCombiner *combiner = GST_WVC_COMBINER (aggregator); + GstAggregatorPad *wv_sink = GST_AGGREGATOR_PAD (combiner->wv_sink); + GstAggregatorPad *wvc_sink = NULL; + GstBuffer *buf, *wvc_buf; + BlockHeader hdr = { 0, }; + + if (combiner->wvc_sink != NULL) + wvc_sink = GST_AGGREGATOR_PAD (combiner->wvc_sink); + + if (gst_aggregator_pad_is_eos (wv_sink)) { + if (wvc_sink != NULL && !gst_aggregator_pad_is_eos (wvc_sink)) { + GST_WARNING_OBJECT (combiner, "Have more correction data, but main " + "stream is already EOS, very unexpected!"); + gst_aggregator_pad_drop_buffer (wvc_sink); + } + return GST_FLOW_EOS; + } + + buf = gst_aggregator_pad_pop_buffer (wv_sink); + GST_LOG_OBJECT (wv_sink, "buffer %" GST_PTR_FORMAT, buf); + + if (!gst_wvc_combiner_parse_block_header (wv_sink, &hdr, buf)) { + GST_WARNING_OBJECT (wv_sink, "Couldn't parse wavpack header from buffer"); + gst_buffer_unref (buf); + return GST_FLOW_OK; + } + + /* No need for correction data in lossless mode */ + if (hdr.mode == MODE_LOSSLESS) + goto finish; + + /* Check if block contains audio data at all. If not we just push it out + * without combining it with data from the correction stream, as the + * correction stream won't have matching blocks for any non-audio blocks. */ + if (hdr.samples == 0) { + GST_DEBUG_OBJECT (wv_sink, "Buffer has no audio data"); + goto finish; + } + + /* Do we have correction data? */ + if (wvc_sink != NULL && gst_aggregator_pad_has_buffer (wvc_sink)) { + BlockHeader wvc_hdr = { 0, }; + + wvc_buf = gst_aggregator_pad_pop_buffer (wvc_sink); + GST_LOG_OBJECT (wvc_sink, "buffer %" GST_PTR_FORMAT, wvc_buf); + if (gst_wvc_combiner_parse_block_header (wvc_sink, &wvc_hdr, wvc_buf)) { + if (wvc_hdr.index == hdr.index) { + gst_buffer_add_wvc_meta (buf, wvc_buf); + } else { + GST_WARNING_OBJECT (wvc_sink, "Correction data offset mismatch"); + } + } else { + GST_WARNING_OBJECT (wvc_sink, "Couldn't parse wavpack header"); + } + gst_buffer_unref (wvc_buf); + } + +finish: + + return gst_aggregator_finish_buffer (aggregator, buf); +} + +gboolean +gst_wvc_combiner_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_wvc_combiner_debug, "wvccombiner", 0, + "Wavpack correction data combiner"); + + /* FIXME: set non-zero rank? */ + return gst_element_register (plugin, "wvccombiner", GST_RANK_SECONDARY, + GST_TYPE_WVC_COMBINER); +} diff --git a/ext/wavpack/gstwvccombiner.h b/ext/wavpack/gstwvccombiner.h new file mode 100644 index 0000000000..766c036755 --- /dev/null +++ b/ext/wavpack/gstwvccombiner.h @@ -0,0 +1,52 @@ +/* GStreamer Wavpack correction stream combiner + * Copyright (c) 2018 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#ifndef GST_WAVPACK_CORRECTION_COMBINER_H +#define GST_WAVPACK_CORRECTION_COMBINER_H + +#define GST_TYPE_WVC_COMBINER (gst_wvc_combiner_get_type ()) +#define GST_WVC_COMBINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WVC_COMBINER, GstWvcCombiner)) +#define GST_WVC_COMBINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WVC_COMBINER, GstWvcCombinerClass)) +#define GST_WVC_COMBINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_WVC_COMBINER, GstWvcCombinerClass)) + +typedef struct _GstWvcCombiner GstWvcCombiner; +typedef struct _GstWvcCombinerClass GstWvcCombinerClass; + +struct _GstWvcCombiner +{ + GstAggregator aggregator; + + /*< private >*/ + GstPad *wv_sink; + GstPad *wvc_sink; +}; + +struct _GstWvcCombinerClass +{ + GstAggregatorClass aggregator_class; +}; + +GType gst_wvc_combiner_get_type (void); + +gboolean gst_wvc_combiner_plugin_init (GstPlugin * plugin); + +#endif /* GST_WAVPACK_CORRECTION_COMBINER_H */ diff --git a/ext/wavpack/gstwvcmeta.c b/ext/wavpack/gstwvcmeta.c new file mode 100644 index 0000000000..bc632a2556 --- /dev/null +++ b/ext/wavpack/gstwvcmeta.c @@ -0,0 +1,126 @@ +/* GStreamer Wavpack correction stream combiner + * Copyright (c) 2018 Tim-Philipp Müller + * + * 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 wvc_metaied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstwvcmeta.h" + +GST_DEBUG_CATEGORY_EXTERN (wavpack_debug); +#define GST_CAT_DEFAULT wavpack_debug + +GType +gst_wvc_meta_api_get_type (void) +{ + static volatile GType type; + static const gchar *tags[] = { NULL }; /* FIXME: review tags */ + + if (g_once_init_enter (&type)) { + GType _type = gst_meta_api_type_register ("GstWVCMetaAPI", tags); + g_once_init_leave (&type, _type); + } + return type; +} + +static gboolean +gst_wvc_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) +{ + GstWVCMeta *wvc_meta = (GstWVCMeta *) meta; + + wvc_meta->wvc_buf = NULL; + return TRUE; +} + +static void +gst_wvc_meta_clear (GstMeta * meta, GstBuffer * buffer) +{ + GstWVCMeta *wvc_meta = (GstWVCMeta *) meta; + + gst_buffer_replace (&wvc_meta->wvc_buf, NULL); +} + +static gboolean +gst_wvc_meta_transform (GstBuffer * dest, GstMeta * meta, + GstBuffer * buffer, GQuark type, gpointer data) +{ + GstWVCMeta *smeta; + GstWVCMeta *dmeta; + + smeta = (GstWVCMeta *) meta; + + if (GST_META_TRANSFORM_IS_COPY (type)) { + dmeta = gst_buffer_add_wvc_meta (dest, smeta->wvc_buf); + if (!dmeta) + return FALSE; + } else { + return FALSE; + } + + return TRUE; +} + +const GstMetaInfo * +gst_wvc_meta_get_info (void) +{ + static const GstMetaInfo *wvc_meta_info = NULL; + + if (g_once_init_enter ((GstMetaInfo **) & wvc_meta_info)) { + const GstMetaInfo *meta = + gst_meta_register (GST_WVC_META_API_TYPE, "GstWVCMeta", + sizeof (GstWVCMeta), gst_wvc_meta_init, gst_wvc_meta_clear, + gst_wvc_meta_transform); + g_once_init_leave ((GstMetaInfo **) & wvc_meta_info, (GstMetaInfo *) meta); + } + return wvc_meta_info; +} + +/* Add Wavpack correction data to a buffer */ +GstWVCMeta * +gst_buffer_add_wvc_meta (GstBuffer * buffer, GstBuffer * wvc_buf) +{ + GstWVCMeta *wvc_meta; + GstWVCMeta *meta; + + g_return_val_if_fail (wvc_buf != NULL, NULL); + + if (wvc_buf == NULL) + return NULL; + + meta = (GstWVCMeta *) gst_buffer_add_meta (buffer, GST_WVC_META_INFO, NULL); + + GST_LOG ("Adding %u bytes of WVC data to buffer %p", + (guint) gst_buffer_get_size (wvc_buf), buffer); + + wvc_meta = (GstWVCMeta *) meta; + wvc_meta->wvc_buf = gst_buffer_ref (wvc_buf); + + return meta; +} + +GstWVCMeta * +gst_buffer_get_wvc_meta (GstBuffer * buffer) +{ + gpointer state = NULL; + GstMeta *m; + + m = gst_buffer_iterate_meta_filtered (buffer, &state, GST_WVC_META_API_TYPE); + + return (GstWVCMeta *) m; +} diff --git a/ext/wavpack/gstwvcmeta.h b/ext/wavpack/gstwvcmeta.h new file mode 100644 index 0000000000..35b8286f37 --- /dev/null +++ b/ext/wavpack/gstwvcmeta.h @@ -0,0 +1,46 @@ +/* GStreamer Wavpack correction stream combiner + * Copyright (c) 2018 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#ifndef GST_WVC_META_H +#define GST_WVC_META_H + +/* private meta shared between combiner and decoder */ +typedef struct { + /*< private >*/ + GstMeta meta; + GstBuffer *wvc_buf; +} GstWVCMeta; + +G_GNUC_INTERNAL GType gst_wvc_meta_get_type (void); +G_GNUC_INTERNAL GType gst_wvc_meta_api_get_type (void); +G_GNUC_INTERNAL const GstMetaInfo * gst_wvc_meta_get_info (void); + +#define GST_WVC_META_API_TYPE (gst_wvc_meta_api_get_type()) +#define GST_WVC_META_INFO (gst_wvc_meta_get_info()) + +/* Add Wavpack correction data to a buffer */ +G_GNUC_INTERNAL +GstWVCMeta * gst_buffer_add_wvc_meta (GstBuffer * buffer, GstBuffer * wvc_buf); + +G_GNUC_INTERNAL +GstWVCMeta * gst_buffer_get_wvc_meta (GstBuffer * buffer); + +#endif /* GST_WVC_META_H */ diff --git a/ext/wavpack/gstwvfilesrc.c b/ext/wavpack/gstwvfilesrc.c new file mode 100644 index 0000000000..1448afadea --- /dev/null +++ b/ext/wavpack/gstwvfilesrc.c @@ -0,0 +1,503 @@ +/* GStreamer Wavpack File Source + * Copyright (C) 2018 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-wvfilesrc + * @see_also: filesrc + * + * This element is only useful for testing the wvcombiner element in an + * autoplugging context. It will create a filesrc operating in push mode for + * the file URI passed. + * + * If there is also a .wvc file with the same basename in the same directory + * it will plug a second filesrc and expose a second pad for the correction + * data. + * + * FIXME: GstStream announcements + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstwvfilesrc.h" + +#include +#include + +GST_DEBUG_CATEGORY_STATIC (wvfilesrc_debug); +#define GST_CAT_DEFAULT wvfilesrc_debug + +enum +{ + PROP_0, + PROP_LOCATION, +}; + +static void gst_wv_file_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_wv_file_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static GstStateChangeReturn gst_wv_file_src_change_state (GstElement * element, + GstStateChange transition); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static void gst_wv_file_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +#define gst_wv_file_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstWvFileSrc, gst_wv_file_src, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, + gst_wv_file_src_uri_handler_init)); + +static void +gst_wv_file_src_class_init (GstWvFileSrcClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = gst_wv_file_src_set_property; + gobject_class->get_property = gst_wv_file_src_get_property; + + g_object_class_install_property (gobject_class, PROP_LOCATION, + g_param_spec_string ("location", "File Location", + "Location of the file to read", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + + element_class->change_state = gst_wv_file_src_change_state; + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + + gst_element_class_set_static_metadata (element_class, "Wavpack File Source", + "Testing", + "Implements wvfile:// URI-handler for wavpack correction file testing", + "Tim-Philipp Müller "); +} + +static void +gst_wv_file_set_location (GstWvFileSrc * src, WvFile * wvfile, + const gchar * location) +{ + if (wvfile->uri) + gst_uri_set_path (wvfile->uri, location); + else + wvfile->uri = gst_uri_new ("wvfile", NULL, "", 0, location, NULL, NULL); +} + +static gchar * +gst_wv_file_get_location (GstWvFileSrc * src, WvFile * wvfile) +{ + if (wvfile->uri == NULL) + return NULL; + + return gst_uri_get_path (wvfile->uri); +} + +static void +gst_wv_file_set_uri (GstWvFileSrc * src, WvFile * wvfile, const gchar * uri) +{ + if (wvfile->uri) + gst_uri_unref (wvfile->uri); + + wvfile->uri = gst_uri_from_string (uri); + gst_uri_set_host (wvfile->uri, ""); +} + +static gchar * +gst_wv_file_get_uri (GstWvFileSrc * src, WvFile * wvfile) +{ + if (wvfile->uri == NULL) + return NULL; + + return gst_uri_to_string (wvfile->uri); +} + +static void +gst_wv_file_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWvFileSrc *src = (GstWvFileSrc *) object; + + switch (prop_id) { + case PROP_LOCATION: + gst_wv_file_set_location (src, &src->wv, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_wv_file_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstWvFileSrc *src = (GstWvFileSrc *) object; + + switch (prop_id) { + case PROP_LOCATION:{ + gchar *location = gst_wv_file_get_location (src, &src->wv); + g_value_take_string (value, location); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_wv_file_src_init (GstWvFileSrc * src) +{ + src->wvc.ignore_notlinked = FALSE; +} + +static void +create_and_post_collection (GstWvFileSrc * src) +{ + GstStreamCollection *collection; + GstStream *stream; + GstMessage *msg; + GstCaps *caps; + gchar *topstreamid, *streamid; + + /* FIXME : Do we need an upstream id ? We are the creator after all */ + collection = gst_stream_collection_new (NULL); + + /* There is only one top-level stream (with two variants) */ + topstreamid = g_strdup_printf ("%s/audio", src->unique_hash); + stream = gst_stream_new (topstreamid, NULL, GST_STREAM_TYPE_AUDIO, 0); + gst_stream_collection_add_stream (collection, stream); + + /* Base variant */ + gst_stream_collection_add_variant (collection, topstreamid, + gst_object_ref (src->wv.stream)); + + /* rich variant */ + caps = gst_caps_from_string ("audio/x-wavpack(meta:GstWVCorrection)"); + streamid = g_strdup_printf ("%s/enriched", src->unique_hash); + stream = gst_stream_new (streamid, caps, GST_STREAM_TYPE_AUDIO, 0); + g_free (streamid); + gst_caps_unref (caps); + gst_stream_add_component (stream, src->wv.stream); + gst_stream_add_component (stream, src->wvc.stream); + gst_stream_collection_add_variant (collection, topstreamid, stream); + g_free (topstreamid); + + msg = gst_message_new_stream_collection (GST_OBJECT (src), collection); + gst_element_post_message (GST_ELEMENT (src), msg); + + src->collection = collection; +} + +static GstPadProbeReturn +replace_stream_id_cb (GstPad * pad, GstPadProbeInfo * info, WvFile * wvfile) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + + if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START) { + GstPad *peer; + /* We are going to replace the event with a new one containing all + * the proper information */ + gst_event_unref (event); + event = gst_event_new_stream_start (wvfile->stream_id); + gst_event_set_stream (event, wvfile->stream); + gst_event_set_group_id (event, wvfile->group_id); + + peer = gst_pad_get_peer (pad); + if (peer) { + gst_pad_send_event (peer, event); + gst_object_unref (peer); + } else + gst_event_unref (event); + ret = GST_PAD_PROBE_HANDLED; + } + + return ret; +} + +static gboolean +gst_wv_file_add (GstWvFileSrc * src, WvFile * wvfile) +{ + GstStateChangeReturn sret; + const gchar *name; + GstPad *pad; + + wvfile->filesrc = gst_element_factory_make ("filesrc", NULL); + if (wvfile->uri) { + gchar *uri = gst_wv_file_get_uri (src, wvfile); + GError *err = NULL; + + GST_LOG_OBJECT (src, "Set URI %s on %" GST_PTR_FORMAT, uri + 2, + wvfile->filesrc); + + /* skip 'wv' prefix of 'wvfile://' */ + gst_uri_handler_set_uri (GST_URI_HANDLER (wvfile->filesrc), uri + 2, &err); + g_free (uri); + + if (err != NULL) { + GST_ERROR_OBJECT (src, "Could not set URI: %s", err->message); + g_clear_error (&err); + return FALSE; + } + } + wvfile->typefind = gst_element_factory_make ("typefind", NULL); + wvfile->queue = gst_element_factory_make ("queue", NULL); + if (wvfile->ignore_notlinked) { + wvfile->filter = gst_element_factory_make ("errorignore", NULL); + g_object_set (wvfile->filter, "ignore-error", FALSE, + "ignore-notlinked", TRUE, "ignore-notnegotiated", FALSE, + "convert-to", GST_FLOW_EOS, NULL); + } else { + wvfile->filter = gst_element_factory_make ("identity", NULL); + } + gst_bin_add_many (GST_BIN (src), wvfile->filesrc, wvfile->typefind, + wvfile->queue, wvfile->filter, NULL); + gst_element_link_many (wvfile->filesrc, wvfile->typefind, wvfile->queue, + wvfile->filter, NULL); + gst_element_set_state (wvfile->queue, GST_STATE_READY); + gst_element_set_state (wvfile->filter, GST_STATE_READY); + sret = gst_element_set_state (wvfile->typefind, GST_STATE_READY); + if (sret == GST_STATE_CHANGE_FAILURE) + return FALSE; + sret = gst_element_set_state (wvfile->filesrc, GST_STATE_READY); + if (sret == GST_STATE_CHANGE_FAILURE) + return FALSE; + + pad = gst_element_get_static_pad (wvfile->filter, "src"); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) replace_stream_id_cb, wvfile, NULL); + name = (wvfile == &src->wv) ? "src_0" : "src_1"; + wvfile->srcpad = gst_ghost_pad_new (name, pad); + gst_pad_set_active (wvfile->srcpad, TRUE); + gst_element_add_pad (GST_ELEMENT (src), wvfile->srcpad); + gst_object_unref (pad); + + return TRUE; +} + +static void +gst_wv_file_remove (GstWvFileSrc * src, WvFile * wvfile, gboolean clear_uri) +{ + if (wvfile->queue) { + gst_element_set_state (wvfile->queue, GST_STATE_NULL); + gst_bin_remove (GST_BIN (src), wvfile->queue); + wvfile->queue = NULL; + } + if (wvfile->typefind) { + gst_element_set_state (wvfile->typefind, GST_STATE_NULL); + gst_bin_remove (GST_BIN (src), wvfile->typefind); + wvfile->typefind = NULL; + } + if (wvfile->filesrc) { + gst_element_set_state (wvfile->filesrc, GST_STATE_NULL); + gst_bin_remove (GST_BIN (src), wvfile->filesrc); + wvfile->filesrc = NULL; + } + if (wvfile->srcpad) { + gst_pad_set_active (wvfile->srcpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (src), wvfile->srcpad); + wvfile->srcpad = NULL; + } + if (clear_uri && wvfile->uri != NULL) { + gst_uri_unref (wvfile->uri); + wvfile->uri = NULL; + } +} + +static gboolean +gst_wv_file_src_start (GstWvFileSrc * src) +{ + gchar *wv_fn, *wvc_fn; + GChecksum *cs; + GstCaps *caps; + GstEvent *event; + + if (!gst_wv_file_add (src, &src->wv)) + return FALSE; + + wv_fn = gst_wv_file_get_location (src, &src->wv); + g_assert (wv_fn != NULL); + + if (g_str_has_suffix (wv_fn, ".wv") || g_str_has_suffix (wv_fn, ".Wv")) { + wvc_fn = g_strconcat (wv_fn, "c", NULL); + } else if (g_str_has_suffix (wv_fn, ".WV")) { + wvc_fn = g_strconcat (wv_fn, "C", NULL); + } else { + GST_WARNING_OBJECT (src, "Not looking for correction file, no .wv file"); + goto done; + } + + cs = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (cs, (const guchar *) wv_fn, strlen (wv_fn) - 3); + src->unique_hash = g_strdup (g_checksum_get_string (cs)); + g_checksum_free (cs); + + g_free (wv_fn); + + if (!g_file_test (wvc_fn, G_FILE_TEST_EXISTS)) { + GST_WARNING_OBJECT (src, "No correction file '%s' found", wvc_fn); + g_free (wvc_fn); + return FALSE; + } + GST_INFO_OBJECT (src, "Correction file '%s' exists", wvc_fn); + + /* Create stream objects and id */ + caps = gst_caps_from_string ("audio/x-wavpack"); + src->wv.stream_id = g_strdup_printf ("%s/base", src->unique_hash); + src->wv.stream = + gst_stream_new (src->wv.stream_id, caps, GST_STREAM_TYPE_AUDIO, 0); + gst_caps_unref (caps); + caps = gst_caps_from_string ("audio/x-wavpack-correction"); + src->wvc.stream_id = g_strdup_printf ("%s/correction", src->unique_hash); + src->wvc.stream = + gst_stream_new (src->wvc.stream_id, caps, GST_STREAM_TYPE_AUDIO, 0); + gst_caps_unref (caps); + src->wv.group_id = src->wvc.group_id = gst_util_group_id_next (); + + /* FIXME : Create collection with combined stream and check whether + * downstream can support that stream before adding it */ + create_and_post_collection (src); + + gst_wv_file_set_location (src, &src->wvc, wvc_fn); + g_free (wvc_fn); + + if (!gst_wv_file_add (src, &src->wvc)) + return FALSE; + + /* Finally send the collection on all pads */ + event = gst_event_new_stream_collection (src->collection); + GST_DEBUG_OBJECT (src, "Sending collection %p", src->collection); + gst_pad_push_event (src->wv.srcpad, gst_event_ref (event)); + gst_pad_push_event (src->wvc.srcpad, event); + GST_DEBUG_OBJECT (src, "Done sending collection"); + + /* And the selected streams */ + event = gst_event_new_streams_selected (src->collection); + gst_event_streams_selected_add (event, src->wv.stream); + gst_event_streams_selected_add (event, src->wvc.stream); + GST_DEBUG_OBJECT (src, "Sending STREAMS_SELECTED"); + gst_pad_push_event (src->wv.srcpad, gst_event_ref (event)); + gst_pad_push_event (src->wvc.srcpad, event); + GST_DEBUG_OBJECT (src, "Done sending STREAMS_SELECTED"); + +done: + gst_element_no_more_pads (GST_ELEMENT (src)); + return TRUE; +} + +static void +gst_wv_file_src_stop (GstWvFileSrc * src) +{ + gst_wv_file_remove (src, &src->wv, FALSE); + gst_wv_file_remove (src, &src->wvc, TRUE); +} + +static GstStateChangeReturn +gst_wv_file_src_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstWvFileSrc *src = (GstWvFileSrc *) element; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_wv_file_src_start (src)) + return GST_STATE_CHANGE_FAILURE; + 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_READY_TO_NULL: + gst_wv_file_src_stop (src); + break; + default: + break; + } + + return ret; +} + +/*** GSTURIHANDLER INTERFACE *************************************************/ + +static GstURIType +gst_wv_file_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_wv_file_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "wvfile", NULL }; + + return protocols; +} + +static gchar * +gst_wv_file_src_uri_get_uri (GstURIHandler * handler) +{ + GstWvFileSrc *src = GST_WV_FILE_SRC (handler); + + return gst_wv_file_get_uri (src, &src->wv); +} + +static gboolean +gst_wv_file_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstWvFileSrc *src = GST_WV_FILE_SRC (handler); + + gst_wv_file_set_uri (src, &src->wv, uri); + + /* FIXME: no error handling, as this is for testing only */ + return TRUE; +} + +static void +gst_wv_file_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_wv_file_src_uri_get_type; + iface->get_protocols = gst_wv_file_src_uri_get_protocols; + iface->get_uri = gst_wv_file_src_uri_get_uri; + iface->set_uri = gst_wv_file_src_uri_set_uri; +} + +gboolean +gst_wv_file_src_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (wvfilesrc_debug, "wvfilesrc", 0, + "wvfilesrc element"); + + return gst_element_register (plugin, "wvfilesrc", GST_RANK_MARGINAL, + GST_TYPE_WV_FILE_SRC); +} diff --git a/ext/wavpack/gstwvfilesrc.h b/ext/wavpack/gstwvfilesrc.h new file mode 100644 index 0000000000..a7067445c8 --- /dev/null +++ b/ext/wavpack/gstwvfilesrc.h @@ -0,0 +1,77 @@ +/* GStreamer Wavpack File Source + * Copyright (C) 2018 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_WV_FILE_SRC_H__ +#define __GST_WV_FILE_SRC_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_WV_FILE_SRC \ + (gst_wv_file_src_get_type()) +#define GST_WV_FILE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WV_FILE_SRC,GstWvFileSrc)) +#define GST_WV_FILE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_WV_FILE_SRC,GstWvFileSrcClass)) +#define GST_IS_WV_FILE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WV_FILE_SRC)) +#define GST_IS_WV_FILE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_WV_FILE_SRC)) + +typedef struct _GstWvFileSrc GstWvFileSrc; +typedef struct _GstWvFileSrcClass GstWvFileSrcClass; + +typedef struct { + GstElement *filesrc; + GstElement *typefind; + GstElement *filter; /* errorignore or identity */ + GstElement *queue; + GstPad *srcpad; + GstUri *uri; + gboolean ignore_notlinked; + gchar *stream_id; + guint group_id; + GstStream *stream; +} WvFile; + +struct _GstWvFileSrc +{ + GstBin parent; + + /*< private > */ + WvFile wv; + WvFile wvc; + + GstStreamCollection *collection; + gchar *unique_hash; +}; + +struct _GstWvFileSrcClass +{ + GstBinClass parent_class; +}; + +GType gst_wv_file_src_get_type (void); + +gboolean gst_wv_file_src_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif /* __GST_WV_FILE_SRC_H__ */ diff --git a/ext/wavpack/meson.build b/ext/wavpack/meson.build index c4c9071595..d94c587b22 100644 --- a/ext/wavpack/meson.build +++ b/ext/wavpack/meson.build @@ -4,6 +4,9 @@ wavpack_sources = [ 'gstwavpackdec.c', 'gstwavpackenc.c', 'gstwavpackstreamreader.c', + 'gstwvccombiner.c', + 'gstwvcmeta.c', + 'gstwvfilesrc.c', ] wavpack_dep = dependency('wavpack', version : '>= 4.60.0', required : false) diff --git a/fix_sysroot.sh b/fix_sysroot.sh new file mode 100755 index 0000000000..c474846ced --- /dev/null +++ b/fix_sysroot.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Copyright (c) 2019 LG Electronics, Inc. + +arm_libtool=`find -name "arm-*gnueabi-libtool"` +echo "Found ${arm_libtool}" + +line=`grep -n "func_resolve_sysroot_result=\$\lt_sysroot\$\func_stripname_result" ./${arm_libtool} | cut -d':' -f1` + +my_ltsysroot="if [[ "\$func_stripname_result" =~ "\$lt_sysroot" ]]; then func_resolve_sysroot_result=\$func_stripname_result; else func_resolve_sysroot_result=\$lt_sysroot\$func_stripname_result; fi" + +if [ "${line}+99999" = "+99999" ]; then + echo "Nothing to do for ${arm_libtool}" +else + sed -i "${line}s/.*/${my_ltsysroot}/g" ./arm-starfishmllib32-linux-gnueabi-libtool + echo "Updated sysroot in ${arm_libtool}" +fi + +found=`grep -c ${SDKTARGETSYSROOT}${SDKTARGETSYSROOT} Makefile` +# found=`grep -c "'"${mysysroot}"''"${mysysroot}"'" Makefile` +if [ "${found}" = "0" ]; then + echo "No Found duplicated sysroot" +else + echo "Found duplicated sysroot" + mysysroot=`echo ${SDKTARGETSYSROOT} | sed -e 's/\//\\\\\//g'` + find -name "*Makefile" -exec sed -i 's/'"${mysysroot}"''"${mysysroot}"'/'"${mysysroot}"'/g' {} \; + echo "Updated sysroot in all Makefile(s)" +fi diff --git a/gst/audiofx/gstscaletempo.c b/gst/audiofx/gstscaletempo.c index 0bbbd56de9..61d4541bb5 100644 --- a/gst/audiofx/gstscaletempo.c +++ b/gst/audiofx/gstscaletempo.c @@ -436,19 +436,7 @@ reverse_buffer (GstScaletempo * st, GstBuffer * inbuf) outbuf = gst_buffer_new_and_alloc (imap.size); gst_buffer_map (outbuf, &omap, GST_MAP_WRITE); - if (st->format == GST_AUDIO_FORMAT_F64) { - const gint64 *ip = (const gint64 *) imap.data; - gint64 *op = (gint64 *) (omap.data + omap.size - 8 * st->samples_per_frame); - guint i, n = imap.size / (8 * st->samples_per_frame); - guint j, c = st->samples_per_frame; - - for (i = 0; i < n; i++) { - for (j = 0; j < c; j++) - op[j] = ip[j]; - op -= c; - ip += c; - } - } else { + { const gint32 *ip = (const gint32 *) imap.data; gint32 *op = (gint32 *) (omap.data + omap.size - 4 * st->samples_per_frame); guint i, n = imap.size / (4 * st->samples_per_frame); diff --git a/gst/audioparsers/Makefile.am b/gst/audioparsers/Makefile.am index 6727688c30..f39f186ea1 100644 --- a/gst/audioparsers/Makefile.am +++ b/gst/audioparsers/Makefile.am @@ -1,8 +1,8 @@ plugin_LTLIBRARIES = libgstaudioparsers.la libgstaudioparsers_la_SOURCES = \ - gstaacparse.c gstamrparse.c gstac3parse.c \ - gstdcaparse.c gstflacparse.c gstmpegaudioparse.c \ + gstaacparse.c gstamrparse.c gstac3parse.c gstac4parse.c \ + gstflacparse.c gstmpegaudioparse.c \ gstsbcparse.c gstwavpackparse.c plugin.c libgstaudioparsers_la_CFLAGS = \ @@ -14,6 +14,6 @@ libgstaudioparsers_la_LIBADD = \ $(GST_BASE_LIBS) $(GST_LIBS) libgstaudioparsers_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -noinst_HEADERS = gstaacparse.h gstamrparse.h gstac3parse.h \ - gstdcaparse.h gstflacparse.h gstmpegaudioparse.h gstsbcparse.h \ +noinst_HEADERS = gstaacparse.h gstamrparse.h gstac3parse.h gstac4parse.h \ + gstflacparse.h gstmpegaudioparse.h gstsbcparse.h \ gstwavpackparse.h diff --git a/gst/audioparsers/gstaacparse.c b/gst/audioparsers/gstaacparse.c index ad81a5468a..60737f636c 100644 --- a/gst/audioparsers/gstaacparse.c +++ b/gst/audioparsers/gstaacparse.c @@ -150,6 +150,7 @@ gst_aac_parse_class_init (GstAacParseClass * klass) static void gst_aac_parse_init (GstAacParse * aacparse) { + gst_base_parse_set_seek_tolerance (GST_BASE_PARSE (aacparse), 1 * GST_SECOND); GST_DEBUG ("initialized"); GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (aacparse)); GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (aacparse)); @@ -221,11 +222,14 @@ gst_aac_parse_set_src_caps (GstAacParse * aacparse, GstCaps * sink_caps) s = gst_caps_get_structure (src_caps, 0); if (aacparse->sample_rate > 0) gst_structure_set (s, "rate", G_TYPE_INT, aacparse->sample_rate, NULL); - if (aacparse->channels > 0) + if (aacparse->channels >= 0) gst_structure_set (s, "channels", G_TYPE_INT, aacparse->channels, NULL); if (stream_format) gst_structure_set (s, "stream-format", G_TYPE_STRING, stream_format, NULL); + /* To Support the AAC-ELD */ + gst_structure_set (s, "object-type", G_TYPE_INT, aacparse->object_type, NULL); + allowed = gst_pad_get_allowed_caps (GST_BASE_PARSE (aacparse)->srcpad); if (allowed && !gst_caps_can_intersect (src_caps, allowed)) { GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, @@ -265,9 +269,6 @@ gst_aac_parse_set_src_caps (GstAacParse * aacparse, GstCaps * sink_caps) if (allowed) gst_caps_unref (allowed); - aacparse->last_parsed_channels = 0; - aacparse->last_parsed_sample_rate = 0; - GST_DEBUG_OBJECT (aacparse, "setting src caps: %" GST_PTR_FORMAT, src_caps); res = gst_pad_set_caps (GST_BASE_PARSE (aacparse)->srcpad, src_caps); @@ -448,10 +449,9 @@ gst_aac_parse_check_adts_frame (GstAacParse * aacparse, /* CRC size test */ if (*framesize < 7 + crc_size) { - *needed_data = 7 + crc_size; return FALSE; } - +#if 0 /* In EOS mode this is enough. No need to examine the data further. We also relax the check when we have sync, on the assumption that if we're not looking at random data, we have a much higher chance @@ -460,6 +460,7 @@ gst_aac_parse_check_adts_frame (GstAacParse * aacparse, if (drain || !GST_BASE_PARSE_LOST_SYNC (aacparse)) { return TRUE; } +#endif if (*framesize + ADTS_MAX_SIZE > avail) { /* We have found a possible frame header candidate, but can't be @@ -479,7 +480,11 @@ gst_aac_parse_check_adts_frame (GstAacParse * aacparse, gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), nextlen + ADTS_MAX_SIZE); return TRUE; + } else { + GST_DEBUG_OBJECT (aacparse, "That was a false positive"); + gst_base_parse_set_skipped_frame (GST_BASE_PARSE (aacparse)); } + } return FALSE; } @@ -577,11 +582,16 @@ gst_aac_parse_read_audio_specific_config (GstAacParse * aacparse, *channels = 2; } + /* extensionSamplingFrequency belongs to output sampling frequency + But the frame duration will be computed based on the input sampling + frequency, hence commented the reading of extensionSamplingFrequency here */ +#if 0 GST_LOG_OBJECT (aacparse, "Audio object type 5 or 29, so rereading sampling rate (was %d)...", *sample_rate); if (!gst_aac_parse_get_audio_sample_rate (aacparse, br, sample_rate)) return FALSE; +#endif if (!gst_aac_parse_get_audio_object_type (aacparse, br, &audio_object_type)) return FALSE; @@ -792,6 +802,7 @@ gst_aac_parse_check_loas_frame (GstAacParse * aacparse, GST_DEBUG_OBJECT (aacparse, "Found possible %u byte LOAS frame", *framesize); +#if 0 /* In EOS mode this is enough. No need to examine the data further. We also relax the check when we have sync, on the assumption that if we're not looking at random data, we have a much higher chance @@ -800,6 +811,7 @@ gst_aac_parse_check_loas_frame (GstAacParse * aacparse, if (drain || !GST_BASE_PARSE_LOST_SYNC (aacparse)) { return TRUE; } +#endif if (*framesize + LOAS_MAX_SIZE > avail) { /* We have found a possible frame header candidate, but can't be @@ -821,6 +833,7 @@ gst_aac_parse_check_loas_frame (GstAacParse * aacparse, return TRUE; } else { GST_DEBUG_OBJECT (aacparse, "That was a false positive"); + gst_base_parse_set_skipped_frame (GST_BASE_PARSE (aacparse)); } } return FALSE; @@ -908,6 +921,8 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse, if (!found) { if (i) *skipsize = i; + GST_DEBUG_OBJECT (aacparse, "There is any syncword in this frame"); + gst_base_parse_set_skipped_frame (GST_BASE_PARSE (aacparse)); return FALSE; } @@ -920,7 +935,7 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse, gst_aac_parse_parse_adts_header (aacparse, data, &rate, &channels, &aacparse->object_type, &aacparse->mpegversion); - if (!channels || !framesize) { + if (!framesize) { GST_DEBUG_OBJECT (aacparse, "impossible ADTS configuration"); return FALSE; } @@ -1352,7 +1367,10 @@ gst_aac_parse_handle_frame (GstBaseParse * parse, if (G_UNLIKELY (rate != aacparse->sample_rate || channels != aacparse->channels)) { - aacparse->sample_rate = rate; + /* If the chip support up to 96000, + * it is better to change. WOSLQEVENT-11292 */ + if (rate <= 48000 && rate >= 8000) + aacparse->sample_rate = rate; aacparse->channels = channels; if (!gst_aac_parse_set_src_caps (aacparse, NULL)) { diff --git a/gst/audioparsers/gstac3parse.c b/gst/audioparsers/gstac3parse.c index afc8770bcc..1ebefe330f 100644 --- a/gst/audioparsers/gstac3parse.c +++ b/gst/audioparsers/gstac3parse.c @@ -212,6 +212,7 @@ gst_ac3_parse_reset (GstAc3Parse * ac3parse) ac3parse->eac = FALSE; ac3parse->sent_codec_tag = FALSE; g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_NONE); + ac3parse->is_atmos = FALSE; } static void @@ -285,6 +286,9 @@ gst_ac3_parse_set_alignment (GstAc3Parse * ac3parse, gboolean eac) GST_WARNING_OBJECT (ac3parse, "unknown alignment: %s", str); } break; + } else { + g_atomic_int_set (&ac3parse->align, GST_AC3_PARSE_ALIGN_IEC61937); + GST_DEBUG_OBJECT (ac3parse, "set as iec61937 alignment of EAC-3"); } } @@ -469,16 +473,12 @@ gst_ac3_parse_frame_header (GstAc3Parse * parse, GstBuffer * buf, gint skip, ret = gst_ac3_parse_frame_header_ac3 (parse, buf, skip, framesize, rate, chans, blocks, sid); goto cleanup; - } else if (bsid <= 16) { + } else { if (eac) *eac = TRUE; ret = gst_ac3_parse_frame_header_eac3 (parse, buf, skip, framesize, rate, chans, blocks, sid); goto cleanup; - } else { - GST_DEBUG_OBJECT (parse, "unexpected bsid %d", bsid); - ret = FALSE; - goto cleanup; } GST_DEBUG_OBJECT (parse, "unexpected bsid %d", bsid); @@ -621,7 +621,7 @@ gst_ac3_parse_handle_frame (GstBaseParse * parse, ret = TRUE; /* arrange for metadata setup */ - if (G_UNLIKELY (sid)) { + if (G_UNLIKELY (sid) && !more) { /* dependent frame, no need to (ac)count for or consider further */ GST_LOG_OBJECT (parse, "sid: %d", sid); frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NO_FRAME; @@ -643,6 +643,8 @@ gst_ac3_parse_handle_frame (GstBaseParse * parse, gst_caps_set_simple (caps, "alignment", G_TYPE_STRING, g_atomic_int_get (&ac3parse->align) == GST_AC3_PARSE_ALIGN_IEC61937 ? "iec61937" : "frame", NULL); + if (ac3parse->is_atmos) + gst_caps_set_simple (caps, "immersive", G_TYPE_STRING, "ATMOS", NULL); gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); gst_caps_unref (caps); @@ -949,5 +951,9 @@ gst_ac3_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps) } else { gst_pad_set_chain_function (parse->sinkpad, ac3parse->baseparse_chainfunc); } + /* Store ATMOS information from upstream element */ + if (gst_structure_get_string (s, "immersive")) { + ac3parse->is_atmos = TRUE; + } return TRUE; } diff --git a/gst/audioparsers/gstac3parse.h b/gst/audioparsers/gstac3parse.h index 0e7af5acd7..fd4aaf16b2 100644 --- a/gst/audioparsers/gstac3parse.h +++ b/gst/audioparsers/gstac3parse.h @@ -63,6 +63,7 @@ struct _GstAc3Parse { gboolean eac; gboolean sent_codec_tag; volatile gint align; + gboolean is_atmos; GstPadChainFunction baseparse_chainfunc; }; diff --git a/gst/audioparsers/gstac4parse.c b/gst/audioparsers/gstac4parse.c new file mode 100644 index 0000000000..18983abb4a --- /dev/null +++ b/gst/audioparsers/gstac4parse.c @@ -0,0 +1,620 @@ +/* GStreamer AC4 parser + * Copyright (C) 2016 LG Electronics, Inc. + * Author: Dinesh Anand K + * Kumar Vijay Vikram + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* + * spec: ETSI TS 103 190-2 Annex C.2 + * AC-4 Sync Frame structure (i.e. Raw Frame encapsulation), + * + * ------------------------- ------------------------------- + * |Sync word| Frame | Raw | |Sync word| Frame | Raw | CRC | + * |(0xAC40) | Size | Frame | (or) |(0xAC41) | Size | Frame | | + * ------------------------- ------------------------------- + * + * spec : ETSI TS 103 190 - 4.2.1 + * AC-4 Raw Frame structure + * + * ---------------------------------------------------- + * | TOC | substream 0 | substream1 | ... | substream N | + * ---------------------------------------------------- + */ + +/** + * SECTION:element-ac4parse + * @short_description: AC4 parser + * @see_also: #GstAmrParse, #GstAACParse + * + * This is an AC4 parser. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=abc.ac4 ! ac4parse ! a52dec ! audioresample ! audioconvert ! autoaudiosink + * ]| + * + */ + +/* TODO: + * - audio/ac4 to audio/x-private1-ac4 is not implemented (done in the muxer) + * - should accept framed and unframed input (needs decodebin fixes first) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstac4parse.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (ac4_parse_debug); +#define GST_CAT_DEFAULT ac4_parse_debug + +#define AC4_SYNC_WORD1 0xAC40 /*sync frame with CRC disabled */ +#define AC4_SYNC_WORD2 0xAC41 /*sync frame with CRC enabled */ + +/*spec: 4.3.3.2.2*/ +#define AC4_SEQ_CNTR_WRAP_VAL 1020 /*seq. counter wrap value */ + +#define AC4_BS_VER_SUPPORTED 2 /*bitstream version till which supported */ + +/* Not specified in spec. based on experiment*/ +#define AC4_MIN_FRAME_SIZE 12 /* min frame size req. for parsing */ + +/* spec:4.3.3.2.6*/ +/* gets the frame rate based on fs base & fps */ +#define AC4_GET_FPS(fs, fps_index, fr_num, fr_den) \ + *fr_num = -1;\ + *fr_den = -1;\ + if(fs == 48000 && fps_index < 14) {\ + *fr_num = fps_table_48K[fps_index][0];\ + *fr_den = fps_table_48K[fps_index][1];\ + }else if(fs == 44100 && fps_index == 13) {\ + *fr_num = 11025;\ + *fr_den = 512;\ + }\ + +/* spec: 4.3.3.2.5 */ +static const guint fs_base[2] = { 44100, 48000 }; + +/* spec: 4.3.3.2.6 */ +/* frame rate(numerator, denominator) table for base fs, 48KHz */ +static const guint fps_table_48K[16][2] = { + {0x44AA2000, 0x2DD2780}, {0x00119400, 0x0000BB80}, {0x000BB800, 0x00007800}, + {0x44AA2000, 0x24A8600}, {0x00119400, 0x00009600}, {0x44AA2000, 0x016E93C0}, + {0x00119400, 0x0005DC0}, {0x000BB800, 0x00003C00}, {0x44AA2000, 0x01254300}, + {0x00119400, 0x0004B00}, {0x000BB800, 0x00001E00}, {0x44AA2000, 0x0092A180}, + {0x00119400, 0x0002580}, {0x0000BB80, 0x00000800} + /*frame_rate_index(14 & 15) are reserved */ +}; + + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-ac4, framed = (boolean) true, " + " channels = (int) [ 1, 12 ], rate = (int) [ 8000, 48000 ], " + " frame-format = (string) {SYNC, RAW}, " + " alignment = (string) { frame}; ")); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-ac4; " "audio/ac4 ")); + +static void gst_ac4_parse_finalize (GObject * object); + +static gboolean gst_ac4_parse_start (GstBaseParse * parse); +static gboolean gst_ac4_parse_stop (GstBaseParse * parse); +static GstFlowReturn gst_ac4_parse_handle_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, gint * skipsize); +static GstFlowReturn gst_ac4_parse_pre_push_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); +static GstCaps *gst_ac4_parse_get_sink_caps (GstBaseParse * parse, + GstCaps * filter); +static gboolean gst_ac4_parse_set_sink_caps (GstBaseParse * parse, + GstCaps * caps); + +#define gst_ac4_parse_parent_class parent_class +G_DEFINE_TYPE (GstAc4Parse, gst_ac4_parse, GST_TYPE_BASE_PARSE); + +static void +gst_ac4_parse_class_init (GstAc4ParseClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (ac4_parse_debug, "ac4parse", 0, + "AC4 audio stream parser"); + + object_class->finalize = gst_ac4_parse_finalize; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_static_metadata (element_class, + "AC4 audio stream parser", "Codec/Parser/Converter/Audio", + "AC4 parser", "Dinesh Anand K "); + + parse_class->start = GST_DEBUG_FUNCPTR (gst_ac4_parse_start); + parse_class->stop = GST_DEBUG_FUNCPTR (gst_ac4_parse_stop); + parse_class->handle_frame = GST_DEBUG_FUNCPTR (gst_ac4_parse_handle_frame); + parse_class->pre_push_frame = + GST_DEBUG_FUNCPTR (gst_ac4_parse_pre_push_frame); + parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_ac4_parse_set_sink_caps); + parse_class->get_sink_caps = GST_DEBUG_FUNCPTR (gst_ac4_parse_get_sink_caps); +} + +static void +gst_ac4_parse_reset (GstAc4Parse * ac4parse) +{ + /* Initialize the state variables */ + ac4parse->n_presentations = 0; + ac4parse->bitstream_version = 0; + + ac4parse->sink_cap_ch = 1; + ac4parse->channels = -1; + ac4parse->sample_rate = -1; + ac4parse->fps_num = -1; + ac4parse->fps_den = -1; + ac4parse->bsversion = 0; + ac4parse->is_framed = FALSE; + ac4parse->sent_codec_tag = FALSE; +} + +static void +gst_ac4_parse_init (GstAc4Parse * ac4parse) +{ + /*TODO: Minimum frame size has to be decided */ + gst_base_parse_set_min_frame_size (GST_BASE_PARSE (ac4parse), + AC4_MIN_FRAME_SIZE); + gst_ac4_parse_reset (ac4parse); + ac4parse->baseparse_chainfunc = + GST_BASE_PARSE_SINK_PAD (GST_BASE_PARSE (ac4parse))->chainfunc; + GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (ac4parse)); + GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (ac4parse)); +} + +static void +gst_ac4_parse_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_ac4_parse_start (GstBaseParse * parse) +{ + GstAc4Parse *ac4parse = GST_AC4_PARSE (parse); + + GST_DEBUG_OBJECT (parse, "starting"); + + gst_ac4_parse_reset (ac4parse); + + return TRUE; +} + +static gboolean +gst_ac4_parse_stop (GstBaseParse * parse) +{ + GST_DEBUG_OBJECT (parse, "stopping"); + + return TRUE; +} + +/** + * variable_bits_read: + * @bit_reader: #GstBitReader + * @n_bits: minimum element length of variable lenght fields + * + * spec: 4.3.2 + * + * TODO: Check whether the buffer of 32 bit is sufficient + * to decode the variable lenght field value. + * + * Returns: variable lenght field value + */ +static guint +variable_bits_read (GstBitReader * bit_reader, guint16 n_bits) +{ + gboolean read_more_flag = FALSE; + guint value = 0; + guint buffer1 = 0; + guint buffer1_mask; + + /* Read the first group(i.e. n_bits+1 bits)into the variable buffer1 */ + gst_bit_reader_get_bits_uint32 (bit_reader, &buffer1, (n_bits + 1)); + /* Compute the mask value for buffer1 */ + buffer1_mask = ((guint) 1 << n_bits) - 1; + + do { + /* get the value from buffer1 */ + value |= (buffer1 & buffer1_mask); + /*skip the read bits in buffer1 */ + buffer1 >>= n_bits; + /* read the read_more_flag */ + read_more_flag = (buffer1 & 0x1); + /* compute the value of variable length field */ + value <<= (n_bits * read_more_flag); + value += (read_more_flag << n_bits); + /* Read the next group in case of read_more_flag set */ + gst_bit_reader_get_bits_uint32 (bit_reader, &buffer1, + read_more_flag * (n_bits + 1)); + } while (read_more_flag); + + return value; +} + + +static gboolean +gst_ac4_parse_frame_header (GstAc4Parse * ac4parse, GstBuffer * buf, + gint skip, guint * frame_size, guint * sampling_rate, guint * chans, + gint * fps_num, gint * fps_den, guint * bsversion) +{ + GstBitReader bit_reader; + GstMapInfo map; + gboolean fs_index, crc_enable, ret = FALSE; + guint8 frame_rate_index; + guint16 uvalue16 = 0; + guint uvalue32 = 0, base_sample_freq; + + /*TODO: Avoid using the gst_bit_reader functions */ + /* Initialize the bit reader */ + gst_buffer_map (buf, &map, GST_MAP_READ); + gst_bit_reader_init (&bit_reader, map.data, map.size); + + /* Peek the sync word */ + gst_bit_reader_peek_bits_uint16 (&bit_reader, &uvalue16, 16); + + if (uvalue16 == AC4_SYNC_WORD1 || uvalue16 == AC4_SYNC_WORD2) { + /* Read the sync word */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 16); + + GST_LOG_OBJECT (ac4parse, "AC4 Sync frame parsing ..."); + /* Read the CRC enable flag */ + crc_enable = (uvalue16 == AC4_SYNC_WORD2); + /* Annex C.3: Read the frame size */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 16); + /* framesize + sync word (2b) + framesize field (2b) + crc_word(2b) */ + *frame_size = uvalue16 + 2 + 2 + (crc_enable * 2); + if (uvalue16 == 0xFFFF) { + gst_bit_reader_get_bits_uint32 (&bit_reader, &uvalue32, 24); + /* framesize + sync word (2b) + framesize field (5b) + crc_word(2b) */ + *frame_size = uvalue32 + 2 + 5 + (crc_enable * 2); + } + GST_LOG_OBJECT (ac4parse, "AC4 Sync frame size:%d", *frame_size); + } + GST_LOG_OBJECT (ac4parse, "AC4 Raw frame, TOC parsing ..."); + /* spec: 4.2.3.1 */ + /* bitstream version */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 2); + uvalue16 += (uvalue16 == 0x3 ? variable_bits_read (&bit_reader, 2) : 0); + ac4parse->bitstream_version = uvalue16; + if (uvalue16 > AC4_BS_VER_SUPPORTED) { + GST_LOG_OBJECT (ac4parse, "Invalid bitstream Ver:%d", uvalue16); + goto cleanup; + } + GST_LOG_OBJECT (ac4parse, "TOC, bitstream Ver:%d", uvalue16); + *bsversion = uvalue16; + /* sequence counter */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 10); + if (uvalue16 > AC4_SEQ_CNTR_WRAP_VAL) { + GST_LOG_OBJECT (ac4parse, "Sequence counter:%d exceeds Max", uvalue16); + goto cleanup; + } + GST_LOG_OBJECT (ac4parse, "TOC, Sequence counter:%d", uvalue16); + /* wait frames information parsing */ + /*b_wait_frames */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 1); + + if (uvalue16) { + /*wait_frames */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 3); + /* br_code */ + if (uvalue16) + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 2); + } + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 7); + /* fs_index */ + fs_index = (uvalue16 & (0x1 << 6)) >> 6; + base_sample_freq = fs_base[fs_index]; + GST_LOG_OBJECT (ac4parse, "TOC, base sampling freq:%d", base_sample_freq); + *sampling_rate = base_sample_freq; + /*frame_rate_index */ + frame_rate_index = (uvalue16 & (0xF << 2)) >> 2; + if ((frame_rate_index > 13 && fs_index) || + (frame_rate_index != 13 && !fs_index)) { + GST_LOG_OBJECT (ac4parse, "Invalid framerate index:%d", frame_rate_index); + goto cleanup; + } + AC4_GET_FPS (base_sample_freq, frame_rate_index, fps_num, fps_den); + GST_LOG_OBJECT (ac4parse, "TOC, fps_num:%d fps_den:%d", *fps_num, *fps_den); + + /* presentation information parsing */ + /*b_single_presentation */ + ac4parse->n_presentations = 1; + if (!(uvalue16 & 0x1)) { + /*b_more_presentations */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 1); + /*n_presentations */ + ac4parse->n_presentations = + (uvalue16 == 1) ? variable_bits_read (&bit_reader, 2) + 2 : 0; + } + + /* payload base offset information parsing */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 1); + if (uvalue16) { + /*payload_base_minus1 */ + gst_bit_reader_get_bits_uint16 (&bit_reader, &uvalue16, 5); + uvalue16 = uvalue16 + 1; + /*b_payload_base */ + uvalue16 += (uvalue16 == 0x20) ? variable_bits_read (&bit_reader, 3) : 0; + } + ret = TRUE; + + +cleanup: + GST_LOG_OBJECT (ac4parse, "Bytes consumed for AC4 header parsing:%u", + (gst_bit_reader_get_pos (&bit_reader) >> 3)); + gst_buffer_unmap (buf, &map); + + return ret; +} + +static GstFlowReturn +gst_ac4_parse_handle_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, gint * skipsize) +{ + GstAc4Parse *ac4parse = GST_AC4_PARSE (parse); + GstBuffer *buf = frame->buffer; + GstByteReader reader; + GstMapInfo map; + GstFlowReturn res = GST_FLOW_OK; + gboolean lost_sync, draining; + gboolean ret = FALSE, update_rate = TRUE, is_syncframe = FALSE; + guint marker, framesize = 0, bsversion = 0; + guint sampling_rate = 0, num_chans = 0; + gint fps_num = -1, fps_den = -1; + + gst_buffer_map (buf, &map, GST_MAP_READ); + /*TODO: Minimum size required for parsing necessary info */ + if (G_UNLIKELY (map.size < AC4_MIN_FRAME_SIZE)) { + *skipsize = 1; + goto cleanup; + } + /* Initialize the frame size to buffer size */ + framesize = map.size; + + gst_byte_reader_init (&reader, map.data, map.size); + + marker = gst_byte_reader_peek_uint16_be_unchecked (&reader); + + /* check for valid AC4 sync word */ + if (marker == AC4_SYNC_WORD1 || marker == AC4_SYNC_WORD2) { + is_syncframe = TRUE; + GST_LOG_OBJECT (ac4parse, "AC4 sync marker 0x%02x at offset %u", marker, + gst_byte_reader_get_pos (&reader)); + } else if (!ac4parse->is_framed) { + *skipsize = 1; + goto cleanup; + } + /* Initialize the num channels to sink cap channel value */ + num_chans = ac4parse->sink_cap_ch; + /* parse AC4 frame header information and check for valid values */ + if (!gst_ac4_parse_frame_header (ac4parse, buf, 0, &framesize, + &sampling_rate, &num_chans, &fps_num, &fps_den, &bsversion)) { + *skipsize = 1; + goto cleanup; + } + GST_LOG_OBJECT (parse, "AC4 frame parsing successful.."); + GST_LOG_OBJECT (parse, "Framesize: %u", framesize); + GST_LOG_OBJECT (parse, "Bitstream version: %u", bsversion); + GST_LOG_OBJECT (parse, "Sampling_rate:%u", sampling_rate); + GST_LOG_OBJECT (parse, "Number of presentations:%u", + ac4parse->n_presentations); + GST_LOG_OBJECT (parse, "Number of channels:%u", num_chans); + GST_LOG_OBJECT (parse, "Frame rate, fps_num:%d fps_den:%d)", fps_num, + fps_den); + + GST_LOG_OBJECT (parse, "got frame"); + + lost_sync = GST_BASE_PARSE_LOST_SYNC (parse); + draining = GST_BASE_PARSE_DRAINING (parse); + + if (lost_sync && !draining && is_syncframe) { + guint16 word = 0; + + GST_DEBUG_OBJECT (ac4parse, "Resyncing: checking for next frame syncword"); + + if (!gst_byte_reader_skip (&reader, framesize) || + !gst_byte_reader_get_uint16_be (&reader, &word)) { + GST_DEBUG_OBJECT (ac4parse, "... but not sufficient data"); + /*TODO: Minimum size required for parsing necessary info */ + gst_base_parse_set_min_frame_size (parse, framesize + AC4_MIN_FRAME_SIZE); + *skipsize = 0; + goto cleanup; + } else { + if (word != AC4_SYNC_WORD1 && word != AC4_SYNC_WORD2) { + GST_DEBUG_OBJECT (ac4parse, + "Invalid sync word:0x%x found at frame end...", word); + /* skip the current frame */ + *skipsize = 1; + goto cleanup; + } else { + /* got sync now, let's assume constant frame size */ + gst_base_parse_set_min_frame_size (parse, framesize); + } + } + } + + /* expect to have found a frame here */ + g_assert (framesize); + ret = TRUE; + + /* For same sampling rate, frame rate will vary based on framerate index */ + if (G_UNLIKELY (ac4parse->fps_num != fps_num || ac4parse->fps_den != fps_den)) { + update_rate = TRUE; + /* update the previous frame information */ + ac4parse->fps_num = fps_num; + ac4parse->fps_den = fps_den; + } + + if (G_UNLIKELY (ac4parse->sample_rate != sampling_rate || + ac4parse->channels != num_chans || + ac4parse->bsversion != bsversion)) { + GstCaps *caps = gst_caps_new_simple ("audio/x-ac4", + "framed", G_TYPE_BOOLEAN, TRUE, + "rate", G_TYPE_INT, sampling_rate, + "channels", G_TYPE_INT, num_chans, NULL); + gst_caps_set_simple (caps, "framed", G_TYPE_BOOLEAN, TRUE, + "bsversion", G_TYPE_INT, ac4parse->bsversion, + "frame-format", G_TYPE_STRING, (is_syncframe ? "SYNC" : "RAW"), NULL); + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); + gst_caps_unref (caps); + + /* update the previous frame information */ + ac4parse->sample_rate = sampling_rate; + ac4parse->channels = num_chans; + ac4parse->bsversion = bsversion; + + update_rate = TRUE; + } + + if (G_UNLIKELY (update_rate)) + gst_base_parse_set_frame_rate (parse, fps_num, fps_den, 2, 2); + +cleanup: + gst_buffer_unmap (buf, &map); + + if (ret && framesize <= map.size) { + res = gst_base_parse_finish_frame (parse, frame, framesize); + } + + return res; +} + + +static GstFlowReturn +gst_ac4_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) +{ + GstAc4Parse *ac4parse = GST_AC4_PARSE (parse); + + if (!ac4parse->sent_codec_tag) { + GstTagList *taglist; + GstCaps *caps; + + /* codec tag */ + caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse)); + if (G_UNLIKELY (caps == NULL)) { + if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) { + GST_INFO_OBJECT (parse, "Src pad is flushing"); + return GST_FLOW_FLUSHING; + } else { + GST_INFO_OBJECT (parse, "Src pad is not negotiated!"); + return GST_FLOW_NOT_NEGOTIATED; + } + } + + taglist = gst_tag_list_new_empty (); + gst_pb_utils_add_codec_description_to_tag_list (taglist, + GST_TAG_AUDIO_CODEC, caps); + gst_caps_unref (caps); + + gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE); + gst_tag_list_unref (taglist); + + /* also signals the end of first-frame processing */ + ac4parse->sent_codec_tag = TRUE; + } + + return GST_FLOW_OK; +} + +static GstCaps * +gst_ac4_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter) +{ + GstCaps *peercaps, *templ; + GstCaps *res; + + templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse)); + if (filter) { + GstCaps *fcopy = gst_caps_copy (filter); + /* TODO: Remove the fields we convert */ + + peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy); + gst_caps_unref (fcopy); + } else + peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL); + + if (peercaps) { + + peercaps = gst_caps_make_writable (peercaps); + /* TODO: Remove the fields we convert */ + + res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (peercaps); + gst_caps_unref (templ); + } else { + res = templ; + } + + if (filter) { + GstCaps *intersection; + + intersection = + gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (res); + res = intersection; + } + + return res; +} + +static gboolean +gst_ac4_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps) +{ + GstAc4Parse *ac4parse = GST_AC4_PARSE (parse); + const GValue *value; + guint i, n; + + n = gst_caps_get_size (caps); + + for (i = 0; i < n; i++) { + GstStructure *s = gst_caps_get_structure (caps, i); + + if (gst_structure_has_field (s, "framed")) { + value = gst_structure_get_value (s, "framed"); + ac4parse->is_framed = g_value_get_boolean (value); + } + if (gst_structure_has_field (s, "channels")) { + value = gst_structure_get_value (s, "channels"); + ac4parse->sink_cap_ch = g_value_get_int (value); + } + } + + gst_pad_set_chain_function (parse->sinkpad, ac4parse->baseparse_chainfunc); + + return TRUE; +} diff --git a/gst/audioparsers/gstac4parse.h b/gst/audioparsers/gstac4parse.h new file mode 100644 index 0000000000..ecde349e2e --- /dev/null +++ b/gst/audioparsers/gstac4parse.h @@ -0,0 +1,86 @@ +/* GStreamer AC4 parser + * Copyright (C) 2016 LG Electronics, Inc. + * Author: Dinesh Anand K + * Kumar Vijay Vikram + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_AC4_PARSE_H__ +#define __GST_AC4_PARSE_H__ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_AC4_PARSE \ + (gst_ac4_parse_get_type()) +#define GST_AC4_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AC4_PARSE, GstAc4Parse)) +#define GST_AC4_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AC4_PARSE, GstAc4ParseClass)) +#define GST_IS_AC4_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AC4_PARSE)) +#define GST_IS_AC4_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AC4_PARSE)) +typedef struct _GstAc4Parse GstAc4Parse; +typedef struct _GstAc4ParseClass GstAc4ParseClass; + +enum +{ + GST_AC4_PARSE_ALIGN_NONE, + GST_AC4_PARSE_ALIGN_FRAME, +}; + +/** + * GstAc4Parse: + * + * The opaque GstAc4Parse object + */ +struct _GstAc4Parse +{ + GstBaseParse baseparse; + /* AC-4 codec parser variables */ + guint16 n_presentations; + guint bitstream_version; + /* AC-4 parser element variables */ + gboolean sent_codec_tag; + gboolean is_framed; + gint sink_cap_ch; + /* previous frame state variables */ + guint bsversion; + gint sample_rate; + gint channels; + guint fps_num; + guint fps_den; + GstPadChainFunction baseparse_chainfunc; +}; + +/** + * GstAc4ParseClass: + * @parent_class: Element parent class. + * + * The opaque GstAc4ParseClass data structure. + */ +struct _GstAc4ParseClass +{ + GstBaseParseClass baseparse_class; +}; + +GType gst_ac4_parse_get_type (void); + +G_END_DECLS +#endif /* __GST_AC4_PARSE_H__ */ diff --git a/gst/audioparsers/gstflacparse.c b/gst/audioparsers/gstflacparse.c index 90176af9ad..087aae97b1 100644 --- a/gst/audioparsers/gstflacparse.c +++ b/gst/audioparsers/gstflacparse.c @@ -1336,6 +1336,17 @@ gst_flac_parse_handle_headers (GstFlacParse * flacparse) "streamheader", &array); g_value_unset (&array); + // For missing vorbiscomment, we don't push a unnecessary header. + // This is related in the specific flac file: MFTEVENTFT-26222. + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)), caps); + gst_caps_unref (caps); + + g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (flacparse->headers); + flacparse->headers = NULL; + + return res; + push_headers: gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)), caps); @@ -1510,6 +1521,7 @@ gst_flac_parse_handle_block_type (GstFlacParse * flacparse, guint type, case 4: /* VORBIS_COMMENT */ GST_INFO_OBJECT (flacparse, "VORBISCOMMENT header"); ret = gst_flac_parse_handle_vorbiscomment (flacparse, sbuffer); + ret = FALSE; break; case 5: /* CUESHEET */ GST_INFO_OBJECT (flacparse, "CUESHEET header"); @@ -1518,9 +1530,11 @@ gst_flac_parse_handle_block_type (GstFlacParse * flacparse, guint type, case 6: /* PICTURE */ GST_INFO_OBJECT (flacparse, "PICTURE header"); ret = gst_flac_parse_handle_picture (flacparse, sbuffer); + ret = FALSE; break; case 1: /* PADDING */ GST_INFO_OBJECT (flacparse, "PADDING header"); + ret = FALSE; break; case 2: /* APPLICATION */ GST_INFO_OBJECT (flacparse, "APPLICATION header"); diff --git a/gst/audioparsers/gstmpegaudioparse.c b/gst/audioparsers/gstmpegaudioparse.c index cfad8833a4..716923c183 100644 --- a/gst/audioparsers/gstmpegaudioparse.c +++ b/gst/audioparsers/gstmpegaudioparse.c @@ -220,6 +220,7 @@ gst_mpeg_audio_parse_reset (GstMpegAudioParse * mp3parse) mp3parse->encoder_delay = 0; mp3parse->encoder_padding = 0; + mp3parse->mpegvalid = FALSE; } static void @@ -486,6 +487,7 @@ gst_mpeg_audio_parse_head_check (GstMpegAudioParse * mp3parse, /* Ignore this as there are some files with emphasis 0x2 that can * be played fine. See BGO #537235 */ GST_WARNING_OBJECT (mp3parse, "invalid emphasis: 0x%lx", head & 0x3); + mp3parse->mpegvalid = TRUE; } return TRUE; @@ -600,6 +602,48 @@ gst_mp3parse_find_freerate (GstMpegAudioParse * mp3parse, GstMapInfo * map, return TRUE; } +static gboolean +gst_mpeg_audio_parse_set_src_caps (GstBaseParse * parse, GstCaps * sink_caps) +{ + GstMpegAudioParse *mp3parse = GST_MPEG_AUDIO_PARSE (parse); + GstStructure *s; + gboolean res = FALSE; + gboolean update_caps = FALSE; + gint layer = 0, rate = 0, channels = 0, version = 0; + + GST_DEBUG_OBJECT (mp3parse, "sink caps: %" GST_PTR_FORMAT, sink_caps); + if (sink_caps) { + update_caps = TRUE; + s = gst_caps_get_structure (sink_caps, 0); + gst_structure_get_int (s, "mpegversion", &version); + gst_structure_get_int (s, "layer", &layer); + if (!gst_structure_get_int (s, "rate", &rate)) + update_caps = FALSE; + if (!gst_structure_get_int (s, "channels", &channels)) + update_caps = FALSE; + if (rate == 0 || channels == 0) + update_caps = FALSE; + GST_DEBUG ("mpegaudioversion = %d layer = %d rate = %d channels = %d", + version, layer, rate, channels); + } else { + GST_DEBUG_OBJECT (mp3parse, "There is no sink_caps!"); + } + + + if (G_UNLIKELY (update_caps)) { + GstCaps *caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, + "mpegaudioversion", G_TYPE_INT, version, + "layer", G_TYPE_INT, layer, + "rate", G_TYPE_INT, rate, + "channels", G_TYPE_INT, channels, "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + res = gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); + gst_caps_unref (caps); + } + + return res; +} + static GstFlowReturn gst_mpeg_audio_parse_handle_frame (GstBaseParse * parse, GstBaseParseFrame * frame, gint * skipsize) @@ -613,6 +657,10 @@ gst_mpeg_audio_parse_handle_frame (GstBaseParse * parse, guint bitrate, layer, rate, channels, version, mode, crc; GstMapInfo map; gboolean res = FALSE; + guint64 dummyskipsize = 0; + const guint8 *dummydata; + guint64 dummysize; + gint i, a_header_size; gst_buffer_map (buf, &map, GST_MAP_READ); if (G_UNLIKELY (map.size < 6)) { @@ -622,8 +670,61 @@ gst_mpeg_audio_parse_handle_frame (GstBaseParse * parse, gst_byte_reader_init (&reader, map.data, map.size); + dummydata = map.data; + dummysize = map.size; + + while (dummyskipsize < dummysize && dummydata[dummyskipsize] == 0xff) { + dummyskipsize++; + } + /* make sure the values in the frame header look sense */ + if (dummyskipsize > 1) { + header = GST_READ_UINT32_BE (&dummydata[dummyskipsize - 1]); + if (!gst_mpeg_audio_parse_head_check (mp3parse, header)) { + dummyskipsize--; + } + if (mp3parse->mpegvalid) { + dummyskipsize--; + } + } + + a_header_size = 2; /*acceptable header size as 0xff */ + + if (dummyskipsize > a_header_size) { + GstCaps *sinkCaps = NULL, *srcCaps = NULL; + if (dummyskipsize != dummysize) + dummyskipsize--; + + srcCaps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse)); + + if (!srcCaps) { + sinkCaps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse)); + if (sinkCaps) { + if (gst_mpeg_audio_parse_set_src_caps (parse, sinkCaps)) + GST_DEBUG_OBJECT (mp3parse, "Set src pad caps using sink's"); + else + GST_WARNING_OBJECT (mp3parse, + "CAN NOT set src pad caps using sink's"); + + gst_buffer_unmap (buf, &map); + gst_caps_unref (sinkCaps); + return gst_base_parse_finish_frame (parse, frame, dummyskipsize); + } else + dummyskipsize = 0; + } else { + gst_buffer_unmap (buf, &map); + gst_caps_unref (srcCaps); + return gst_base_parse_finish_frame (parse, frame, dummyskipsize); + } + } + + for (i = 0; i < a_header_size; i++) { + if (dummyskipsize > 0) { + dummyskipsize--; + } + } + off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffe00000, 0xffe00000, - 0, map.size); + dummyskipsize, map.size); GST_LOG_OBJECT (parse, "possible sync at buffer offset %d", off); @@ -658,9 +759,12 @@ gst_mpeg_audio_parse_handle_frame (GstBaseParse * parse, &version, &layer, &channels, &bitrate, &rate, &mode, &crc); if (channels != mp3parse->channels || rate != mp3parse->rate || - layer != mp3parse->layer || version != mp3parse->version) - caps_change = TRUE; - else + layer != mp3parse->layer || version != mp3parse->version) { + if (mp3parse->layer != 0 && layer != mp3parse->layer) + caps_change = FALSE; + else + caps_change = TRUE; + } else caps_change = FALSE; /* maybe free format */ diff --git a/gst/audioparsers/gstmpegaudioparse.h b/gst/audioparsers/gstmpegaudioparse.h index 5e576beae5..081fefec12 100644 --- a/gst/audioparsers/gstmpegaudioparse.h +++ b/gst/audioparsers/gstmpegaudioparse.h @@ -94,6 +94,7 @@ struct _GstMpegAudioParse { /* LAME info */ guint32 encoder_delay; guint32 encoder_padding; + gboolean mpegvalid; }; /** diff --git a/gst/audioparsers/gstwavpackparse.c b/gst/audioparsers/gstwavpackparse.c index 3e1d19c94f..7dc34cf442 100644 --- a/gst/audioparsers/gstwavpackparse.c +++ b/gst/audioparsers/gstwavpackparse.c @@ -61,7 +61,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/x-wavpack")); + GST_STATIC_CAPS ("audio/x-wavpack; audio/x-wavpack-correction")); static void gst_wavpack_parse_finalize (GObject * object); @@ -325,7 +325,9 @@ gst_wavpack_parse_frame_metadata (GstWavpackParse * parse, GstBuffer * buf, CHECK (gst_byte_reader_get_data (&br, size + (size & 1), &data)); gst_byte_reader_init (&mbr, data, size); - switch (id) { + /* 0x1f is the metadata id mask and 0x20 flag is for later extensions + * that do not need to be handled by the decoder */ + switch (id & 0x3f) { case ID_WVC_BITSTREAM: GST_LOG_OBJECT (parse, "correction bitstream"); wpi->correction = TRUE; diff --git a/gst/audioparsers/meson.build b/gst/audioparsers/meson.build index 9a5bf87ddf..12b2cc2f0f 100644 --- a/gst/audioparsers/meson.build +++ b/gst/audioparsers/meson.build @@ -2,7 +2,7 @@ audioparsers_src = [ 'gstaacparse.c', 'gstamrparse.c', 'gstac3parse.c', - 'gstdcaparse.c', + 'gstac4parse.c', 'gstflacparse.c', 'gstmpegaudioparse.c', 'gstsbcparse.c', diff --git a/gst/audioparsers/plugin.c b/gst/audioparsers/plugin.c index 8cbfe605d2..c80895f522 100644 --- a/gst/audioparsers/plugin.c +++ b/gst/audioparsers/plugin.c @@ -24,7 +24,7 @@ #include "gstaacparse.h" #include "gstamrparse.h" #include "gstac3parse.h" -#include "gstdcaparse.h" +#include "gstac4parse.h" #include "gstflacparse.h" #include "gstmpegaudioparse.h" #include "gstsbcparse.h" @@ -41,8 +41,8 @@ plugin_init (GstPlugin * plugin) GST_RANK_PRIMARY + 1, GST_TYPE_AMR_PARSE); ret &= gst_element_register (plugin, "ac3parse", GST_RANK_PRIMARY + 1, GST_TYPE_AC3_PARSE); - ret &= gst_element_register (plugin, "dcaparse", - GST_RANK_PRIMARY + 1, GST_TYPE_DCA_PARSE); + ret &= gst_element_register (plugin, "ac4parse", + GST_RANK_PRIMARY + 1, GST_TYPE_AC4_PARSE); ret &= gst_element_register (plugin, "flacparse", GST_RANK_PRIMARY + 1, GST_TYPE_FLAC_PARSE); ret &= gst_element_register (plugin, "mpegaudioparse", diff --git a/gst/autodetect/gstautodetect.c b/gst/autodetect/gstautodetect.c index 3e302410a1..a256e520dd 100644 --- a/gst/autodetect/gstautodetect.c +++ b/gst/autodetect/gstautodetect.c @@ -310,7 +310,6 @@ gst_auto_detect_find_best (GstAutoDetect * self) ret = gst_element_set_state (el, GST_STATE_READY); if (ret == GST_STATE_CHANGE_SUCCESS) { GST_DEBUG_OBJECT (self, "This worked!"); - gst_element_set_state (el, GST_STATE_NULL); choice = el; break; } @@ -380,13 +379,14 @@ gst_auto_detect_detect (GstAutoDetect * self) } self->kid = kid; - - gst_bin_add (GST_BIN (self), kid); - - /* Ensure the child is brought up to the right state to match the parent. */ + /* Ensure the child is brought up to the right state to match the parent. + * Although it's currently always in READY and we're always doing NULL->READY. + */ if (GST_STATE (self->kid) < GST_STATE (self)) gst_element_set_state (self->kid, GST_STATE (self)); + gst_bin_add (GST_BIN (self), kid); + /* attach ghost pad */ GST_DEBUG_OBJECT (self, "Re-assigning ghostpad"); if (!gst_auto_detect_attach_ghost_pad (self)) diff --git a/gst/avi/avi-ids.h b/gst/avi/avi-ids.h index 9c09803342..22cbf8ba0a 100644 --- a/gst/avi/avi-ids.h +++ b/gst/avi/avi-ids.h @@ -49,6 +49,7 @@ typedef struct _gst_riff_avih { /* vprp (video properties) ODML header */ /* see ODML spec for some/more explanation */ #define GST_RIFF_TAG_vprp GST_MAKE_FOURCC ('v','p','r','p') +#define GST_RIFF_DXSA GST_MAKE_FOURCC ('D','X','S','A') #define GST_RIFF_DXSB GST_MAKE_FOURCC ('D','X','S','B') #define GST_RIFF_VPRP_VIDEO_FIELDS (2) diff --git a/gst/avi/gstavidemux.c b/gst/avi/gstavidemux.c index c02ee896c0..1d65aba557 100644 --- a/gst/avi/gstavidemux.c +++ b/gst/avi/gstavidemux.c @@ -45,6 +45,7 @@ #include #include +#include #include "gst/riff/riff-media.h" #include "gstavidemux.h" @@ -53,17 +54,44 @@ #include #include +#define GST_IS_SUBTITLE_STREAM(e) (((e)->name != NULL && strstr ((e)->name, "Subtitle")) || \ + ((e)->strf.vids->compression == GST_RIFF_DXSA ||(e)->strf.vids->compression == GST_RIFF_DXSB)) + #define DIV_ROUND_UP(s,v) (((s) + ((v)-1)) / (v)) +#define GST_AVI_MAJOR_KEYFRAME ((1 << 1) | (1 << 0)) +#define ENTRY_IS_MAJOR_KEYFRAME(e) ((e)->flags == GST_AVI_MAJOR_KEYFRAME) +#define ENTRY_SET_MAJOR_KEYFRAME(e) ((e)->flags = GST_AVI_MAJOR_KEYFRAME) + #define GST_AVI_KEYFRAME (1 << 0) -#define ENTRY_IS_KEYFRAME(e) ((e)->flags == GST_AVI_KEYFRAME) +#define ENTRY_IS_KEYFRAME(e) ((e)->flags == GST_AVI_KEYFRAME || (e)->flags == GST_AVI_MAJOR_KEYFRAME) #define ENTRY_SET_KEYFRAME(e) ((e)->flags = GST_AVI_KEYFRAME) #define ENTRY_UNSET_KEYFRAME(e) ((e)->flags = 0) +#define GST_GET_MP3_FRAMESYNC(e) (e >> 21) +#define GST_GET_MP3_VERSION(e) ((e >> 19) & 0x03) +#define GST_MP3_FRAMESYNC (0xFFFFFFFF >> 21) +#define GST_MP3_VERSION1 3 +#define GST_MP3_VERSION2 2 +#define GST_FIRST_STREAM 0 +#define GST_MP3_FOURCC 0x55 +#define GST_AVI_FOURCC_LENGTH 4 +#define GST_AVI_CHUNK_HEADER_LENGTH 8 GST_DEBUG_CATEGORY_STATIC (avidemux_debug); #define GST_CAT_DEFAULT avidemux_debug +/*For Push based Trick mode*/ +#ifdef AVI_PUSHMODE_TRICK +static gboolean gst_avi_demux_handle_trick (GstAviDemux * avi, gint64 cur); +#endif + +enum +{ + PROP_0, + PROP_THUMBNAIL_MODE +}; + static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -81,18 +109,13 @@ static void gst_avi_demux_finalize (GObject * object); static void gst_avi_demux_reset (GstAviDemux * avi); -#if 0 -static const GstEventMask *gst_avi_demux_get_event_mask (GstPad * pad); -#endif static gboolean gst_avi_demux_handle_src_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event); +static gboolean gst_avi_demux_push_gap_audio (GstAviDemux * avi); -#if 0 -static const GstFormat *gst_avi_demux_get_src_formats (GstPad * pad); -#endif static gboolean gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, GstQuery * query); static gboolean gst_avi_demux_src_convert (GstPad * pad, GstFormat src_format, @@ -111,10 +134,6 @@ static gboolean gst_avi_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, GstPadMode mode, gboolean active); static GstFlowReturn gst_avi_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf); -#if 0 -static void gst_avi_demux_set_index (GstElement * element, GstIndex * index); -static GstIndex *gst_avi_demux_get_index (GstElement * element); -#endif static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element, GstStateChange transition); static void gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi); @@ -127,6 +146,13 @@ static void gst_avi_demux_parse_strd (GstAviDemux * avi, GstBuffer * buf); static void parse_tag_value (GstAviDemux * avi, GstTagList * taglist, const gchar * type, guint8 * ptr, guint tsize); +static void gst_avi_demux_parse_mpegaudio (GstAviDemux * avi); +static void gst_avi_demux_set_major_index (GstAviDemux * avi, + GstAviStream * stream); + +static GstBuffer *gst_avi_demux_invert (GstAviStream * stream, GstBuffer * buf); + +static gboolean gst_avi_demux_check_divx (guint32 fourcc); /* GObject methods */ @@ -148,10 +174,6 @@ gst_avi_demux_class_init (GstAviDemuxClass * klass) gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_avi_demux_change_state); -#if 0 - gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_avi_demux_set_index); - gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_avi_demux_get_index); -#endif audcaps = gst_riff_create_audio_template_caps (); gst_caps_append (audcaps, gst_caps_new_empty_simple ("audio/x-avi-unknown")); @@ -274,20 +296,20 @@ gst_avi_demux_reset (GstAviDemux * avi) avi->have_group_id = FALSE; avi->group_id = G_MAXUINT; + /*thumbnail_mode */ + avi->thumbnail_mode = FALSE; + avi->isInterleaved = FALSE; + avi->isNotSupportCodec = FALSE; + avi->state = GST_AVI_DEMUX_START; avi->offset = 0; avi->building_index = FALSE; + avi->is_flushing = FALSE; avi->index_offset = 0; g_free (avi->avih); avi->avih = NULL; -#if 0 - if (avi->element_index) - gst_object_unref (avi->element_index); - avi->element_index = NULL; -#endif - if (avi->seg_event) { gst_event_unref (avi->seg_event); avi->seg_event = NULL; @@ -305,38 +327,18 @@ gst_avi_demux_reset (GstAviDemux * avi) avi->have_eos = FALSE; avi->seekable = TRUE; +#ifdef AVI_PUSHMODE_TRICK + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + avi->all_audio_pushed = FALSE; +#endif + gst_adapter_clear (avi->adapter); gst_segment_init (&avi->segment, GST_FORMAT_TIME); avi->segment_seqnum = 0; } - -/* GstElement methods */ - -#if 0 -static const GstFormat * -gst_avi_demux_get_src_formats (GstPad * pad) -{ - GstAviStream *stream = gst_pad_get_element_private (pad); - - static const GstFormat src_a_formats[] = { - GST_FORMAT_TIME, - GST_FORMAT_BYTES, - GST_FORMAT_DEFAULT, - 0 - }; - static const GstFormat src_v_formats[] = { - GST_FORMAT_TIME, - GST_FORMAT_DEFAULT, - 0 - }; - - return (stream->strh->type == GST_RIFF_FCC_auds ? - src_a_formats : src_v_formats); -} -#endif - /* assumes stream->strf.auds->av_bps != 0 */ static inline GstClockTime avi_stream_convert_bytes_to_time_unchecked (GstAviStream * stream, @@ -529,9 +531,13 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, } /* take stream duration, fall back to avih duration */ - if ((duration = stream->duration) == -1) - if ((duration = stream->hdr_duration) == -1) + if ((duration = stream->duration) == -1) { + if (GST_CLOCK_TIME_IS_VALID (stream->hdr_duration) + && stream->hdr_duration > avi->duration) { + duration = stream->hdr_duration; + } else duration = avi->duration; + } gst_query_parse_duration (query, &fmt, NULL); @@ -558,6 +564,22 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, } break; } + case GST_QUERY_CUSTOM:{ + gboolean trickable = TRUE; + GstStructure *s; + + s = (GstStructure *) gst_query_get_structure (query); + + if (gst_structure_has_name (s, "custom-trickable")) { + + gst_structure_set (s, "trickable", G_TYPE_BOOLEAN, trickable, NULL); + res = TRUE; + break; + } else { + res = gst_pad_query_default (pad, parent, query); + break; + } + } case GST_QUERY_SEEKING:{ GstFormat fmt; @@ -614,67 +636,6 @@ gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent, return res; } -#if 0 -static const GstEventMask * -gst_avi_demux_get_event_mask (GstPad * pad) -{ - static const GstEventMask masks[] = { - {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT}, - {0,} - }; - - return masks; -} -#endif - -#if 0 -static guint64 -gst_avi_demux_seek_streams (GstAviDemux * avi, guint64 offset, gboolean before) -{ - GstAviStream *stream; - GstIndexEntry *entry; - gint i; - gint64 val, min = offset; - - for (i = 0; i < avi->num_streams; i++) { - stream = &avi->stream[i]; - - entry = gst_index_get_assoc_entry (avi->element_index, stream->index_id, - before ? GST_INDEX_LOOKUP_BEFORE : GST_INDEX_LOOKUP_AFTER, - GST_ASSOCIATION_FLAG_NONE, GST_FORMAT_BYTES, offset); - - if (before) { - if (entry) { - gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val); - GST_DEBUG_OBJECT (avi, "stream %d, previous entry at %" - G_GUINT64_FORMAT, i, val); - if (val < min) - min = val; - } - continue; - } - - if (!entry) { - GST_DEBUG_OBJECT (avi, "no position for stream %d, assuming at start", i); - stream->current_entry = 0; - stream->current_total = 0; - continue; - } - - gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &val); - GST_DEBUG_OBJECT (avi, "stream %d, next entry at %" G_GUINT64_FORMAT, - i, val); - - gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &val); - stream->current_total = val; - gst_index_entry_assoc_map (entry, GST_FORMAT_DEFAULT, &val); - stream->current_entry = val; - } - - return min; -} -#endif - static gint gst_avi_demux_index_entry_offset_search (GstAviIndexEntry * entry, guint64 * offset) @@ -758,6 +719,9 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, gint64 boffset, offset = 0; GstSegment segment; GstEvent *segment_event; +#ifdef AVI_PUSHMODE_TRICK + avi->segment_event_recvd = TRUE; +#endif /* some debug output */ gst_event_copy_segment (event, &segment); @@ -819,25 +783,6 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, /* get the ts corresponding to start offset bytes for the stream */ gst_avi_demux_get_buffer_info (avi, stream, index, (GstClockTime *) & segment.time, NULL, NULL, NULL); -#if 0 - } else if (avi->element_index) { - GstIndexEntry *entry; - - /* Let's check if we have an index entry for this position */ - entry = gst_index_get_assoc_entry (avi->element_index, avi->index_id, - GST_INDEX_LOOKUP_AFTER, GST_ASSOCIATION_FLAG_NONE, - GST_FORMAT_BYTES, segment.start); - - /* we can not go where we have not yet been before ... */ - if (!entry) { - GST_WARNING_OBJECT (avi, "insufficient index data, forcing EOS"); - goto eos; - } - - gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, - (gint64 *) & segment.time); - gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &offset); -#endif } else { GST_WARNING_OBJECT (avi, "no index data, forcing EOS"); goto eos; @@ -851,25 +796,46 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, /* rescue duration */ segment.duration = avi->segment.duration; +#ifdef AVI_PUSHMODE_TRICK + if (avi->streaming) { + segment.rate = avi->demux_rate; + avi->segment.rate = avi->demux_rate; + if (avi->demux_rate < 0.0) { + segment.stop = segment.start; + segment.start = 0; + segment.position = segment.stop; + segment.time = segment.start; + } + } +#endif + + /* set up segment and send downstream */ gst_segment_copy_into (&segment, &avi->segment); - GST_DEBUG_OBJECT (avi, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); - avi->segment_seqnum = gst_event_get_seqnum (event); - segment_event = gst_event_new_segment (&segment); - gst_event_set_seqnum (segment_event, gst_event_get_seqnum (event)); - gst_avi_demux_push_event (avi, segment_event); - +#ifdef AVI_PUSHMODE_TRICK + if (avi->rate_changed) { +#endif + GST_DEBUG_OBJECT (avi, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); + avi->segment_seqnum = gst_event_get_seqnum (event); + segment_event = gst_event_new_segment (&segment); + gst_event_set_seqnum (segment_event, gst_event_get_seqnum (event)); + gst_avi_demux_push_event (avi, segment_event); + /* Push gap event to downstream element if we do not need audio */ + if (avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS || + avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) { + GST_DEBUG_OBJECT (avi, "Pushing GAP for no audio !!"); + gst_avi_demux_push_gap_audio (avi); + } +#ifdef AVI_PUSHMODE_TRICK + } +#endif GST_DEBUG_OBJECT (avi, "next chunk expected at %" G_GINT64_FORMAT, boffset); /* adjust state for streaming thread accordingly */ if (avi->have_index) gst_avi_demux_seek_streams_index (avi, offset, FALSE); -#if 0 - else - gst_avi_demux_seek_streams (avi, offset, FALSE); -#endif /* set up streaming thread */ g_assert (offset >= boffset); @@ -897,16 +863,56 @@ gst_avi_demux_handle_sink_event (GstPad * pad, GstObject * parent, } break; } + +#ifdef AVI_PUSHMODE_TRICK + case GST_EVENT_FLUSH_START: + + avi->demux_rate = avi->segment.rate; + avi->segment_event_recvd = FALSE; + + if (((avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + || avi->segment.rate == 0.5) && !avi->rate_changed + && avi->streaming) { + gst_event_unref (event); + res = TRUE; + return res; + } else { + gst_avi_demux_push_event (avi, event); + avi->is_flushing = TRUE; + res = TRUE; + } + break; +#endif + + case GST_EVENT_FLUSH_STOP: { gint i; - gst_adapter_clear (avi->adapter); avi->have_eos = FALSE; for (i = 0; i < avi->num_streams; i++) { avi->stream[i].discont = TRUE; } - /* fall through to default case so that the event gets passed downstream */ + +#ifdef AVI_PUSHMODE_TRICK + if (((avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + || avi->segment.rate == 0.5) && (!avi->rate_changed) + && (avi->streaming)) { + gst_event_unref (event); + return TRUE; + } else +#endif + { + + if (avi->streaming && !avi->have_index) + avi->state = GST_AVI_DEMUX_SEEK; + + /* fall through to default case so that the event gets passed downstream */ + gst_avi_demux_push_event (avi, event); + res = TRUE; + } + avi->is_flushing = FALSE; + break; } default: res = gst_pad_event_default (pad, parent, event); @@ -1199,6 +1205,10 @@ gst_avi_demux_parse_avih (GstAviDemux * avi, else avi->duration = GST_CLOCK_TIME_NONE; + if (avih->flags & GST_RIFF_AVIH_ISINTERLEAVED || avih->streams == 1) + avi->isInterleaved = TRUE; + + GST_INFO_OBJECT (avi, " isInterleaved %d", avi->isInterleaved); GST_INFO_OBJECT (avi, " header duration %" GST_TIME_FORMAT, GST_TIME_ARGS (avi->duration)); @@ -1331,9 +1341,10 @@ gst_avi_demux_add_index (GstAviDemux * avi, GstAviStream * stream, if (idx_max == 0) { /* initial size guess, assume each stream has an equal amount of entries, * overshoot with at least 8K */ - idx_max = (num / avi->num_streams) + (8192 / sizeof (GstAviIndexEntry)); + idx_max = + (num / avi->num_streams) + (8192 * 100 / sizeof (GstAviIndexEntry)); } else { - idx_max += 8192 / sizeof (GstAviIndexEntry); + idx_max += 8192 * 100 / sizeof (GstAviIndexEntry); GST_DEBUG_OBJECT (avi, "expanded index from %u to %u", stream->idx_max, idx_max); } @@ -1396,25 +1407,12 @@ gst_avi_demux_get_buffer_info (GstAviDemux * avi, GstAviStream * stream, if (stream->is_vbr) { /* VBR stream next timestamp */ - if (stream->strh->type == GST_RIFF_FCC_auds) { - if (timestamp) - *timestamp = - avi_stream_convert_frames_to_time_unchecked (stream, entry->total); - if (ts_end) { - gint size = 1; - if (G_LIKELY (entry_n + 1 < stream->idx_n)) - size = stream->index[entry_n + 1].total - entry->total; - *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, - entry->total + size); - } - } else { - if (timestamp) - *timestamp = - avi_stream_convert_frames_to_time_unchecked (stream, entry_n); - if (ts_end) - *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, - entry_n + 1); - } + if (timestamp) + *timestamp = + avi_stream_convert_frames_to_time_unchecked (stream, entry_n); + if (ts_end) + *ts_end = avi_stream_convert_frames_to_time_unchecked (stream, + entry_n + 1); } else if (stream->strh->type == GST_RIFF_FCC_auds) { /* constant rate stream */ if (timestamp) @@ -1473,6 +1471,22 @@ gst_avi_demux_do_index_stats (GstAviDemux * avi) #ifndef GST_DISABLE_GST_DEBUG total_max += stream->idx_max; #endif + if (avi->streaming && stream->strh->type == GST_RIFF_FCC_auds) { + GstAviStream *main_stream = &avi->stream[avi->main_stream]; + guint64 video_lastOffset = 0, audio_firstOffset = 0; + + video_lastOffset = main_stream->index[main_stream->idx_n - 1].offset; + audio_firstOffset = stream->index[0].offset; + + if (video_lastOffset < audio_firstOffset && video_lastOffset > (1 << 24)) + avi->isInterleaved = FALSE; + else + avi->isInterleaved = TRUE; + } + + if (stream->strh->type == GST_RIFF_FCC_vids) + gst_avi_demux_set_major_index (avi, stream); + GST_INFO_OBJECT (avi, "Stream %d, dur %" GST_TIME_FORMAT ", %6u entries, " "%5u keyframes, entry size = %2u, total size = %10u, allocated %10u", i, GST_TIME_ARGS (stream->idx_duration), stream->idx_n, @@ -1586,9 +1600,13 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi, GstAviStream * stream, } entry.size &= ~0x80000000; + /* initialize total */ + entry.total = 0; + /* and add */ if (G_UNLIKELY (!gst_avi_demux_add_index (avi, stream, num, &entry))) goto out_of_mem; + } done: gst_buffer_unmap (buf, &map); @@ -1629,6 +1647,40 @@ gst_avi_demux_parse_subindex (GstAviDemux * avi, GstAviStream * stream, } } +/* + * Set Major keyframe flag for FFx16 + */ +static void +gst_avi_demux_set_major_index (GstAviDemux * avi, GstAviStream * stream) +{ + guint32 num, i; + + num = stream->idx_n; + + if (GST_IS_SUBTITLE_STREAM (stream) + || stream->strh->type != GST_RIFF_FCC_vids) + return; + + if (num / stream->n_keyframes < 10) { + gboolean isKeyframe = FALSE; + + GST_WARNING_OBJECT (avi, "too many keyframe total entry %d, keyframes %d", + num, stream->n_keyframes); + + stream->isHeavyIndex = TRUE; + + for (i = 0; i < num; i++) { + if (ENTRY_IS_KEYFRAME (&stream->index[i])) { + if (!isKeyframe) { + ENTRY_SET_MAJOR_KEYFRAME (&stream->index[i]); + isKeyframe = TRUE; + } else + isKeyframe = FALSE; + } + } + } +} + /* * Create and push a flushing seek event upstream */ @@ -1637,13 +1689,24 @@ perform_seek_to_offset (GstAviDemux * demux, guint64 offset, guint32 seqnum) { GstEvent *event; gboolean res = 0; + GstSeekFlags flags = 0; + + if (demux->is_flushing) { + GST_WARNING_OBJECT (demux, "In flushing state. SKIP this seek event"); + return TRUE; + } GST_DEBUG_OBJECT (demux, "Seeking to %" G_GUINT64_FORMAT, offset); + if (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + flags |= GST_SEEK_FLAG_TRICKMODE_KEY_UNITS; + if (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) + flags |= GST_SEEK_FLAG_TRICKMODE_NO_AUDIO; + event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, - GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, - GST_SEEK_TYPE_NONE, -1); + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SKIP | flags, GST_SEEK_TYPE_SET, + offset, GST_SEEK_TYPE_NONE, -1); gst_event_set_seqnum (event, seqnum); res = gst_pad_push_event (demux->sinkpad, event); @@ -1667,19 +1730,20 @@ gst_avi_demux_read_subindexes_push (GstAviDemux * avi) if (avi->odml_subidxs[avi->odml_subidx] != avi->offset) return FALSE; - if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) - return TRUE; - /* this is the ODML chunk we expect */ odml_stream = avi->odml_stream; - if ((tag != GST_MAKE_FOURCC ('i', 'x', '0' + odml_stream / 10, - '0' + odml_stream % 10)) && - (tag != GST_MAKE_FOURCC ('0' + odml_stream / 10, - '0' + odml_stream % 10, 'i', 'x'))) { - GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS (tag)); - return FALSE; + if (!gst_avi_demux_peek_chunk (avi, &tag, &size)) { + if ((tag == GST_MAKE_FOURCC ('i', 'x', '0' + odml_stream / 10, + '0' + odml_stream % 10)) || + (tag == GST_MAKE_FOURCC ('0' + odml_stream / 10, + '0' + odml_stream % 10, 'i', 'x'))) + return TRUE; + else { + GST_WARNING_OBJECT (avi, "Not an ix## chunk (%" GST_FOURCC_FORMAT ")", + GST_FOURCC_ARGS (tag)); + return FALSE; + } } avi->offset += 8 + GST_ROUND_UP_2 (size); @@ -1898,15 +1962,12 @@ gst_avi_demux_expose_streams (GstAviDemux * avi, gboolean force) gst_element_add_pad ((GstElement *) avi, stream->pad); gst_flow_combiner_add_pad (avi->flowcombiner, stream->pad); -#if 0 - if (avi->element_index) - gst_index_get_writer_id (avi->element_index, - GST_OBJECT_CAST (stream->pad), &stream->index_id); -#endif - stream->exposed = TRUE; - if (avi->main_stream == -1) + if (avi->main_stream == -1 && stream->strh->type == GST_RIFF_FCC_vids) { avi->main_stream = i; + GST_INFO_OBJECT (avi, " Main Stream Num : %d ) Name : %s", i, + stream->name); + } } else { GST_WARNING_OBJECT (avi, "Stream #%d doesn't have any entry, removing it", i); @@ -1993,6 +2054,9 @@ gst_avi_demux_check_caps (GstAviDemux * avi, GstAviStream * stream, /* ... but rather properly parsed bytestream */ gst_structure_set (s, "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", NULL); + } else { + gst_structure_set (s, "stream-format", G_TYPE_STRING, "avc", + "alignment", G_TYPE_STRING, "au", NULL); } } else { gst_buffer_unmap (buf, &map); @@ -2035,6 +2099,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) gchar *stream_id; GstMapInfo map; gboolean sparse = FALSE; + guint max_stream = GST_AVI_DEMUX_MAX_STREAMS; element = GST_ELEMENT_CAST (avi); @@ -2042,10 +2107,12 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) gst_avi_demux_roundup_list (avi, &buf); - if (avi->num_streams >= GST_AVI_DEMUX_MAX_STREAMS) { + if (avi->thumbnail_mode) + max_stream = 1; + + if (avi->num_streams >= max_stream) { GST_WARNING_OBJECT (avi, - "maximum no of streams (%d) exceeded, ignoring stream", - GST_AVI_DEMUX_MAX_STREAMS); + "maximum no of streams (%d) exceeded, ignoring stream", max_stream); gst_buffer_unref (buf); /* not a fatal error, let's say */ return TRUE; @@ -2099,6 +2166,11 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) strh->rate, strh->scale); } } + + if (stream->strh->type == GST_RIFF_FCC_auds + && avi->num_streams == GST_FIRST_STREAM) + avi->thumbnail_mode = FALSE; + /* determine duration as indicated by header */ stream->hdr_duration = gst_util_uint64_scale ((guint64) strh->length * strh->scale, GST_SECOND, (guint64) strh->rate); @@ -2137,9 +2209,20 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) sub = NULL; if (!res) break; + stream->is_vbr = (stream->strh->samplesize == 0) && stream->strh->scale > 1 && stream->strf.auds->blockalign != 1; + + /*zeldasky 2012-02-04 for exceptional case for AAC */ + if (!stream->is_vbr && stream->strf.auds->format == 0xFF) { + stream->is_vbr = (stream->strh->samplesize == 0 || + stream->strh->samplesize == 1 || + stream->strh->samplesize == 1024) + && stream->strh->scale > 1 + && stream->strf.auds->blockalign != 1; + } + GST_DEBUG_OBJECT (element, "marking audio as VBR:%d, res %d", stream->is_vbr, res); /* we need these or we have no way to come up with timestamps */ @@ -2225,6 +2308,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) gst_avi_demux_parse_strd (avi, sub); sub = NULL; } + sub = NULL; break; case GST_RIFF_TAG_strn: g_free (stream->name); @@ -2243,6 +2327,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) gst_buffer_unmap (sub, &map); gst_buffer_unref (sub); sub = NULL; + break; case GST_RIFF_IDIT: gst_avi_demux_parse_idit (avi, sub); @@ -2290,6 +2375,9 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) goto fail; } + if (GST_IS_SUBTITLE_STREAM (stream)) + return FALSE; + /* get class to figure out the template */ klass = GST_ELEMENT_GET_CLASS (avi); @@ -2298,6 +2386,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) switch (stream->strh->type) { case GST_RIFF_FCC_vids:{ guint32 fourcc; + gchar *format = NULL; fourcc = (stream->strf.vids->compression) ? stream->strf.vids->compression : stream->strh->fcc_handler; @@ -2326,6 +2415,21 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) n, d, NULL); } caps = gst_avi_demux_check_caps (avi, stream, caps); + + if (gst_avi_demux_check_divx (fourcc)) { + gst_caps_set_simple (caps, "format", G_TYPE_STRING, "XVID", NULL); + } else { + format = g_strdup_printf ("%c%c%c%c", GST_FOURCC_ARGS (fourcc)); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, format, NULL); + + if (strstr (format, "MP42")) { + avi->isNotSupportCodec = TRUE; + } + + g_free (format); + } + + tag_name = GST_TAG_VIDEO_CODEC; avi->num_v_streams++; } else { @@ -2338,12 +2442,23 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) break; } case GST_RIFF_FCC_auds:{ + GstStructure *s; + /* FIXME: Do something with the channel reorder map */ padname = g_strdup_printf ("audio_%u", avi->num_a_streams); templ = gst_element_class_get_pad_template (klass, "audio_%u"); caps = gst_riff_create_audio_caps (stream->strf.auds->format, stream->strh, stream->strf.auds, stream->extradata, stream->initdata, &codec_name, NULL); + + s = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (s, "audio/x-wma")) { + gst_caps_unref (caps); + caps = gst_caps_new_simple ("audio/x-avi-unknown", "codec_id", + G_TYPE_INT, stream->strf.auds->format, NULL); + } + if (!caps) { caps = gst_caps_new_simple ("audio/x-avi-unknown", "codec_id", G_TYPE_INT, stream->strf.auds->format, NULL); @@ -2386,6 +2501,12 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) goto fail; } + /* zeldasky */ + gst_caps_set_simple (caps, "container", G_TYPE_STRING, "avi", NULL); + gst_caps_set_simple (caps, "seekable", G_TYPE_BOOLEAN, avi->seekable, NULL); + + gst_caps_set_simple (caps, "trickable", G_TYPE_BOOLEAN, TRUE, NULL); + GST_DEBUG_OBJECT (element, "codec-name=%s", codec_name ? codec_name : "NULL"); GST_DEBUG_OBJECT (element, "caps=%" GST_PTR_FORMAT, caps); @@ -2396,21 +2517,10 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) g_free (padname); gst_pad_use_fixed_caps (pad); -#if 0 - gst_pad_set_formats_function (pad, - GST_DEBUG_FUNCPTR (gst_avi_demux_get_src_formats)); - gst_pad_set_event_mask_function (pad, - GST_DEBUG_FUNCPTR (gst_avi_demux_get_event_mask)); -#endif gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_handle_src_event)); gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_avi_demux_handle_src_query)); -#if 0 - gst_pad_set_convert_function (pad, - GST_DEBUG_FUNCPTR (gst_avi_demux_src_convert)); -#endif - stream->num = avi->num_streams; stream->start_entry = 0; @@ -2428,6 +2538,7 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) stream->idx_n = 0; stream->idx_max = 0; + stream->isHeavyIndex = FALSE; gst_pad_set_element_private (pad, stream); avi->num_streams++; @@ -2488,7 +2599,6 @@ gst_avi_demux_parse_stream (GstAviDemux * avi, GstBuffer * buf) g_free (vprp); g_free (codec_name); gst_avi_demux_reset_stream (avi, stream); - avi->num_streams++; return FALSE; } } @@ -2662,6 +2772,8 @@ gst_avi_demux_index_for_time (GstAviDemux * avi, if (itime < time && index + 1 < stream->idx_n) index++; } + if (index == -1) + total = 0; } } else if (stream->strh->type == GST_RIFF_FCC_auds) { /* constant rate stream */ @@ -2799,6 +2911,9 @@ gst_avi_demux_parse_index (GstAviDemux * avi, GstBuffer * buf) } } + /* initialize total */ + entry.total = 0; + /* and add */ if (G_UNLIKELY (!gst_avi_demux_add_index (avi, stream, num, &entry))) goto out_of_mem; @@ -2867,10 +2982,12 @@ gst_avi_demux_stream_index (GstAviDemux * avi) /* check tag first before blindy trying to read 'size' bytes */ tag = GST_READ_UINT32_LE (map.data); size = GST_READ_UINT32_LE (map.data + 4); - if (tag == GST_RIFF_TAG_LIST) { + if (tag == GST_RIFF_TAG_LIST || + tag == GST_RIFF_TAG_JUNQ || tag == GST_RIFF_TAG_JUNK) { /* this is the movi tag */ - GST_DEBUG_OBJECT (avi, "skip LIST chunk, size %" G_GUINT32_FORMAT, - (8 + GST_ROUND_UP_2 (size))); + GST_DEBUG_OBJECT (avi, + "skip %" GST_FOURCC_FORMAT " chunk, size %" G_GUINT32_FORMAT, + GST_FOURCC_ARGS (tag), (8 + GST_ROUND_UP_2 (size))); offset += 8 + GST_ROUND_UP_2 (size); gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); @@ -3167,6 +3284,15 @@ gst_avi_demux_stream_scan (GstAviDemux * avi) goto out_of_mem; next: + + /*zeldasky 2012-02-21 for exceptionall case */ + if (((tag & 0xff) == 0x00) && ((((tag) >> 8) & 0xff) == 0x00) + && ((((tag) >> 16) & 0xff) == 0x00)) { + GST_WARNING_OBJECT (avi, + "Stopping index lookup since scan fail. something wrong."); + break; + } + /* update position */ pos += GST_ROUND_UP_2 (size); if (G_UNLIKELY (pos > length)) { @@ -3198,6 +3324,7 @@ gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi) guint i; GstClockTime total; GstAviStream *stream; + GstAviIndexEntry *entry; total = GST_CLOCK_TIME_NONE; @@ -3220,6 +3347,20 @@ gst_avi_demux_calculate_durations_from_index (GstAviDemux * avi) /* index gave valid duration, use that */ GST_INFO ("Stream %p duration according to index: %" GST_TIME_FORMAT, stream, GST_TIME_ARGS (duration)); + + /* [20130729-sangkyu : MP4_DivX5_ADPCM.mp4] + some bad case av_bps's information not corrects. + In this case, index durations is modified by hduration + I don't know why most pcm audio's av_bps was not correct... */ + if (stream->strh->type == GST_RIFF_FCC_auds && duration < hduration && + (stream->strf.auds->format == 1 || stream->strf.auds->format == 17)) { + entry = &stream->index[stream->idx_n - 1]; + stream->strf.auds->av_bps = + (entry->total + entry->size) * GST_SECOND / hduration; + + duration = stream->idx_duration = hduration; + GST_ERROR_OBJECT (avi, "Audio-PCM && index-duraion < hduration Case"); + } } else { /* fall back to header info to calculate a duration */ duration = hduration; @@ -3272,6 +3413,10 @@ gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event) for (i = 0; i < avi->num_streams; i++) { GstAviStream *stream = &avi->stream[i]; + if (stream->strh && stream->strh->type == GST_RIFF_FCC_vids + && i != avi->main_stream) + continue; + if (stream->pad) { result = TRUE; gst_pad_push_event (stream->pad, gst_event_ref (event)); @@ -3281,6 +3426,29 @@ gst_avi_demux_push_event (GstAviDemux * avi, GstEvent * event) return result; } +static gboolean +gst_avi_demux_push_gap_audio (GstAviDemux * avi) +{ + gboolean result = FALSE; + gint i; + + GST_DEBUG_OBJECT (avi, "sending GAP event to all audio pads"); + + for (i = 0; i < avi->num_streams; i++) { + GstAviStream *stream = &avi->stream[i]; + + if (stream->strh && stream->strh->type != GST_RIFF_FCC_auds) + continue; + + if (stream->pad) { + result = TRUE; + gst_pad_push_event (stream->pad, + gst_event_new_gap (avi->segment.start, GST_CLOCK_TIME_NONE)); + } + } + return result; +} + static void gst_avi_demux_check_seekability (GstAviDemux * avi) { @@ -3379,6 +3547,9 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) GST_DEBUG_OBJECT (avi, "AVI header ok, reading elemnts from header"); + /* move it for set a flag(seekable, trickable) */ + gst_avi_demux_check_seekability (avi); + /* now, read the elements from the header until the end */ while (gst_riff_parse_chunk (GST_ELEMENT_CAST (avi), buf, &offset, &tag, &sub)) { @@ -3476,6 +3647,7 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) if (tag == GST_RIFF_TAG_LIST) { switch (ltag) { case GST_RIFF_LIST_movi: + gst_avi_demux_parse_mpegaudio (avi); gst_adapter_flush (avi->adapter, 12); if (!avi->first_movi_offset) avi->first_movi_offset = avi->offset; @@ -3550,7 +3722,30 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) avi->num_streams, avi->stream[0].indexes); GST_DEBUG ("Found movi chunk. Starting to stream data"); - avi->state = GST_AVI_DEMUX_MOVI; + + if (!avi->isInterleaved) { + GstEvent *seek_event = NULL; + GstPad *seek_pad = NULL; + + seek_event = + gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1); + + GST_INFO_OBJECT (avi, "work a internal seek"); + + gst_avi_demux_calculate_durations_from_index (avi); + + gst_avi_demux_expose_streams (avi, TRUE); + + GST_DEBUG_OBJECT (avi, "signaling no more pads"); + gst_element_no_more_pads (GST_ELEMENT_CAST (avi)); + + gst_avi_demux_handle_seek_push (avi, seek_pad, seek_event); + + return GST_FLOW_OK; + + } else + avi->state = GST_AVI_DEMUX_MOVI; /* no indexes in push mode, but it still sets some variables */ gst_avi_demux_calculate_durations_from_index (avi); @@ -3568,8 +3763,6 @@ gst_avi_demux_stream_header_push (GstAviDemux * avi) if (avi->segment_seqnum) gst_event_set_seqnum (avi->seg_event, avi->segment_seqnum); - gst_avi_demux_check_seekability (avi); - /* at this point we know all the streams and we can signal the no more * pads signal */ GST_DEBUG_OBJECT (avi, "signaling no more pads"); @@ -4203,6 +4396,7 @@ gst_avi_demux_stream_header_pull (GstAviDemux * avi) case GST_RIFF_TAG_LIST:{ switch (ltag) { case GST_RIFF_LIST_movi: + gst_avi_demux_parse_mpegaudio (avi); GST_DEBUG_OBJECT (avi, "Reached the 'movi' tag, we're done with skipping"); goto skipping_done; @@ -4491,11 +4685,14 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment, /* FIXME, this code assumes the main stream with keyframes is stream 0, * which is mostly correct... */ + + /*zeldasky 2012-02-16 for exceptional case */ + if (avi->main_stream == -1) + avi->main_stream = 0; + stream = &avi->stream[avi->main_stream]; next = after && !before; - if (segment->rate < 0) - next = !next; /* get the entry index for the requested position */ index = gst_avi_demux_index_for_time (avi, stream, seek_time, next); @@ -4503,6 +4700,9 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment, if (index == -1) return FALSE; + if (index > stream->idx_n) + index = 0; + /* check if we are already on a keyframe */ if (!ENTRY_IS_KEYFRAME (&stream->index[index])) { if (next) { @@ -4520,6 +4720,9 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment, } } + if (stream->strh->type == GST_RIFF_FCC_vids && index < 10) + index = 0; + /* move the main stream to this position */ gst_avi_demux_move_stream (avi, stream, segment, index); @@ -4552,9 +4755,16 @@ gst_avi_demux_do_seek (GstAviDemux * avi, GstSegment * segment, if (index == -1) continue; - /* move to previous keyframe */ - if (!ENTRY_IS_KEYFRAME (&ostream->index[index])) - index = gst_avi_demux_index_prev (avi, ostream, index, TRUE); + /*zeldasky 2011-11-30 follow seek dirction */ + if (!ENTRY_IS_KEYFRAME (&ostream->index[index])) { + if (segment->rate > 2.0) { + GST_INFO_OBJECT (avi, "move to next keyframe"); + index = gst_avi_demux_index_next (avi, ostream, index, TRUE); + } else { + GST_INFO_OBJECT (avi, "move to previous keyframe"); + index = gst_avi_demux_index_prev (avi, ostream, index, TRUE); + } + } gst_avi_demux_move_stream (avi, ostream, segment, index); } @@ -4588,6 +4798,9 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) &cur_type, &cur, &stop_type, &stop); seqnum = gst_event_get_seqnum (event); + if (rate < -2 && cur == stop) + return TRUE; + /* we have to have a format as the segment format. Try to convert * if not. */ if (format != GST_FORMAT_TIME) { @@ -4705,6 +4918,103 @@ gst_avi_demux_handle_seek (GstAviDemux * avi, GstPad * pad, GstEvent * event) } } +#ifdef AVI_PUSHMODE_TRICK +static gboolean +gst_avi_demux_handle_trick (GstAviDemux * avi, gint64 cur) +{ + GstAviStream *stream; + guint index; + guint str_num; + guint64 min_offset; + + /* FIXME, this code assumes the main stream with keyframes is stream 0, + * which is mostly correct... */ + str_num = avi->main_stream; + stream = &avi->stream[str_num]; + + avi->rate_changed = FALSE; + + /* get the entry index for the requested position */ + index = gst_avi_demux_index_for_time (avi, stream, cur, FALSE); + + if (!ENTRY_IS_KEYFRAME (&stream->index[index])) { + avi->segment.rate > 0.0 ? index++ : index--; + } + + if (index >= stream->idx_n) { + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + gst_avi_demux_push_event (avi, gst_event_new_eos ()); + return TRUE; + } + + if (index == avi->prev_keyframe_index) { + avi->segment.rate > 0.0 ? index++ : index--; + } + + avi->prev_keyframe_index = index; + GST_DEBUG ("Index found is %d", index); + + GST_DEBUG_OBJECT (avi, "str %u: Found entry %u for %" GST_TIME_FORMAT, + str_num, index, GST_TIME_ARGS (cur)); + if (index == -1) + return -1; + + if (avi->segment.rate > 0.0) { + GST_DEBUG_OBJECT (avi, "Searching forward for keyframe"); + /* now go to the next keyframe, this is where we should start + * decoding from. */ + index = gst_avi_demux_index_next (avi, stream, index, TRUE); + GST_DEBUG_OBJECT (avi, "Found next keyframe at %u", index); + } else { + GST_DEBUG_OBJECT (avi, "Searching back for keyframe"); + /* now go to the previous keyframe, this is where we should start + * decoding from. */ + index = gst_avi_demux_index_prev (avi, stream, index, TRUE); + GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", index); + + if (stream->strh->type == GST_RIFF_FCC_vids && index < 10) + index = 0; + } + if (avi->prev_keyframe_index == index) { + + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + gst_avi_demux_push_event (avi, gst_event_new_eos ()); + return TRUE; + } + if (avi->segment.rate < 0.0 && index == 0) { + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + gst_avi_demux_push_event (avi, gst_event_new_eos ()); + return TRUE; + } + + gst_avi_demux_get_buffer_info (avi, stream, index, + &stream->current_timestamp, &stream->current_ts_end, + &stream->current_offset, &stream->current_offset_end); + + /* re-use cur to be the timestamp of the seek as it _will_ be */ + cur = stream->current_timestamp; + + min_offset = stream->index[index].offset; + avi->seek_kf_offset = min_offset - 8; + + + /* index data refers to data, not chunk header (for pull mode convenience) */ + min_offset -= 8; + GST_DEBUG_OBJECT (avi, "seeking to chunk at offset %" G_GUINT64_FORMAT, + min_offset); + + + if (!perform_seek_to_offset (avi, min_offset, avi->segment_seqnum)) { + GST_DEBUG_OBJECT (avi, "seek event failed!"); + return FALSE; + } + return TRUE; +} +#endif + /* * Handle seek event in push mode. */ @@ -4749,6 +5059,15 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) format = GST_FORMAT_TIME; } +#ifdef AVI_PUSHMODE_TRICK + avi->demux_rate = rate; + avi->segment_event_recvd = FALSE; + avi->rate_changed = TRUE; + avi->segment.rate = rate; + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + avi->all_audio_pushed = FALSE; +#endif /* let gst_segment handle any tricky stuff */ GST_DEBUG_OBJECT (avi, "configuring seek"); @@ -4756,7 +5075,19 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, stop_type, stop, &update); - keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); + if (seeksegment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + avi->segment.flags |= GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS; + } else { + avi->segment.flags &= ~GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS; + } + if (seeksegment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) { + avi->segment.flags |= GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO; + } else { + avi->segment.flags &= ~GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO; + } + + //keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); + keyframe = TRUE; cur = seeksegment.position; before = ! !(flags & GST_SEEK_FLAG_SNAP_BEFORE); after = ! !(flags & GST_SEEK_FLAG_SNAP_AFTER); @@ -4766,19 +5097,23 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) ", kf %u, %s, rate %lf", GST_TIME_ARGS (cur), GST_TIME_ARGS (stop), keyframe, snap_types[before ? 1 : 0][after ? 1 : 0], rate); - if (rate < 0) { - GST_DEBUG_OBJECT (avi, "negative rate seek not supported in push mode"); - return FALSE; - } + /* FIXME, this code assumes the main stream with keyframes is stream 0, * which is mostly correct... */ str_num = avi->main_stream; stream = &avi->stream[str_num]; - next = after && !before; - if (seeksegment.rate < 0) - next = !next; + if (!avi->streaming) { + next = after && !before; + if (seeksegment.rate < 0) + next = !next; + } else { + if (avi->segment.rate > 2.0) + next = TRUE; + else + next = FALSE; + } /* get the entry index for the requested position */ index = gst_avi_demux_index_for_time (avi, stream, cur, next); @@ -4794,7 +5129,7 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) /* now go to the next keyframe, this is where we should start * decoding from. */ index = gst_avi_demux_index_next (avi, stream, index, TRUE); - GST_DEBUG_OBJECT (avi, "Found previous keyframe at %u", index); + GST_DEBUG_OBJECT (avi, "Found next keyframe at %u", index); } else { GST_DEBUG_OBJECT (avi, "Entry is not a keyframe - searching back"); /* now go to the previous keyframe, this is where we should start @@ -4804,6 +5139,9 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) } } + if (stream->strh->type == GST_RIFF_FCC_vids && index < 10) + index = 0; + gst_avi_demux_get_buffer_info (avi, stream, index, &stream->current_timestamp, &stream->current_ts_end, &stream->current_offset, &stream->current_offset_end); @@ -4854,7 +5192,7 @@ avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, GstEvent * event) &str->current_timestamp, &str->current_ts_end, &str->current_offset, &str->current_offset_end); - if (str->index[idx].offset < min_offset) { + if (str->index[idx].offset < min_offset && (seeksegment.rate == 1.0)) { min_offset = str->index[idx].offset; GST_DEBUG_OBJECT (avi, "Found an earlier offset at %" G_GUINT64_FORMAT ", str %u", @@ -4900,8 +5238,6 @@ gst_avi_demux_handle_seek_push (GstAviDemux * avi, GstPad * pad, gboolean building_index; GST_OBJECT_LOCK (avi); - /* handle the seek event in the chain function */ - avi->state = GST_AVI_DEMUX_SEEK; /* copy the event */ if (avi->seek_event) @@ -5023,29 +5359,6 @@ gst_avi_demux_invert (GstAviStream * stream, GstBuffer * buf) return buf; } -#if 0 -static void -gst_avi_demux_add_assoc (GstAviDemux * avi, GstAviStream * stream, - GstClockTime timestamp, guint64 offset, gboolean keyframe) -{ - /* do not add indefinitely for open-ended streaming */ - if (G_UNLIKELY (avi->element_index && avi->seekable)) { - GST_LOG_OBJECT (avi, "adding association %" GST_TIME_FORMAT "-> %" - G_GUINT64_FORMAT, GST_TIME_ARGS (timestamp), offset); - gst_index_add_association (avi->element_index, avi->index_id, - keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT : - GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, timestamp, - GST_FORMAT_BYTES, offset, NULL); - /* current_entry is DEFAULT (frame #) */ - gst_index_add_association (avi->element_index, stream->index_id, - keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT : - GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, timestamp, - GST_FORMAT_BYTES, offset, GST_FORMAT_DEFAULT, stream->current_entry, - NULL); - } -} -#endif - /* * Returns the aggregated GstFlowReturn. */ @@ -5067,72 +5380,124 @@ gst_avi_demux_advance (GstAviDemux * avi, GstAviStream * stream, GstFlowReturn ret) { guint old_entry, new_entry; + guint skip_count = 0; + gboolean skip_feeding = FALSE; + gboolean keyframe_trick = FALSE; + + if (avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + keyframe_trick = TRUE; + stream->discont = TRUE; + if (stream->strh->type == GST_RIFF_FCC_auds) { + skip_feeding = TRUE; + } + } else if (G_UNLIKELY (avi-> + segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) + && (stream->strh->type == GST_RIFF_FCC_auds)) { + stream->discont = TRUE; + skip_feeding = TRUE; + } - old_entry = stream->current_entry; - /* move forwards */ - new_entry = old_entry + 1; + /*zeldasky 2012-03-07 For exceptional case. Some SOC performance very low and Mjpeg all video is key frame */ + /* in case of MJPEG, feeding video frame skips when high speed trick play */ + if (stream->strh != NULL + && (stream->strh->fcc_handler == GST_MAKE_FOURCC ('M', 'J', 'P', 'G') + || stream->strh->fcc_handler == GST_MAKE_FOURCC ('m', 'j', 'p', 'g'))) { + if (keyframe_trick && !skip_feeding) + skip_feeding = TRUE; + } + + do { + skip_count++; + + old_entry = stream->current_entry; + /* move forwards */ + new_entry = old_entry + 1; + + /* see if we reached the end */ + if (new_entry >= stream->stop_entry) { + if (avi->segment.rate < 0.0) { + if (stream->step_entry == stream->start_entry) { + /* we stepped all the way to the start, eos */ + GST_DEBUG_OBJECT (avi, "reverse reached start %u", + stream->start_entry); + goto eos; + } + /* backwards, stop becomes step, find a new step */ + stream->stop_entry = stream->step_entry; + stream->step_entry = gst_avi_demux_index_prev (avi, stream, + stream->stop_entry, TRUE); + + GST_DEBUG_OBJECT (avi, + "reverse playback jump: start %u, step %u, stop %u", + stream->start_entry, stream->step_entry, stream->stop_entry); - /* see if we reached the end */ - if (new_entry >= stream->stop_entry) { - if (avi->segment.rate < 0.0) { - if (stream->step_entry == stream->start_entry) { - /* we stepped all the way to the start, eos */ - GST_DEBUG_OBJECT (avi, "reverse reached start %u", stream->start_entry); + /* and start from the previous keyframe now */ + new_entry = stream->step_entry; + } else { + /* EOS */ + GST_DEBUG_OBJECT (avi, "forward reached stop %u", stream->stop_entry); goto eos; } - /* backwards, stop becomes step, find a new step */ - stream->stop_entry = stream->step_entry; - stream->step_entry = gst_avi_demux_index_prev (avi, stream, - stream->stop_entry, TRUE); - - GST_DEBUG_OBJECT (avi, - "reverse playback jump: start %u, step %u, stop %u", - stream->start_entry, stream->step_entry, stream->stop_entry); + } - /* and start from the previous keyframe now */ - new_entry = stream->step_entry; - } else { - /* EOS */ - GST_DEBUG_OBJECT (avi, "forward reached stop %u", stream->stop_entry); - goto eos; + if (new_entry != old_entry) { + stream->current_entry = new_entry; + stream->current_total = stream->index[new_entry].total; + + if (new_entry == old_entry + 1) { + GST_DEBUG_OBJECT (avi, "moved forwards from %u to %u", + old_entry, new_entry); + /* we simply moved one step forwards, reuse current info */ + stream->current_timestamp = stream->current_ts_end; + stream->current_offset = stream->current_offset_end; + gst_avi_demux_get_buffer_info (avi, stream, new_entry, + NULL, &stream->current_ts_end, NULL, &stream->current_offset_end); + } else { + /* we moved DISCONT, full update */ + gst_avi_demux_get_buffer_info (avi, stream, new_entry, + &stream->current_timestamp, &stream->current_ts_end, + &stream->current_offset, &stream->current_offset_end); + /* and MARK discont for this stream */ + stream->discont = TRUE; + GST_DEBUG_OBJECT (avi, "Moved from %u to %u, ts %" GST_TIME_FORMAT + ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT + ", off_end %" G_GUINT64_FORMAT, old_entry, new_entry, + GST_TIME_ARGS (stream->current_timestamp), + GST_TIME_ARGS (stream->current_ts_end), stream->current_offset, + stream->current_offset_end); + } } - } - if (new_entry != old_entry) { - stream->current_entry = new_entry; - stream->current_total = stream->index[new_entry].total; + if (!skip_feeding) + return ret; + + } while (skip_count < 17); - if (new_entry == old_entry + 1) { - GST_DEBUG_OBJECT (avi, "moved forwards from %u to %u", - old_entry, new_entry); - /* we simply moved one step forwards, reuse current info */ - stream->current_timestamp = stream->current_ts_end; - stream->current_offset = stream->current_offset_end; - gst_avi_demux_get_buffer_info (avi, stream, new_entry, - NULL, &stream->current_ts_end, NULL, &stream->current_offset_end); - } else { - /* we moved DISCONT, full update */ - gst_avi_demux_get_buffer_info (avi, stream, new_entry, - &stream->current_timestamp, &stream->current_ts_end, - &stream->current_offset, &stream->current_offset_end); - /* and MARK discont for this stream */ - stream->discont = TRUE; - GST_DEBUG_OBJECT (avi, "Moved from %u to %u, ts %" GST_TIME_FORMAT - ", ts_end %" GST_TIME_FORMAT ", off %" G_GUINT64_FORMAT - ", off_end %" G_GUINT64_FORMAT, old_entry, new_entry, - GST_TIME_ARGS (stream->current_timestamp), - GST_TIME_ARGS (stream->current_ts_end), stream->current_offset, - stream->current_offset_end); - } - } return ret; /* ERROR */ eos: { - GST_DEBUG_OBJECT (avi, "we are EOS"); + GST_DEBUG_OBJECT (avi, "we are EOS : %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->strh->type)); + /* setting current_timestamp to -1 marks EOS */ stream->current_timestamp = -1; + if (stream->pad && + GST_CLOCK_TIME_IS_VALID (stream->idx_duration) && + GST_CLOCK_TIME_IS_VALID (avi->segment.duration) + && stream->strh->type == GST_RIFF_FCC_auds + && (avi->segment.start + GST_SECOND >= stream->idx_duration)) { + guint64 start = stream->idx_duration; + guint64 stop = avi->segment.duration; + GstEvent *gap = NULL; + gap = gst_event_new_gap (start, (stop - start)); + gst_pad_push_event (stream->pad, gap); + GST_INFO_OBJECT (avi, + "push gap event stream %d with time :%" + GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT, stream->num, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + } return GST_FLOW_EOS; } } @@ -5214,6 +5579,15 @@ gst_avi_demux_align_buffer (GstAviDemux * demux, return buffer; } +static inline int +read_1digit (const unsigned char *buf) +{ + if ('0' <= buf[0] && buf[0] <= '9') + return buf[0] - '0'; + else + return 0; +} + static GstFlowReturn gst_avi_demux_loop_data (GstAviDemux * avi) { @@ -5221,7 +5595,9 @@ gst_avi_demux_loop_data (GstAviDemux * avi) guint stream_num; GstAviStream *stream; gboolean processed = FALSE; - GstBuffer *buf; + GstBuffer *buf, *tag_buf; + GstMapInfo map; + guint32 tag = 0; guint64 offset, size; GstClockTime timestamp, duration; guint64 out_offset, out_offset_end; @@ -5259,6 +5635,30 @@ gst_avi_demux_loop_data (GstAviDemux * avi) size = entry->size; keyframe = ENTRY_IS_KEYFRAME (entry); + if (stream->strh->type == GST_RIFF_FCC_vids) { + if (stream->isHeavyIndex && (abs (avi->segment.rate) > 8)) + keyframe = ENTRY_IS_MAJOR_KEYFRAME (entry); + } + + /* for keyframe trick mode */ + if (avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + if (stream->strh->type == GST_RIFF_FCC_vids && !keyframe) { + GST_DEBUG_OBJECT (avi, + "skipping entry caused by keyframe trick mode and video entry :%d or keyframe:%d", + stream->current_entry, keyframe); + goto next; + } else if (stream->strh->type == GST_RIFF_FCC_auds) { + GST_LOG_OBJECT (avi, "skipping audio entry %d for keyframe trick mode", + stream->current_entry); + goto next; + } + } else if ((avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) && + (stream->strh->type == GST_RIFF_FCC_auds)) { + GST_LOG_OBJECT (avi, "skipping audio entry %d for no audio trick mode", + stream->current_entry); + goto next; + } + /* skip empty entries */ if (size == 0) { GST_DEBUG_OBJECT (avi, "Skipping entry %u (%" G_GUINT64_FORMAT ", %p)", @@ -5286,7 +5686,32 @@ gst_avi_demux_loop_data (GstAviDemux * avi) /* pull in the data */ buf = NULL; + tag_buf = NULL; + + ret = + gst_pad_pull_range (avi->sinkpad, + offset - GST_AVI_CHUNK_HEADER_LENGTH, GST_AVI_FOURCC_LENGTH, &tag_buf); + + if (ret != GST_FLOW_OK) + goto pull_failed; + + gst_buffer_map (tag_buf, &map, GST_MAP_READ); + tag = GST_READ_UINT32_LE (map.data); + + if (G_UNLIKELY (tag == 0)) { + GST_WARNING_OBJECT (avi, "Invalid tag %d (%" GST_FOURCC_FORMAT ")", + stream_num, GST_FOURCC_ARGS (tag)); + gst_buffer_unmap (tag_buf, &map); + gst_buffer_unref (tag_buf); + goto next; + } + + gst_buffer_unmap (tag_buf, &map); + gst_buffer_unref (tag_buf); + + /* pull in the data */ ret = gst_pad_pull_range (avi->sinkpad, offset, size, &buf); + if (ret != GST_FLOW_OK) goto pull_failed; @@ -5303,7 +5728,9 @@ gst_avi_demux_loop_data (GstAviDemux * avi) GST_BUFFER_PTS (buf) = timestamp; } else { GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); - GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; + /* 20130721 by zeldasky our vdec needs the pts, despite of not keyframe */ + //GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buf) = timestamp; } GST_BUFFER_DTS (buf) = timestamp; @@ -5320,9 +5747,6 @@ gst_avi_demux_loop_data (GstAviDemux * avi) } else { GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); } -#if 0 - gst_avi_demux_add_assoc (avi, stream, timestamp, offset, keyframe); -#endif /* update current position in the segment */ avi->segment.position = timestamp; @@ -5434,6 +5858,13 @@ gst_avi_demux_stream_data (GstAviDemux * avi) GST_DEBUG ("Trying chunk (%" GST_FOURCC_FORMAT "), size %d", GST_FOURCC_ARGS (tag), size); + if (G_UNLIKELY (tag == 0)) { + GST_WARNING ("Invalid tag %d (%" GST_FOURCC_FORMAT ")", + stream_nr, GST_FOURCC_ARGS (tag)); + GST_ELEMENT_ERROR (avi, STREAM, FAILED, (NULL), ("Illegal stream data")); + return GST_FLOW_ERROR; + } + if (G_LIKELY ((tag & 0xff) >= '0' && (tag & 0xff) <= '9' && ((tag >> 8) & 0xff) >= '0' && ((tag >> 8) & 0xff) <= '9')) { GST_LOG ("Chunk ok"); @@ -5460,6 +5891,7 @@ gst_avi_demux_stream_data (GstAviDemux * avi) /* accept 0 size buffer here */ avi->abort_buffering = FALSE; GST_DEBUG (" skipping %d bytes for now", size); + avi->offset += 8 + GST_ROUND_UP_2 (size); gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); } return GST_FLOW_OK; @@ -5479,6 +5911,9 @@ gst_avi_demux_stream_data (GstAviDemux * avi) avi->abort_buffering = FALSE; GST_DEBUG (" skipping %d bytes for now", size); gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); + + if (size != 0) + continue; } return GST_FLOW_OK; } else { @@ -5516,11 +5951,8 @@ gst_avi_demux_stream_data (GstAviDemux * avi) GstAviStream *stream; GstClockTime next_ts = 0; GstBuffer *buf = NULL; -#if 0 - guint64 offset; -#endif gboolean saw_desired_kf = stream_nr != avi->main_stream - || avi->offset >= avi->seek_kf_offset; + || avi->offset + size >= avi->seek_kf_offset; if (stream_nr == avi->main_stream && avi->offset == avi->seek_kf_offset) { GST_DEBUG_OBJECT (avi, "Desired keyframe reached"); @@ -5543,9 +5975,6 @@ gst_avi_demux_stream_data (GstAviDemux * avi) gst_adapter_flush (avi->adapter, 8 + GST_ROUND_UP_2 (size)); } -#if 0 - offset = avi->offset; -#endif avi->offset += 8 + GST_ROUND_UP_2 (size); stream = &avi->stream[stream_nr]; @@ -5567,10 +5996,6 @@ gst_avi_demux_stream_data (GstAviDemux * avi) gst_pad_query_position (stream->pad, GST_FORMAT_TIME, (gint64 *) & next_ts); -#if 0 - gst_avi_demux_add_assoc (avi, stream, next_ts, offset, FALSE); -#endif - /* increment our positions */ stream->current_entry++; /* as in pull mode, 'total' is either bytes (CBR) or frames (VBR) */ @@ -5599,7 +6024,7 @@ gst_avi_demux_stream_data (GstAviDemux * avi) (gint64 *) & dur_ts); GST_BUFFER_DTS (buf) = next_ts; - GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buf) = next_ts; GST_BUFFER_DURATION (buf) = dur_ts - next_ts; if (stream->strh->type == GST_RIFF_FCC_vids) { GST_BUFFER_OFFSET (buf) = stream->current_entry - 1; @@ -5609,38 +6034,132 @@ gst_avi_demux_stream_data (GstAviDemux * avi) GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE; } - GST_DEBUG_OBJECT (avi, - "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" - GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT - " and size %d over pad %s", GST_TIME_ARGS (next_ts), - GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), - GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad)); - - /* mark discont when pending */ - if (G_UNLIKELY (stream->discont)) { - GST_DEBUG_OBJECT (avi, "Setting DISCONT"); - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); - stream->discont = FALSE; - } else { - GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); +#ifdef AVI_PUSHMODE_TRICK + if ((avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && avi->segment_event_recvd && ((avi->num_streams == 1 + && avi->pushed_Iframe) || (avi->pushed_Iframe + && avi->all_audio_pushed))) { + avi->pushed_Iframe = FALSE; + avi->pushed_Audio = FALSE; + avi->all_audio_pushed = FALSE; + /*We should unref all peeked buffers before moving to next I frame */ + gst_buffer_unref (buf); + avi->prev_video_position = avi->video_position; + gst_avi_demux_handle_trick (avi, avi->video_position); + goto exit; } +#endif - if (stream->alignment > 1) - buf = gst_avi_demux_align_buffer (avi, buf, stream->alignment); - res = gst_pad_push (stream->pad, buf); - buf = NULL; +#ifdef AVI_PUSHMODE_TRICK + if (((avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && avi->all_audio_pushed == FALSE) + && avi->segment_event_recvd) { + if (!strcmp ("audio_0", GST_PAD_NAME (stream->pad)) + && avi->pushed_Iframe == TRUE) { + avi->pushed_Audio = TRUE; + + GST_DEBUG_OBJECT (avi, + "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" + GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT + " and size %d over pad %s", GST_TIME_ARGS (next_ts), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad)); + + /* mark discont when pending */ + if (G_UNLIKELY (stream->discont)) { + GST_DEBUG_OBJECT (avi, "Setting DISCONT"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + + if (stream->alignment > 1) + buf = gst_avi_demux_align_buffer (avi, buf, stream->alignment); + res = gst_pad_push (stream->pad, buf); + buf = NULL; + + /* combine flows */ + res = gst_avi_demux_combine_flows (avi, stream, res); + if (G_UNLIKELY (res != GST_FLOW_OK)) { + GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); + return res; + } + } else if (!strcmp ("video_0", GST_PAD_NAME (stream->pad))) { + + if (avi->pushed_Audio) { + avi->all_audio_pushed = TRUE; + if (avi->segment.rate < 0.0 + && next_ts <= avi->prev_video_position) + avi->all_audio_pushed = FALSE; + } + if (avi->all_audio_pushed == FALSE && avi->pushed_Iframe == FALSE) { + avi->pushed_Iframe = TRUE; + avi->video_position = avi->segment.position; + GST_DEBUG_OBJECT (avi, + "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" + GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT + " and size %d over pad %s", GST_TIME_ARGS (next_ts), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad)); + /* mark discont when pending */ + if (G_UNLIKELY (stream->discont)) { + GST_DEBUG_OBJECT (avi, "Setting DISCONT"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + + if (stream->alignment > 1) + buf = + gst_avi_demux_align_buffer (avi, buf, stream->alignment); + res = gst_pad_push (stream->pad, buf); + buf = NULL; + + /* combine flows */ + res = gst_avi_demux_combine_flows (avi, stream, res); + if (G_UNLIKELY (res != GST_FLOW_OK)) { + GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); + return res; + } + } else { + gst_buffer_unref (buf); + } + } + + } else if (avi->segment_event_recvd && avi->segment.rate > 0.0) { - /* combine flows */ - res = gst_avi_demux_combine_flows (avi, stream, res); - if (G_UNLIKELY (res != GST_FLOW_OK)) { - GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); - return res; +#endif + GST_DEBUG_OBJECT (avi, + "Pushing buffer with time=%" GST_TIME_FORMAT ", duration %" + GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT + " and size %d over pad %s", GST_TIME_ARGS (next_ts), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_BUFFER_OFFSET (buf), size, GST_PAD_NAME (stream->pad)); + /* mark discont when pending */ + if (G_UNLIKELY (stream->discont) && avi->rate_changed) { + GST_DEBUG_OBJECT (avi, "Setting DISCONT"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + res = gst_pad_push (stream->pad, buf); + buf = NULL; + + /* combine flows */ + res = gst_avi_demux_combine_flows (avi, stream, res); + if (G_UNLIKELY (res != GST_FLOW_OK)) { + GST_DEBUG ("Push failed; %s", gst_flow_get_name (res)); + return res; + } } } } } } - +exit: return res; } @@ -5709,9 +6228,17 @@ gst_avi_demux_loop (GstPad * pad) avi->state = GST_AVI_DEMUX_MOVI; break; case GST_AVI_DEMUX_MOVI: + if (avi->isNotSupportCodec) + goto not_support; + if (G_UNLIKELY (avi->seg_event)) { gst_avi_demux_push_event (avi, avi->seg_event); avi->seg_event = NULL; + + /* Push gap event to downstream element if we do not need audio */ + if (avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS || + avi->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) + gst_avi_demux_push_gap_audio (avi); } if (G_UNLIKELY (avi->got_tags)) { push_tag_lists (avi); @@ -5792,6 +6319,11 @@ pause:{ (NULL), ("got eos but no streams (yet)")); } } + return; + } +not_support: + { + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("not support file type")); } } @@ -5829,6 +6361,12 @@ gst_avi_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) } break; case GST_AVI_DEMUX_MOVI: + if (!avi->isInterleaved && avi->num_streams > 1) + goto not_support; + + if (avi->isNotSupportCodec) + goto not_support; + if (G_UNLIKELY (avi->seg_event)) { gst_avi_demux_push_event (avi, avi->seg_event); avi->seg_event = NULL; @@ -5908,6 +6446,11 @@ gst_avi_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("unhandled buffer size")); return GST_FLOW_ERROR; } +not_support: + { + GST_ELEMENT_ERROR (avi, STREAM, DEMUX, (NULL), ("not support file type")); + return GST_FLOW_NOT_SUPPORTED; + } } static gboolean @@ -5973,51 +6516,27 @@ gst_avi_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, return res; } -#if 0 -static void -gst_avi_demux_set_index (GstElement * element, GstIndex * index) -{ - GstAviDemux *avi = GST_AVI_DEMUX (element); - - GST_OBJECT_LOCK (avi); - if (avi->element_index) - gst_object_unref (avi->element_index); - if (index) { - avi->element_index = gst_object_ref (index); - } else { - avi->element_index = NULL; - } - GST_OBJECT_UNLOCK (avi); - /* object lock might be taken again */ - if (index) - gst_index_get_writer_id (index, GST_OBJECT_CAST (element), &avi->index_id); - GST_DEBUG_OBJECT (avi, "Set index %" GST_PTR_FORMAT, avi->element_index); -} - -static GstIndex * -gst_avi_demux_get_index (GstElement * element) -{ - GstIndex *result = NULL; - GstAviDemux *avi = GST_AVI_DEMUX (element); - - GST_OBJECT_LOCK (avi); - if (avi->element_index) - result = gst_object_ref (avi->element_index); - GST_OBJECT_UNLOCK (avi); - - GST_DEBUG_OBJECT (avi, "Returning index %" GST_PTR_FORMAT, result); - - return result; -} -#endif - static GstStateChangeReturn gst_avi_demux_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; + GstSmartPropertiesReturn property_ret; GstAviDemux *avi = GST_AVI_DEMUX (element); - + GstAviStream *stream = NULL; + guint num; switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* For thumbnail extraction */ + property_ret = gst_element_get_smart_properties (GST_ELEMENT_CAST (avi), + "thumbnail-mode", &avi->thumbnail_mode, NULL); + + if (property_ret != GST_SMART_PROPERTIES_OK) { + GST_WARNING_OBJECT (avi, "faield to get thumbnail-mode property"); + avi->thumbnail_mode = FALSE; + } + + GST_INFO_OBJECT (avi, "thumbnail-mode = %d", avi->thumbnail_mode); + break; case GST_STATE_CHANGE_READY_TO_PAUSED: avi->streaming = FALSE; gst_segment_init (&avi->segment, GST_FORMAT_TIME); @@ -6035,6 +6554,34 @@ gst_avi_demux_change_state (GstElement * element, GstStateChange transition) avi->have_index = FALSE; gst_avi_demux_reset (avi); break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + for (num = 0; num < avi->num_streams; num++) { + stream = &avi->stream[num]; + if (stream->pad && stream->strh->type == GST_RIFF_FCC_auds && + stream->current_timestamp == -1) { + if (GST_CLOCK_TIME_IS_VALID (stream->idx_duration) && + GST_CLOCK_TIME_IS_VALID (avi->segment.duration) && + stream->stop_entry - 1 <= stream->current_entry) { + + guint64 start = stream->idx_duration; + guint64 stop = avi->segment.duration; + guint64 gap_time; + GstEvent *gap = NULL; + + gap_time = start > stop ? (start - stop) : (stop - start); + + if (gap_time > 10 * GST_SECOND) { + gap = gst_event_new_gap (start, (stop - start)); + gst_pad_push_event (stream->pad, gap); + + GST_INFO_OBJECT (avi, + "push gap event stream %d with start time :%" + GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT, stream->num, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop - start)); + } + } + } + } default: break; } @@ -6042,3 +6589,88 @@ gst_avi_demux_change_state (GstElement * element, GstStateChange transition) done: return ret; } + +static void +gst_avi_demux_parse_mpegaudio (GstAviDemux * avi) +{ + GstAviStream *stream; + GstMapInfo map; + const guint8 *dataHeader; + guint32 mp3Header = 0; + guint count; + + /*For check mp3 files audio VBR/CBR - zeldasky 2012-04-05 */ + /*MPEG version1 Layer3 or MPEG version2 Layer3 */ + for (count = 0; count < avi->num_streams; count++) { + stream = &avi->stream[count]; + + if (stream->strh->type == GST_RIFF_FCC_auds && + stream->strf.auds->format == GST_MP3_FOURCC) { + + if (avi->streaming) { + if (gst_adapter_available (avi->adapter) < 24) + GST_ERROR_OBJECT (avi, + "failed getting an enough data while looking for MP3 Header"); + + dataHeader = gst_adapter_map (avi->adapter, 24); + mp3Header = GST_READ_UINT32_BE (dataHeader + 20); + gst_adapter_unmap (avi->adapter); + } else { + GstBuffer *bufParse = NULL; + if (gst_pad_pull_range (avi->sinkpad, avi->offset + 20, 4, + &bufParse) != GST_FLOW_OK || gst_buffer_get_size (bufParse) < 4) + GST_ERROR_OBJECT (avi, + "failed getting an enough data while looking for MP3 Header"); + + if (!bufParse) + continue; + + gst_buffer_map (bufParse, &map, GST_MAP_READ); + + dataHeader = map.data; + mp3Header = GST_READ_UINT32_BE (dataHeader); + + gst_buffer_unmap (bufParse, &map); + gst_buffer_unref (bufParse); + } + + if (GST_GET_MP3_FRAMESYNC (mp3Header) == GST_MP3_FRAMESYNC) { + if ((GST_GET_MP3_VERSION (mp3Header) == GST_MP3_VERSION2 + && stream->strh->samplesize == 576) + || (GST_GET_MP3_VERSION (mp3Header) == GST_MP3_VERSION1 + && stream->strh->samplesize == 1152)) + stream->is_vbr = TRUE; + } + } + } +} + +static gboolean +gst_avi_demux_check_divx (guint32 fourcc) +{ + gboolean res = FALSE; + + switch (fourcc) { + case GST_MAKE_FOURCC ('D', 'X', '5', '0'): + case GST_MAKE_FOURCC ('d', 'i', 'v', 'x'): + case GST_MAKE_FOURCC ('D', 'I', 'V', 'X'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '3'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '3'): + case GST_MAKE_FOURCC ('D', 'V', 'X', '3'): + case GST_MAKE_FOURCC ('d', 'v', 'x', '3'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '4'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '4'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '5'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '5'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '6'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '6'): + res = TRUE; + break; + + default: + break; + } + + return res; + +} diff --git a/gst/avi/gstavidemux.h b/gst/avi/gstavidemux.h index 22e46a2edc..b23dfec113 100644 --- a/gst/avi/gstavidemux.h +++ b/gst/avi/gstavidemux.h @@ -30,7 +30,6 @@ #include G_BEGIN_DECLS - #define GST_TYPE_AVI_DEMUX \ (gst_avi_demux_get_type ()) #define GST_AVI_DEMUX(obj) \ @@ -41,96 +40,104 @@ G_BEGIN_DECLS (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_AVI_DEMUX)) #define GST_IS_AVI_DEMUX_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_AVI_DEMUX)) - -#define GST_AVI_DEMUX_MAX_STREAMS 16 - +#define GST_AVI_DEMUX_MAX_STREAMS 32 #define CHUNKID_TO_STREAMNR(chunkid) \ ((((chunkid) & 0xff) - '0') * 10 + \ (((chunkid) >> 8) & 0xff) - '0') - - +#define AVI_PUSHMODE_TRICK +#ifdef AVI_PUSHMODE_TRICK +#define TIME_ADJUST 100000000 +#endif /* new index entries 24 bytes */ -typedef struct { - guint32 flags; - guint32 size; /* bytes of the data */ - guint64 offset; /* data offset in file */ - guint64 total; /* total bytes before */ + typedef struct +{ + guint32 flags; + guint32 size; /* bytes of the data */ + guint64 offset; /* data offset in file */ + guint64 total; /* total bytes before */ + GstClockTime time; /* in nanoseconds */ } GstAviIndexEntry; -typedef struct { +typedef struct +{ /* index of this streamcontext */ - guint num; + guint num; - /* pad*/ - GstPad *pad; - gboolean exposed; + /* pad */ + GstPad *pad; + gboolean exposed; /* stream info and headers */ gst_riff_strh *strh; - union { + union + { gst_riff_strf_vids *vids; gst_riff_strf_auds *auds; gst_riff_strf_iavs *iavs; - gpointer data; + gpointer data; } strf; - GstBuffer *extradata, *initdata; - GstBuffer *rgb8_palette; - gchar *name; + GstBuffer *extradata, *initdata; + GstBuffer *rgb8_palette; + gchar *name; /* the start/step/stop entries */ - guint start_entry; - guint step_entry; - guint stop_entry; + guint start_entry; + guint step_entry; + guint stop_entry; /* current index entry */ - guint current_entry; + guint current_entry; /* position (byte, frame, time) for current_entry */ - guint current_total; - GstClockTime current_timestamp; - GstClockTime current_ts_end; - guint64 current_offset; - guint64 current_offset_end; + guint current_total; + GstClockTime current_timestamp; + GstClockTime current_ts_end; + guint64 current_offset; + guint64 current_offset_end; gboolean discont; /* stream length */ - guint64 total_bytes; - guint32 total_blocks; - guint n_keyframes; + guint64 total_bytes; + guint32 total_blocks; + guint n_keyframes; /* stream length according to index */ - GstClockTime idx_duration; + GstClockTime idx_duration; /* stream length according to header */ - GstClockTime hdr_duration; + GstClockTime hdr_duration; /* stream length based on header/index */ - GstClockTime duration; + GstClockTime duration; /* VBR indicator */ - gboolean is_vbr; + gboolean is_vbr; /* openDML support (for files >4GB) */ - gboolean superindex; - guint64 *indexes; + gboolean superindex; + guint64 *indexes; /* new indexes */ - GstAviIndexEntry *index; /* array with index entries */ - guint idx_n; /* number of entries */ - guint idx_max; /* max allocated size of entries */ + GstAviIndexEntry *index; /* array with index entries */ + guint idx_n; /* number of entries */ + guint idx_max; /* max allocated size of entries */ - GstTagList *taglist; + GstTagList *taglist; - gint index_id; + gint index_id; gboolean is_raw; gsize alignment; + + gboolean isHeavyIndex; } GstAviStream; -typedef enum { +typedef enum +{ GST_AVI_DEMUX_START, GST_AVI_DEMUX_HEADER, GST_AVI_DEMUX_MOVI, GST_AVI_DEMUX_SEEK, } GstAviDemuxState; -typedef enum { +typedef enum +{ GST_AVI_DEMUX_HEADER_TAG_LIST, GST_AVI_DEMUX_HEADER_AVIH, GST_AVI_DEMUX_HEADER_ELEMENTS, @@ -139,32 +146,33 @@ typedef enum { GST_AVI_DEMUX_HEADER_DATA } GstAviDemuxHeaderState; -typedef struct _GstAviDemux { - GstElement parent; +typedef struct _GstAviDemux +{ + GstElement parent; /* pads */ - GstPad *sinkpad; + GstPad *sinkpad; /* AVI decoding state */ GstAviDemuxState state; GstAviDemuxHeaderState header_state; - guint64 offset; - gboolean abort_buffering; + guint64 offset; + gboolean abort_buffering; /* when we loaded the indexes */ - gboolean have_index; + gboolean have_index; /* index offset in the file */ - guint64 index_offset; + guint64 index_offset; /* streams */ - GstAviStream stream[GST_AVI_DEMUX_MAX_STREAMS]; - guint num_streams; - guint num_v_streams; - guint num_a_streams; - guint num_t_streams; /* subtitle text streams */ + GstAviStream stream[GST_AVI_DEMUX_MAX_STREAMS]; + guint num_streams; + guint num_v_streams; + guint num_a_streams; + guint num_t_streams; /* subtitle text streams */ guint num_sp_streams; /* subpicture streams */ - guint main_stream; /* used for seeking */ + guint main_stream; /* used for seeking */ GstFlowCombiner *flowcombiner; @@ -172,50 +180,62 @@ typedef struct _GstAviDemux { guint group_id; /* for streaming mode */ - gboolean streaming; - gboolean have_eos; - GstAdapter *adapter; - guint todrop; + gboolean streaming; + gboolean have_eos; + GstAdapter *adapter; + guint todrop; /* some stream info for length */ gst_riff_avih *avih; - GstClockTime duration; + GstClockTime duration; /* segment in TIME */ GstSegment segment; guint32 segment_seqnum; /* pending tags/events */ - GstEvent *seg_event; - GstTagList *globaltags; - gboolean got_tags; - -#if 0 - /* gst index support */ - GstIndex *element_index; - gint index_id; + GstEvent *seg_event; + GstTagList *globaltags; + gboolean got_tags; + +#ifdef AVI_PUSHMODE_TRICK + gdouble demux_rate; + gboolean pushed_Iframe; + gboolean pushed_Audio; + gboolean all_audio_pushed; + gboolean segment_event_recvd; + gboolean rate_changed; + guint64 video_position; + guint64 prev_video_position; + guint prev_keyframe_index; + gboolean is_flushing; #endif - gboolean seekable; + gboolean seekable; - guint64 first_movi_offset; - guint64 idx1_offset; /* offset in file of list/chunk after movi */ - GstEvent *seek_event; + guint64 first_movi_offset; + guint64 idx1_offset; /* offset in file of list/chunk after movi */ + GstEvent *seek_event; - gboolean building_index; - guint odml_stream; - guint odml_subidx; - guint64 *odml_subidxs; + gboolean building_index; + guint odml_stream; + guint odml_subidx; + guint64 *odml_subidxs; - guint64 seek_kf_offset; /* offset of the keyframe to which we want to seek */ + guint64 seek_kf_offset; /* offset of the keyframe to which we want to seek */ + + /*thumbnail flag */ + gboolean thumbnail_mode; + gboolean isInterleaved; + gboolean isNotSupportCodec; } GstAviDemux; -typedef struct _GstAviDemuxClass { +typedef struct _GstAviDemuxClass +{ GstElementClass parent_class; } GstAviDemuxClass; -GType gst_avi_demux_get_type (void); +GType gst_avi_demux_get_type (void); G_END_DECLS - #endif /* __GST_AVI_DEMUX_H__ */ diff --git a/gst/deinterlace/gstdeinterlacemethod.c b/gst/deinterlace/gstdeinterlacemethod.c index 93e77e7f09..ed4f1b4280 100644 --- a/gst/deinterlace/gstdeinterlacemethod.c +++ b/gst/deinterlace/gstdeinterlacemethod.c @@ -66,6 +66,7 @@ gst_deinterlace_method_supported_impl (GstDeinterlaceMethodClass * klass, case GST_VIDEO_FORMAT_Y42B: return (klass->deinterlace_frame_y42b != NULL); case GST_VIDEO_FORMAT_Y41B: + case GST_VIDEO_FORMAT_YUV9: return (klass->deinterlace_frame_y41b != NULL); case GST_VIDEO_FORMAT_AYUV: return (klass->deinterlace_frame_ayuv != NULL); @@ -138,6 +139,7 @@ gst_deinterlace_method_setup_impl (GstDeinterlaceMethod * self, self->deinterlace_frame = klass->deinterlace_frame_y42b; break; case GST_VIDEO_FORMAT_Y41B: + case GST_VIDEO_FORMAT_YUV9: self->deinterlace_frame = klass->deinterlace_frame_y41b; break; case GST_VIDEO_FORMAT_AYUV: @@ -277,6 +279,7 @@ gst_deinterlace_simple_method_supported (GstDeinterlaceMethodClass * mklass, case GST_VIDEO_FORMAT_Y444: case GST_VIDEO_FORMAT_Y42B: case GST_VIDEO_FORMAT_Y41B: + case GST_VIDEO_FORMAT_YUV9: return (klass->interpolate_scanline_planar_y != NULL && klass->copy_scanline_planar_y != NULL && klass->interpolate_scanline_planar_u != NULL @@ -678,6 +681,7 @@ gst_deinterlace_simple_method_setup (GstDeinterlaceMethod * method, case GST_VIDEO_FORMAT_Y444: case GST_VIDEO_FORMAT_Y42B: case GST_VIDEO_FORMAT_Y41B: + case GST_VIDEO_FORMAT_YUV9: self->interpolate_scanline_planar[0] = klass->interpolate_scanline_planar_y; self->copy_scanline_planar[0] = klass->copy_scanline_planar_y; diff --git a/gst/flv/gstflvdemux.c b/gst/flv/gstflvdemux.c index 2a0fa45aed..9599a59b54 100644 --- a/gst/flv/gstflvdemux.c +++ b/gst/flv/gstflvdemux.c @@ -84,6 +84,11 @@ static GstStaticPadTemplate video_src_template = "video/x-vp6-flash; " "video/x-vp6-alpha; " "video/x-h264, stream-format=avc;") ); +enum +{ + PROP_INIT, + PROP_THUMBNAIL_MODE +}; GST_DEBUG_CATEGORY_STATIC (flvdemux_debug); #define GST_CAT_DEFAULT flvdemux_debug @@ -116,6 +121,11 @@ static GstIndex *gst_flv_demux_get_index (GstElement * element); static void gst_flv_demux_push_tags (GstFlvDemux * demux); +static GstFlowReturn gst_flv_demux_seek_to_keyframe (GstFlvDemux * demux, + gboolean prev); +static gboolean gst_flv_demux_send_seek_push (GstFlvDemux * demux, gdouble rate, + gint64 seek_offset); + static void gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts, guint64 pos, gboolean keyframe) @@ -497,13 +507,25 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader, if (demux->times) { g_array_free (demux->times, TRUE); } - demux->times = g_array_new (FALSE, TRUE, sizeof (gdouble)); + if (nb_elems < 2) { /*when only one element is exist for time and fileposition, we cannot seek */ + demux->invalid_keyframe = TRUE; + demux->times = NULL; + } else + demux->times = g_array_new (FALSE, TRUE, sizeof (gdouble)); + } else if (!strcmp (tag_name, "filepositions")) { if (demux->filepositions) { g_array_free (demux->filepositions, TRUE); } - demux->filepositions = g_array_new (FALSE, TRUE, sizeof (gdouble)); + if (nb_elems < 2) { /*when only one element is exist for time or fileposition, we cannot seek */ + demux->invalid_keyframe = TRUE; + demux->filepositions = NULL; + } else + demux->filepositions = g_array_new (FALSE, TRUE, sizeof (gdouble)); + } + if (nb_elems <= 1) /*when only one element is exist for time or fileposition, we cannot seek */ + break; while (nb_elems--) { guint8 elem_type = 0; @@ -727,10 +749,12 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag, gchar *stream_id; switch (codec_tag) { +#if 0 case 1: caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, "swf", NULL); break; +#endif case 2: case 14: caps = gst_caps_new_simple ("audio/mpeg", @@ -855,8 +879,13 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag, adjusted_rate = 16000; break; } + case 1: /* audio/x-adpcm is unsupported */ default: GST_WARNING_OBJECT (demux, "unsupported audio codec tag %u", codec_tag); + demux->unsupported_codec = TRUE; + + GST_ELEMENT_WARNING (GST_ELEMENT_CAST (demux), STREAM, CODEC_NOT_FOUND, + ("This audio is not supported"), ("This audio is not supported")); break; } @@ -885,6 +914,16 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag, gst_pad_push_event (demux->audio_pad, event); g_free (stream_id); } + + /* custom caps */ + gst_caps_set_simple (caps, "seekable", G_TYPE_BOOLEAN, + demux->upstream_seekable, NULL); + + if (demux->random_access) + gst_caps_set_simple (caps, "trickable", G_TYPE_BOOLEAN, TRUE, NULL); + else + gst_caps_set_simple (caps, "trickable", G_TYPE_BOOLEAN, FALSE, NULL); + if (!old_caps || !gst_caps_is_equal (old_caps, caps)) ret = gst_pad_set_caps (demux->audio_pad, caps); else @@ -1057,8 +1096,14 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer) flags = GST_READ_UINT8 (data + 7); /* Silently skip buffers with no data */ - if (map.size == 11) + if (map.size == 11) { + GST_WARNING_OBJECT (demux, "no audio data. Send not supported audio msg"); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_application (GST_OBJECT_CAST (demux), + gst_structure_new ("GstMessageAudio", "AUDIO", G_TYPE_BOOLEAN, + FALSE, NULL))); goto beach; + } /* Channels */ if (flags & 0x01) { @@ -1208,6 +1253,10 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer) goto beach; } + if (demux->high_speed_trick && demux->audio_push_done) { + goto beach; + } + /* Create buffer from pad */ outbuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY, 7 + codec_data, demux->tag_data_size - codec_data); @@ -1284,6 +1333,10 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer) gst_element_no_more_pads (GST_ELEMENT_CAST (demux)); demux->no_more_pads = TRUE; } + if (GST_BUFFER_TIMESTAMP (outbuf) > demux->segment.stop) { + gst_buffer_unref (outbuf); + goto beach; + } /* Push downstream */ ret = gst_pad_push (demux->audio_pad, outbuf); @@ -1322,13 +1375,15 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag) case 2: caps = gst_caps_new_simple ("video/x-flash-video", "flvversion", G_TYPE_INT, - 1, NULL); + 1, "format", G_TYPE_STRING, "FLV1", NULL); break; case 3: caps = gst_caps_new_empty_simple ("video/x-flash-screen"); break; case 4: - caps = gst_caps_new_empty_simple ("video/x-vp6-flash"); + caps = + gst_caps_new_simple ("video/x-vp6-flash", "format", G_TYPE_STRING, + "vp6f", NULL); break; case 5: caps = gst_caps_new_empty_simple ("video/x-vp6-alpha"); @@ -1341,7 +1396,7 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag) } caps = gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING, - "avc", NULL); + "avc", "format", G_TYPE_STRING, "avc1", NULL); break; /* The following two are non-standard but apparently used, see in ffmpeg * https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/flvdec.c;h=2bf1e059e1cbeeb79e4af9542da23f4560e1cf59;hb=b18d6c58000beed872d6bb1fe7d0fbe75ae26aef#l254 @@ -1357,6 +1412,9 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag) break; default: GST_WARNING_OBJECT (demux, "unsupported video codec tag %u", codec_tag); + GST_ELEMENT_WARNING (GST_ELEMENT_CAST (demux), STREAM, CODEC_NOT_FOUND, + ("This video is not supported"), ("This video is not supported")); + break; } if (G_UNLIKELY (!caps)) { @@ -1406,6 +1464,16 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag) gst_pad_push_event (demux->video_pad, event); } + /* custom caps */ + gst_caps_set_simple (caps, "container", G_TYPE_STRING, "flv", NULL); + gst_caps_set_simple (caps, "seekable", G_TYPE_BOOLEAN, + demux->upstream_seekable, NULL); + + if (demux->random_access) + gst_caps_set_simple (caps, "trickable", G_TYPE_BOOLEAN, TRUE, NULL); + else + gst_caps_set_simple (caps, "trickable", G_TYPE_BOOLEAN, FALSE, NULL); + if (!old_caps || !gst_caps_is_equal (old_caps, caps)) ret = gst_pad_set_caps (demux->video_pad, caps); else @@ -1637,6 +1705,18 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer) goto beach; } + if (demux->segment.rate < 0.0) { + if (FALSE == keyframe) { + demux->audio_push_done = TRUE; + goto beach; + } else { + demux->iframe_push_done = TRUE; + demux->audio_push_done = FALSE; + } + } else if ((demux->segment.rate > 2.0) && (!keyframe)) { + goto beach; + } + /* Create buffer from pad */ outbuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY, 7 + codec_data, demux->tag_data_size - codec_data); @@ -1663,8 +1743,8 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer) if (!keyframe) GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); - - if (!demux->indexed) { + /*Create index entries for keyframe only, in case of video stream */ + if (!demux->indexed && keyframe) { gst_flv_demux_parse_and_add_index_entry (demux, GST_BUFFER_TIMESTAMP (outbuf), demux->cur_tag_offset, keyframe); } @@ -1679,12 +1759,21 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer) /* Do we need a newsegment event ? */ if (G_UNLIKELY (demux->video_need_segment)) { if (!demux->new_seg_event) { - GST_DEBUG_OBJECT (demux, "pushing newsegment from %" - GST_TIME_FORMAT " to %" GST_TIME_FORMAT, - GST_TIME_ARGS (demux->segment.position), - GST_TIME_ARGS (demux->segment.stop)); - demux->segment.start = demux->segment.time = demux->segment.position; - demux->new_seg_event = gst_event_new_segment (&demux->segment); + if (!demux->random_access && (demux->segment.rate < 0.0)) { + demux->segment.start = demux->seeksegment.start = 0; //for sure. + demux->segment.time = demux->seeksegment.time = demux->segment.start; + GST_INFO_OBJECT (demux, "pushing newsegment %" + GST_SEGMENT_FORMAT, &demux->seeksegment); + + demux->new_seg_event = gst_event_new_segment (&demux->seeksegment); + } else { + demux->segment.start = demux->segment.time = demux->segment.position; + + GST_WARNING_OBJECT (demux, "pushing newsegment %" + GST_SEGMENT_FORMAT, &demux->segment); + + demux->new_seg_event = gst_event_new_segment (&demux->segment); + } } else { GST_DEBUG_OBJECT (demux, "pushing pre-generated newsegment event"); } @@ -1719,6 +1808,10 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer) demux->no_more_pads = TRUE; } + if (GST_BUFFER_TIMESTAMP (outbuf) > demux->segment.stop) { + gst_buffer_unref (outbuf); + goto beach; + } /* Push downstream */ ret = gst_pad_push (demux->video_pad, outbuf); @@ -1807,7 +1900,7 @@ gst_flv_demux_parse_tag_timestamp (GstFlvDemux * demux, gboolean index, ret = dts * GST_MSECOND; GST_LOG_OBJECT (demux, "dts: %" GST_TIME_FORMAT, GST_TIME_ARGS (ret)); - if (index && !demux->indexed && (type == 9 || (type == 8 + if (index && !demux->indexed && ((type == 9 && keyframe) || (type == 8 && !demux->has_video))) { gst_flv_demux_parse_and_add_index_entry (demux, ret, demux->offset, keyframe); @@ -1937,7 +2030,7 @@ gst_flv_demux_flush (GstFlvDemux * demux, gboolean discont) demux->flushing = FALSE; /* Only in push mode and if we're not during a seek */ - if (!demux->random_access && demux->state != FLV_STATE_SEEK) { + if (!demux->random_access) { /* After a flush we expect a tag_type */ demux->state = FLV_STATE_TAG_TYPE; /* We reset the offset and will get one from first push */ @@ -1962,6 +2055,8 @@ gst_flv_demux_cleanup (GstFlvDemux * demux) demux->audio_need_discont = TRUE; demux->video_need_discont = TRUE; + demux->resuming = FALSE; + demux->has_audio = FALSE; demux->has_video = FALSE; demux->got_par = FALSE; @@ -1983,6 +2078,19 @@ gst_flv_demux_cleanup (GstFlvDemux * demux) demux->no_audio_warned = FALSE; demux->no_video_warned = FALSE; #endif + demux->invalid_keyframe = FALSE; + demux->unsupported_codec = FALSE; + + demux->rate_is_changed = FALSE; + demux->prev_seek_offset = 0; + demux->seek_state = FLV_SEEK_STATE_NONE; + + /* Trick play support */ + demux->play_rate = 1.0; + demux->high_speed_trick = FALSE; + demux->audio_push_done = FALSE; + demux->ignore_flush = FALSE; + demux->iframe_push_done = FALSE; gst_segment_init (&demux->segment, GST_FORMAT_TIME); @@ -2081,7 +2189,8 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) demux->offset = 0; } - if (G_UNLIKELY (demux->offset == 0 && GST_BUFFER_OFFSET (buffer) != 0)) { + if (G_UNLIKELY (demux->offset == 0 && GST_BUFFER_OFFSET (buffer) != 0 + && GST_BUFFER_OFFSET (buffer) != -1)) { GST_DEBUG_OBJECT (demux, "offset was zero, synchronizing with buffer's"); demux->offset = GST_BUFFER_OFFSET (buffer); } @@ -2093,13 +2202,6 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) gst_adapter_push (demux->adapter, buffer); - if (demux->seeking) { - demux->state = FLV_STATE_SEEK; - GST_OBJECT_LOCK (demux); - demux->seeking = FALSE; - GST_OBJECT_UNLOCK (demux); - } - parse: if (G_UNLIKELY (ret != GST_FLOW_OK)) { GST_DEBUG_OBJECT (demux, "got flow return %s", gst_flow_get_name (ret)); @@ -2112,6 +2214,17 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) goto beach; } + /* 1. the state can be overwritten in the chain after gst_flv_demux_handle_seek_push + * so here back the seek state again + * 2. to parse the tag timing, we need to parse the tag type first + * so need have complete tag type in the adapter */ + if ((demux->seeking) && (demux->state == FLV_STATE_TAG_TYPE)) { + demux->state = FLV_STATE_SEEK; + GST_OBJECT_LOCK (demux); + demux->seeking = FALSE; + GST_OBJECT_UNLOCK (demux); + } + switch (demux->state) { case FLV_STATE_HEADER: { @@ -2130,6 +2243,7 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } else { goto beach; } + break; } case FLV_STATE_TAG_TYPE: { @@ -2155,6 +2269,7 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } else { goto beach; } + break; } case FLV_STATE_TAG_VIDEO: { @@ -2166,6 +2281,13 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) ret = gst_flv_demux_parse_tag_video (demux, buffer); gst_buffer_unref (buffer); + + /* Go out when suceess send the I frame to decoder. */ + if (demux->iframe_push_done && demux->audio_push_done) { + demux->offset = 0; + demux->state = FLV_STATE_TAG_TYPE; + goto send_seek; + } demux->offset += demux->tag_size; demux->state = FLV_STATE_TAG_TYPE; @@ -2173,6 +2295,7 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } else { goto beach; } + break; } case FLV_STATE_TAG_AUDIO: { @@ -2191,6 +2314,7 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } else { goto beach; } + break; } case FLV_STATE_TAG_SCRIPT: { @@ -2214,11 +2338,11 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) else goto no_index; } - goto parse; } else { goto beach; } + break; } case FLV_STATE_SEEK: { @@ -2226,41 +2350,73 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) ret = GST_FLOW_OK; - if (!demux->indexed) { - if (demux->offset == demux->file_size - sizeof (guint32)) { - guint64 seek_offset; - guint8 *data; - - data = gst_adapter_take (demux->adapter, 4); - if (!data) - goto no_index; - - seek_offset = demux->file_size - sizeof (guint32) - - GST_READ_UINT32_BE (data); - g_free (data); - - GST_INFO_OBJECT (demux, - "Seeking to beginning of last tag at %" G_GUINT64_FORMAT, - seek_offset); - demux->state = FLV_STATE_TAG_TYPE; - flv_demux_seek_to_offset (demux, seek_offset); + if (!demux->indexed) { /* parse more data to build index now */ + GstBuffer *buffer; + size_t tag_size; + GstClockTime tag_time; + gboolean seek_found = FALSE; + + /* parse tag type */ + if (demux->seek_state == FLV_SEEK_STATE_PARSE_TAG_TYPE) { + if (gst_adapter_available (demux->adapter) >= 12) { + buffer = gst_adapter_take_buffer (demux->adapter, 12); + tag_time = + gst_flv_demux_parse_tag_timestamp (demux, TRUE, buffer, + &tag_size); + GST_DEBUG_OBJECT (demux, + "tag time %" GST_TIME_FORMAT "seek time %" GST_TIME_FORMAT, + GST_TIME_ARGS (tag_time), GST_TIME_ARGS (demux->seek_time)); + GST_DEBUG_OBJECT (demux, "tag size %" G_GSIZE_FORMAT, tag_size); + gst_buffer_unref (buffer); + + if (tag_time == GST_CLOCK_TIME_NONE) { + GST_WARNING_OBJECT (demux, "invalid tag time"); + seek_found = TRUE; + } else if (tag_time > demux->seek_time) { + GST_INFO_OBJECT (demux, + "tag time %" GST_TIME_FORMAT " > seek time %" GST_TIME_FORMAT, + GST_TIME_ARGS (tag_time), GST_TIME_ARGS (demux->seek_time)); + seek_found = TRUE; + } else { + /* need to skip the tag data */ + demux->tag_size = tag_size; + demux->seek_state = FLV_SEEK_STATE_PARSE_TAG_DATA; + goto parse; + } + } + } + /* parse tag data */ + if (demux->seek_state == FLV_SEEK_STATE_PARSE_TAG_DATA) { + if (gst_adapter_available (demux->adapter) >= (demux->tag_size - 12)) { + buffer = + gst_adapter_take_buffer (demux->adapter, + (demux->tag_size - 12)); + gst_buffer_unref (buffer); + demux->offset += demux->tag_size; + demux->seek_state = FLV_SEEK_STATE_PARSE_TAG_TYPE; + goto parse; + } + } + if (seek_found == FALSE) { + /* need more data */ goto beach; - } else - goto no_index; + } else { /* found valid seek point */ + GST_OBJECT_LOCK (demux); + event = demux->seek_event; + demux->seek_event = NULL; + demux->resuming = FALSE; + GST_OBJECT_UNLOCK (demux); + + /* calculate and perform seek */ + if (!flv_demux_handle_seek_push (demux, event)) { + gst_event_unref (event); + goto seek_failed; + } + /* re-sync the offset and state */ + demux->offset = 0; + } } - - GST_OBJECT_LOCK (demux); - event = demux->seek_event; - demux->seek_event = NULL; - GST_OBJECT_UNLOCK (demux); - - /* calculate and perform seek */ - if (!flv_demux_handle_seek_push (demux, event)) - goto seek_failed; - - gst_event_unref (event); - demux->state = FLV_STATE_TAG_TYPE; - goto beach; + break; } case FLV_STATE_SKIP: /* Skip unknown tags (set in _parse_tag_type()) */ @@ -2274,6 +2430,7 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } default: GST_DEBUG_OBJECT (demux, "unexpected demuxer state"); + break; } beach: @@ -2297,7 +2454,16 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), ("seek failed")); return GST_FLOW_ERROR; } - +send_seek: + { + demux->offset = demux->prev_seek_offset; + demux->iframe_push_done = FALSE; + demux->ignore_flush = TRUE; + demux->rate_is_changed = FALSE; + /*Send other seek event */ + return gst_flv_demux_seek_to_keyframe (demux, + (demux->play_rate > 0.0 ? FALSE : TRUE)); + } } static GstFlowReturn @@ -2364,7 +2530,19 @@ gst_flv_demux_pull_tag (GstPad * pad, GstFlvDemux * demux) ret = gst_flv_demux_parse_tag_video (demux, buffer); break; case FLV_STATE_TAG_AUDIO: - ret = gst_flv_demux_parse_tag_audio (demux, buffer); + if (demux->thumbnail_mode) { + demux->has_audio = FALSE; + ret = GST_FLOW_OK; + } else if (demux->segment.rate < 0.0 || demux->segment.rate > 2.0) { + ret = GST_FLOW_OK; + } else { + ret = gst_flv_demux_parse_tag_audio (demux, buffer); + } + + if (demux->unsupported_codec) { + demux->has_audio = FALSE; + ret = GST_FLOW_OK; + } break; case FLV_STATE_TAG_SCRIPT: ret = gst_flv_demux_parse_tag_script (demux, buffer); @@ -2445,50 +2623,82 @@ gst_flv_demux_move_to_offset (GstFlvDemux * demux, gint64 offset, } static GstFlowReturn -gst_flv_demux_seek_to_prev_keyframe (GstFlvDemux * demux) +gst_flv_demux_seek_to_keyframe (GstFlvDemux * demux, gboolean prev) { GstFlowReturn ret = GST_FLOW_EOS; GstIndex *index; GstIndexEntry *entry = NULL; + GstIndexLookupMethod method; + gint64 search_offset; + + /* push case */ + if (!demux->random_access) + demux->from_offset = demux->prev_seek_offset; GST_DEBUG_OBJECT (demux, "terminated section started at offset %" G_GINT64_FORMAT, demux->from_offset); - /* we are done if we got all audio and video */ - if ((!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts) || - demux->audio_first_ts < demux->segment.start) && - (!GST_CLOCK_TIME_IS_VALID (demux->video_first_ts) || - demux->video_first_ts < demux->segment.start)) - goto done; + if (FALSE == prev) { + method = GST_INDEX_LOOKUP_AFTER; + search_offset = demux->from_offset + 1; + /* we are done if we got all audio and video */ + if ((!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts) || + demux->audio_first_ts > demux->segment.start) && + (!GST_CLOCK_TIME_IS_VALID (demux->video_first_ts) || + demux->video_first_ts > demux->segment.start)) + goto done; + + GST_DEBUG_OBJECT (demux, "locating next position"); + } else { + method = GST_INDEX_LOOKUP_BEFORE; + search_offset = demux->from_offset - 1; + /* we are done if we got all audio and video */ + if ((!GST_CLOCK_TIME_IS_VALID (demux->audio_first_ts) || + demux->audio_first_ts < demux->segment.start) && + (!GST_CLOCK_TIME_IS_VALID (demux->video_first_ts) || + demux->video_first_ts < demux->segment.start)) + goto done; + + GST_DEBUG_OBJECT (demux, "locating previous position"); + } if (demux->from_offset <= 0) goto done; - GST_DEBUG_OBJECT (demux, "locating previous position"); - index = gst_flv_demux_get_index (GST_ELEMENT (demux)); /* locate index entry before previous start position */ if (index) { entry = gst_index_get_assoc_entry (index, demux->index_id, - GST_INDEX_LOOKUP_BEFORE, GST_ASSOCIATION_FLAG_KEY_UNIT, - GST_FORMAT_BYTES, demux->from_offset - 1); + method, GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_BYTES, search_offset); if (entry) { gint64 bytes = 0, time = 0; + gboolean res = TRUE; - gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes); + res = gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes); gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time); GST_DEBUG_OBJECT (demux, "found index entry for %" G_GINT64_FORMAT " at %" GST_TIME_FORMAT ", seeking to %" G_GINT64_FORMAT, - demux->offset - 1, GST_TIME_ARGS (time), bytes); + search_offset, GST_TIME_ARGS (time), bytes); - /* setup for next section */ - demux->to_offset = demux->from_offset; - gst_flv_demux_move_to_offset (demux, bytes, FALSE); - ret = GST_FLOW_OK; + if (!demux->random_access) { + if (!res) + goto done; + + if (gst_flv_demux_send_seek_push (demux, 1.0, bytes)) + ret = GST_FLOW_OK; + else + ret = GST_FLOW_EOS; + + } else { + /* setup for next section */ + demux->to_offset = demux->from_offset; + gst_flv_demux_move_to_offset (demux, bytes, FALSE); + ret = GST_FLOW_OK; + } } gst_object_unref (index); @@ -2662,10 +2872,10 @@ gst_flv_demux_loop (GstPad * pad) if (demux->segment.rate < 0.0) { /* check end of section */ - if ((gint64) demux->offset >= demux->to_offset || - demux->segment.position >= demux->segment.stop + 2 * GST_SECOND || - (demux->audio_done && demux->video_done)) - ret = gst_flv_demux_seek_to_prev_keyframe (demux); + if (demux->iframe_push_done && demux->audio_push_done) { + demux->state = FLV_STATE_TAG_TYPE; + ret = gst_flv_demux_seek_to_keyframe (demux, TRUE); + } } else { /* check EOS condition */ if ((demux->segment.stop != -1) && @@ -2786,7 +2996,8 @@ gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment, /* Key frame seeking */ if (seek_flags & GST_SEEK_FLAG_KEY_UNIT) { /* Adjust the segment so that the keyframe fits in */ - segment->start = segment->time = time; + if (time < segment->start) + segment->start = segment->time = time; segment->position = time; } } else { @@ -2800,6 +3011,26 @@ gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment, return bytes; } +static gboolean +gst_flv_demux_send_seek_push (GstFlvDemux * demux, gdouble rate, + gint64 seek_offset) +{ + GstEvent *event; + gboolean res; + + GST_DEBUG_OBJECT (demux, "Send other seek event"); + + /* BYTE seek event */ + event = gst_event_new_seek (rate, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, + GST_SEEK_TYPE_SET, seek_offset, GST_SEEK_TYPE_NONE, -1); + res = gst_pad_push_event (demux->sinkpad, event); + + demux->prev_seek_offset = seek_offset; + + return res; +} + static gboolean flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event) { @@ -2829,24 +3060,33 @@ flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event) gst_segment_do_seek (&seeksegment, rate, format, flags, start_type, start, stop_type, stop, &update); + if (rate < 0.0) + seeksegment.position = stop; + GST_DEBUG_OBJECT (demux, "segment configured %" GST_SEGMENT_FORMAT, &seeksegment); if (flush || seeksegment.position != demux->segment.position) { /* Do the actual seeking */ - guint64 offset = gst_flv_demux_find_offset (demux, &seeksegment, flags); + guint64 offset; + if ((!demux->indexed) && (!demux->dlna_flagval) && (rate == 1.0) + && (demux->resuming)) + offset = demux->index_max_pos; + else + offset = gst_flv_demux_find_offset (demux, &seeksegment, flags); GST_DEBUG_OBJECT (demux, "generating an upstream seek at position %" G_GUINT64_FORMAT, offset); - ret = gst_pad_push_event (demux->sinkpad, - gst_event_new_seek (seeksegment.rate, GST_FORMAT_BYTES, - flags | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, - offset, GST_SEEK_TYPE_NONE, 0)); + + demux->segment.rate = seeksegment.rate; + ret = gst_flv_demux_send_seek_push (demux, 1.0, offset); if (G_UNLIKELY (!ret)) { GST_WARNING_OBJECT (demux, "upstream seek failed"); + return FALSE; } gst_flow_combiner_reset (demux->flowcombiner); + demux->prev_seek_offset = offset; /* Tell all the stream we moved to a different position (discont) */ demux->audio_need_discont = TRUE; demux->video_need_discont = TRUE; @@ -2856,7 +3096,13 @@ flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event) if (ret) { /* Ok seek succeeded, take the newly configured segment */ - memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); + if (rate > 0.0) + memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); + else { + gst_segment_init (&demux->seeksegment, GST_FORMAT_TIME); + memcpy (&demux->seeksegment, &seeksegment, sizeof (GstSegment)); + } + //memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); /* Tell all the stream a new segment is needed */ demux->audio_need_segment = TRUE; @@ -2892,8 +3138,13 @@ static gboolean gst_flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event) { GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + gdouble rate; - gst_event_parse_seek (event, NULL, &format, NULL, NULL, NULL, NULL, NULL); + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, + &stop_type, &stop); if (format != GST_FORMAT_TIME) { GST_WARNING_OBJECT (demux, "we only support seeking in TIME format"); @@ -2901,60 +3152,58 @@ gst_flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event) return FALSE; } + demux->play_rate = rate; + demux->rate_is_changed = TRUE; + + demux->ignore_flush = FALSE; + if (rate < 0.0 || rate > 2.0) + demux->high_speed_trick = TRUE; + else + demux->high_speed_trick = FALSE; + + if (rate < 0.0) + return flv_demux_handle_seek_push (demux, event); + + //gst_event_parse_seek (event, NULL, &format, NULL, NULL, NULL, NULL, NULL); + /* First try upstream */ + /* blocking this. This algorithm seeked by time. It's not work correctly. 20130816. by meeshel */ +/* if (gst_pad_push_event (demux->sinkpad, gst_event_ref (event))) { GST_DEBUG_OBJECT (demux, "Upstream successfully seeked"); gst_event_unref (event); return TRUE; } +*/ - if (!demux->indexed) { - guint64 seek_offset = 0; - gboolean building_index; - + if (!demux->indexed && (start > demux->index_max_time)) { GST_OBJECT_LOCK (demux); /* handle the seek in the chain function */ demux->seeking = TRUE; demux->state = FLV_STATE_SEEK; + demux->seek_state = FLV_SEEK_STATE_PARSE_TAG_TYPE; /* copy the event */ if (demux->seek_event) gst_event_unref (demux->seek_event); demux->seek_event = gst_event_ref (event); + GST_DEBUG_OBJECT (demux, "delaying seek to post-scan; " + " index only up to %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->index_max_time)); - /* set the building_index flag so that only one thread can setup the - * structures for index seeking. */ - building_index = demux->building_index; - if (!building_index) { - demux->building_index = TRUE; - if (!demux->file_size - && !gst_pad_peer_query_duration (demux->sinkpad, GST_FORMAT_BYTES, - &demux->file_size)) { - GST_WARNING_OBJECT (demux, "Failed to query upstream file size"); - GST_OBJECT_UNLOCK (demux); - return FALSE; - } - - /* we hope the last tag is a scriptdataobject containing an index - * the size of the last tag is given in the last guint32 bits - * then we seek to the beginning of the tag, parse it and hopefully obtain an index */ - seek_offset = demux->file_size - sizeof (guint32); - GST_DEBUG_OBJECT (demux, - "File size obtained, seeking to %" G_GUINT64_FORMAT, seek_offset); - } + demux->seek_time = start; GST_OBJECT_UNLOCK (demux); - if (!building_index) { - GST_INFO_OBJECT (demux, "Seeking to last 4 bytes at %" G_GUINT64_FORMAT, - seek_offset); - return flv_demux_seek_to_offset (demux, seek_offset); - } - /* FIXME: we have to always return true so that we don't block the seek * thread. * Note: maybe it is OK to return true if we're still building the index */ + if ((!demux->dlna_flagval) && (rate == 1.0)) { + demux->resuming = TRUE; + return flv_demux_handle_seek_push (demux, event); + } + return TRUE; + } else if (demux->resuming) return TRUE; - } return flv_demux_handle_seek_push (demux, event); } @@ -2977,6 +3226,13 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event, if (format != GST_FORMAT_TIME) goto wrong_format; + demux->play_rate = rate; + + if (rate < 0.0 || rate > 2.0) + demux->high_speed_trick = TRUE; + else + demux->high_speed_trick = FALSE; + /* mark seeking thread entering flushing/pausing */ GST_OBJECT_LOCK (demux); if (seeking) @@ -3078,6 +3334,15 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event, GST_TIME_ARGS (demux->segment.start), GST_TIME_ARGS (demux->segment.stop)); demux->new_seg_event = gst_event_new_segment (&demux->segment); + + /* Push gap event for audio if demux have negative segment.rate value */ + if (demux->audio_pad && (demux->segment.rate < 0.0 || + demux->segment.rate > 2.0)) { + gst_pad_push_event (demux->audio_pad, + gst_event_ref (demux->new_seg_event)); + gst_pad_push_event (demux->audio_pad, + gst_event_new_gap (demux->segment.start, GST_CLOCK_TIME_NONE)); + } } exit: @@ -3184,11 +3449,20 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: + if (demux->ignore_flush) { + gst_event_unref (event); + return TRUE; + } GST_DEBUG_OBJECT (demux, "trying to force chain function to exit"); demux->flushing = TRUE; ret = gst_flv_demux_push_src_event (demux, event); break; case GST_EVENT_FLUSH_STOP: + if (demux->ignore_flush) { + gst_flv_demux_flush (demux, TRUE); + gst_event_unref (event); + return TRUE; + } GST_DEBUG_OBJECT (demux, "flushing FLV demuxer"); gst_flv_demux_flush (demux, TRUE); ret = gst_flv_demux_push_src_event (demux, event); @@ -3225,30 +3499,58 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) } case GST_EVENT_SEGMENT: { - GstSegment in_segment; + if (!demux->random_access && !demux->rate_is_changed) { + gst_event_unref (event); + ret = TRUE; + } else { + GstSegment in_segment; + GstEvent *segment_event; - GST_DEBUG_OBJECT (demux, "received new segment"); + GST_DEBUG_OBJECT (demux, "received new segment"); - gst_event_copy_segment (event, &in_segment); + gst_event_copy_segment (event, &in_segment); - if (in_segment.format == GST_FORMAT_TIME) { - /* time segment, this is perfect, copy over the values. */ - memcpy (&demux->segment, &in_segment, sizeof (in_segment)); + in_segment.rate = demux->play_rate; + demux->segment.rate = demux->play_rate; - GST_DEBUG_OBJECT (demux, "NEWSEGMENT: %" GST_SEGMENT_FORMAT, - &demux->segment); + if (!demux->random_access) { + if (demux->play_rate < 0.0) { + in_segment.stop = in_segment.start; + in_segment.start = 0; + in_segment.position = in_segment.stop; + in_segment.time = in_segment.start; + } + } - /* and forward */ - ret = gst_flv_demux_push_src_event (demux, event); - } else { - /* non-time format */ - demux->audio_need_segment = TRUE; - demux->video_need_segment = TRUE; - ret = TRUE; - gst_event_unref (event); - if (demux->new_seg_event) { - gst_event_unref (demux->new_seg_event); - demux->new_seg_event = NULL; + if (demux->rate_is_changed) { + GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, + &in_segment); + segment_event = gst_event_new_segment (&in_segment); + in_segment.rate = demux->play_rate; + demux->segment.rate = demux->play_rate; + gst_event_set_seqnum (segment_event, gst_event_get_seqnum (event)); + gst_flv_demux_push_src_event (demux, segment_event); + } + + if (in_segment.format == GST_FORMAT_TIME) { + /* time segment, this is perfect, copy over the values. */ + memcpy (&demux->segment, &in_segment, sizeof (in_segment)); + + GST_DEBUG_OBJECT (demux, "NEWSEGMENT: %" GST_SEGMENT_FORMAT, + &demux->segment); + + /* and forward */ + ret = gst_flv_demux_push_src_event (demux, event); + } else { + /* non-time format */ + demux->audio_need_segment = TRUE; + demux->video_need_segment = TRUE; + ret = TRUE; + gst_event_unref (event); + if (demux->new_seg_event) { + gst_event_unref (demux->new_seg_event); + demux->new_seg_event = NULL; + } } } gst_flow_combiner_reset (demux->flowcombiner); @@ -3352,6 +3654,25 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query) break; } + case GST_QUERY_CUSTOM:{ + gboolean trickable = TRUE; + GstStructure *s; + + s = (GstStructure *) gst_query_get_structure (query); + + if (gst_structure_has_name (s, "custom-trickable")) { + if (demux->invalid_keyframe) + trickable = FALSE; + + gst_structure_set (s, "trickable", G_TYPE_BOOLEAN, trickable, NULL); + res = TRUE; + break; + } else { + res = gst_pad_query_default (pad, parent, query); + break; + } + } + case GST_QUERY_SEEKING:{ GstFormat fmt; @@ -3368,9 +3689,20 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query) } } res = TRUE; + + GST_INFO_OBJECT (demux, + "seeking query, index %" GST_PTR_FORMAT + ", invalid_keyframe %d, indexed %d, randon_access %d", + demux->index, demux->invalid_keyframe, demux->indexed, + demux->random_access); + /* FIXME, check index this way is not thread safe */ if (fmt != GST_FORMAT_TIME || !demux->index) { gst_query_set_seeking (query, fmt, FALSE, -1, -1); + } + /*when only one keyframe or no keyframe is exist */ + else if (demux->invalid_keyframe) { + gst_query_set_seeking (query, fmt, FALSE, -1, -1); } else if (demux->random_access) { gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, demux->duration); @@ -3423,12 +3755,46 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query) static GstStateChangeReturn gst_flv_demux_change_state (GstElement * element, GstStateChange transition) { - GstFlvDemux *demux; - GstStateChangeReturn ret; + GstFlvDemux *demux = NULL; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstSmartPropertiesReturn property_ret = GST_SMART_PROPERTIES_OK; demux = GST_FLV_DEMUX (element); switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* For custom property extraction */ + { + GST_INFO + ("Smart property initials: dlna-opval[0x%02x] dlna-flagval [0x%03x] " + "dlna-duration[%" G_GUINT64_FORMAT "] " + "dlna-filelength[%" G_GUINT64_FORMAT "] " + "thumbnail-mode[%d]", + demux->dlna_opval, demux->dlna_flagval, demux->dlna_duration, + demux->dlna_filelength, demux->thumbnail_mode); + + property_ret = + gst_element_get_smart_properties (GST_ELEMENT_CAST (demux), + "dlna-opval", &demux->dlna_opval, "dlna-flagval", + &demux->dlna_flagval, "dlna-duration", &demux->dlna_duration, + "dlna-contentlength", &demux->dlna_filelength, "thumbnail-mode", + &demux->thumbnail_mode, NULL); + + GST_INFO_OBJECT (demux, + "flvdemux received response of custom query: [%d]", ret); + GST_INFO + ("Smart property results: dlna-opval[0x%02x] dlna-flagval [0x%03x] " + "dlna-duration[%" G_GUINT64_FORMAT "] " + "dlna-filelength[%" G_GUINT64_FORMAT "] " + "thumbnail-mode[%d]", + demux->dlna_opval, demux->dlna_flagval, demux->dlna_duration, + demux->dlna_filelength, demux->thumbnail_mode); + } + if (property_ret != GST_SMART_PROPERTIES_OK) { + GST_WARNING_OBJECT (demux, "faield to get custom property"); + demux->thumbnail_mode = FALSE; + } + break; case GST_STATE_CHANGE_READY_TO_PAUSED: /* If this is our own index destroy it as the * old entries might be wrong for the new stream */ @@ -3642,6 +4008,7 @@ gst_flv_demux_init (GstFlvDemux * demux) demux->flowcombiner = gst_flow_combiner_new (); demux->own_index = FALSE; + demux->thumbnail_mode = FALSE; GST_OBJECT_FLAG_SET (demux, GST_ELEMENT_FLAG_INDEXABLE); diff --git a/gst/flv/gstflvdemux.h b/gst/flv/gstflvdemux.h index 4eb1791760..4059a3081d 100644 --- a/gst/flv/gstflvdemux.h +++ b/gst/flv/gstflvdemux.h @@ -52,6 +52,13 @@ typedef enum FLV_STATE_NONE } GstFlvDemuxState; +typedef enum +{ + FLV_SEEK_STATE_PARSE_TAG_TYPE, + FLV_SEEK_STATE_PARSE_TAG_DATA, + FLV_SEEK_STATE_NONE +} GstFlvDemuxSeekState; + struct _GstFlvDemux { GstElement element; @@ -65,13 +72,13 @@ struct _GstFlvDemux guint group_id; /* */ - + GstIndex *index; gint index_id; gboolean own_index; - - GArray * times; - GArray * filepositions; + + GArray *times; + GArray *filepositions; GstAdapter *adapter; @@ -102,7 +109,7 @@ struct _GstFlvDemux gboolean audio_need_discont; gboolean audio_need_segment; gboolean audio_linked; - GstBuffer * audio_codec_data; + GstBuffer *audio_codec_data; GstClockTime audio_start; guint32 last_audio_pts; GstClockTime audio_time_offset; @@ -118,7 +125,7 @@ struct _GstFlvDemux gboolean video_need_segment; gboolean video_linked; gboolean got_par; - GstBuffer * video_codec_data; + GstBuffer *video_codec_data; GstClockTime video_start; guint32 last_video_dts; GstClockTime video_time_offset; @@ -140,11 +147,12 @@ struct _GstFlvDemux gboolean seeking; gboolean building_index; - gboolean indexed; /* TRUE if index is completely built */ - gboolean upstream_seekable; /* TRUE if upstream is seekable */ + gboolean indexed; /* TRUE if index is completely built */ + gboolean upstream_seekable; /* TRUE if upstream is seekable */ gint64 file_size; GstEvent *seek_event; gint64 seek_time; + GstFlvDemuxSeekState seek_state; GstClockTime index_max_time; gint64 index_max_pos; @@ -156,6 +164,28 @@ struct _GstFlvDemux gboolean audio_done; gint64 from_offset; gint64 to_offset; + + /* For Smart properties */ + guint32 dlna_opval; + guint32 dlna_flagval; + guint64 dlna_duration; + guint64 dlna_filelength; + + gboolean thumbnail_mode; /*thumbnail flag */ + gboolean invalid_keyframe; /* whether if times tag or filepositions tag in metadata is exist or not */ + gboolean unsupported_codec; + + gboolean rate_is_changed; + gint64 prev_seek_offset; + GstSegment seeksegment; + gboolean resuming; + + /* Trick play support */ + gdouble play_rate; + gboolean high_speed_trick; + gboolean iframe_push_done; + gboolean audio_push_done; + gboolean ignore_flush; }; struct _GstFlvDemuxClass diff --git a/gst/isomp4/atoms.c b/gst/isomp4/atoms.c index 4ca69274b3..904fab3ea1 100644 --- a/gst/isomp4/atoms.c +++ b/gst/isomp4/atoms.c @@ -248,12 +248,13 @@ atom_uuid_free (AtomUUID * data) } static void -atom_ftyp_init (AtomFTYP * ftyp, guint32 major, guint32 version, GList * brands) +atom_brands_init (AtomFTYP * ftyp, guint32 fourcc, guint32 major, + guint32 version, GList * brands) { gint index; GList *it = NULL; - atom_header_set (&ftyp->header, FOURCC_ftyp, 16, 0); + atom_header_set (&ftyp->header, fourcc, 16, 0); ftyp->major_brand = major; ftyp->version = version; @@ -268,6 +269,12 @@ atom_ftyp_init (AtomFTYP * ftyp, guint32 major, guint32 version, GList * brands) } } +static void +atom_ftyp_init (AtomFTYP * ftyp, guint32 major, guint32 version, GList * brands) +{ + atom_brands_init (ftyp, FOURCC_ftyp, major, version, brands); +} + AtomFTYP * atom_ftyp_new (AtomsContext * context, guint32 major, guint32 version, GList * brands) @@ -278,6 +285,22 @@ atom_ftyp_new (AtomsContext * context, guint32 major, guint32 version, return ftyp; } +static void +atom_styp_init (AtomFTYP * ftyp, guint32 major, guint32 version, GList * brands) +{ + atom_brands_init (ftyp, FOURCC_styp, major, version, brands); +} + +AtomFTYP * +atom_styp_new (AtomsContext * context, guint32 major, guint32 version, + GList * brands) +{ + AtomFTYP *ftyp = g_new0 (AtomFTYP, 1); + + atom_styp_init (ftyp, major, version, brands); + return ftyp; +} + void atom_ftyp_free (AtomFTYP * ftyp) { @@ -287,6 +310,57 @@ atom_ftyp_free (AtomFTYP * ftyp) g_free (ftyp); } +static void +atom_sidx_init (AtomSIDX * sidx, guint32 reference_id, guint64 dts, + guint32 timescale, guint64 earlist_pts, guint64 first_offset) +{ + guint8 flags[3] = { 0, 0, 0 }; + + atom_full_init (&sidx->header, FOURCC_sidx, 0, 0, 0, flags); + sidx->ref_id = reference_id; + sidx->timescale = timescale; + + /* auto-use 64 bits if needed */ + if (dts > G_MAXUINT32) + sidx->header.version = 1; + + if (sidx->header.version == 0) { + sidx->earlist_pts = (guint32) earlist_pts; + sidx->first_offset = (guint32) first_offset; + } else { + sidx->earlist_pts = earlist_pts; + sidx->first_offset = first_offset; + } + + sidx->reserved = 0; + //FIXME: need to fix reserve size + atom_array_init (&sidx->entries, 512); +} + +AtomSIDX * +atom_sidx_new (AtomsContext * context, guint32 reference_id, guint64 dts, + guint32 timescale, guint64 earlist_pts, guint64 first_offset) +{ + AtomSIDX *sidx = g_new0 (AtomSIDX, 1); + + atom_sidx_init (sidx, reference_id, dts, timescale, + earlist_pts, first_offset); + return sidx; +} + +void +atom_sidx_free (AtomSIDX * sidx) +{ + atom_full_clear (&sidx->header); + atom_array_clear (&sidx->entries); + sidx->ref_id = 0; + sidx->timescale = 0; + sidx->earlist_pts = 0; + sidx->first_offset = 0; + sidx->reserved = 0; + g_free (sidx); +} + static void atom_esds_init (AtomESDS * esds) { @@ -2987,6 +3061,57 @@ atom_wave_copy_data (AtomWAVE * wave, guint8 ** buffer, return *offset - original_offset; } +guint64 +atom_sidx_copy_data (AtomSIDX * sidx, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + guint64 original_offset = *offset; + guint32 i; + SIDXEntry *entry; + guint version; + + if (!atom_full_copy_data (&sidx->header, buffer, size, offset)) { + return 0; + } + + prop_copy_uint32 (sidx->ref_id, buffer, size, offset); + prop_copy_uint32 (sidx->timescale, buffer, size, offset); + + version = atom_full_get_version (&(sidx->header)); + if (version) { + prop_copy_uint64 (sidx->earlist_pts, buffer, size, offset); + prop_copy_uint64 (sidx->first_offset, buffer, size, offset); + } else { + prop_copy_uint32 ((guint32) sidx->earlist_pts, buffer, size, offset); + prop_copy_uint32 ((guint32) sidx->first_offset, buffer, size, offset); + } + + prop_copy_uint16 (sidx->reserved, buffer, size, offset); + prop_copy_uint16 (atom_array_get_len (&sidx->entries), buffer, size, offset); + + /* minimize realloc */ + prop_copy_ensure_buffer (buffer, size, offset, + 12 * atom_array_get_len (&sidx->entries)); + + for (i = 0; i < atom_array_get_len (&sidx->entries); ++i) { + guint32 tmp; + entry = &atom_array_index (&sidx->entries, i); + + tmp = ((entry->ref_type & 1) << 31); + tmp |= (entry->ref_size) & 0x7fffffff; + prop_copy_uint32 (tmp, buffer, size, offset); + prop_copy_uint32 (entry->subsegment_duration, buffer, size, offset); + tmp = ((entry->start_with_sap & 1) << 31); + tmp |= ((entry->sap_type & 0x7) << 28); + tmp |= (entry->sap_delta_time) & 0x0fffffff; + prop_copy_uint32 (tmp, buffer, size, offset); + } + + atom_write_size (buffer, size, offset, original_offset); + return *offset - original_offset; +} + + /* -- end of copy data functions -- */ /* -- general functions, API and support functions */ @@ -3141,6 +3266,23 @@ atom_stbl_add_ctts_entry (AtomSTBL * stbl, guint32 nsamples, guint32 offset) atom_ctts_add_entry (stbl->ctts, nsamples, offset); } +void +atom_sidx_add_entry (AtomSIDX * sidx, gboolean ref_type, guint32 ref_size, + guint32 sub_duration, gboolean start_with_sap, guint8 sap_type, + guint32 sap_delta_time) +{ + SIDXEntry entry; + + entry.ref_type = ref_type; + entry.ref_size = ref_size; + entry.subsegment_duration = sub_duration; + entry.start_with_sap = start_with_sap; + entry.sap_type = sap_type; + entry.sap_delta_time = sap_delta_time; + + atom_array_append (&sidx->entries, entry, 256); +} + void atom_stbl_add_samples (AtomSTBL * stbl, guint32 nsamples, guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync, gint64 pts_offset) @@ -5593,3 +5735,23 @@ build_uuid_xmp_atom (GstBuffer * xmp_data) return build_atom_info_wrapper ((Atom *) uuid, atom_uuid_copy_data, atom_uuid_free); } + +AtomInfo * +build_uuid_dvr_atom (void) +{ + AtomUUID *uuid; + + /* uuid 4747982f-82a3-44a8-bb1d-a8c2ba5dcf9d + NOTE: it's big-edian */ + static const guint8 dvr_uuid[] = { 0x47, 0x47, 0x98, 0x2f, + 0x82, 0xa3, 0x44, 0xa8, + 0xbb, 0x1d, 0xa8, 0xc2, + 0xba, 0x5d, 0xcf, 0x9d + }; + + uuid = atom_uuid_new (); + memcpy (uuid->uuid, dvr_uuid, 16); + + return build_atom_info_wrapper ((Atom *) uuid, atom_uuid_copy_data, + atom_uuid_free); +} diff --git a/gst/isomp4/atoms.h b/gst/isomp4/atoms.h index 27d249431d..e02f5eaaab 100644 --- a/gst/isomp4/atoms.h +++ b/gst/isomp4/atoms.h @@ -177,6 +177,32 @@ typedef struct _AtomFTYP guint32 compatible_brands_size; } AtomFTYP; +typedef struct _SIDXEntry +{ + gboolean ref_type; + guint32 ref_size; + guint32 subsegment_duration; + gboolean start_with_sap; + guint8 sap_type; + guint32 sap_delta_time; +} SIDXEntry; + +typedef struct _AtomSIDX +{ + AtomFull header; + + guint32 ref_id; + guint32 timescale; + + /* version 0: 32 bits */ + guint64 earlist_pts; + guint64 first_offset; + + guint16 reserved; + + ATOM_ARRAY (SIDXEntry) entries; +} AtomSIDX; + typedef struct _AtomMVHD { AtomFull header; @@ -978,6 +1004,7 @@ guint64 atom_stco64_copy_data (AtomSTCO64 *atom, guint8 **buffer, AtomMOOF* atom_moof_new (AtomsContext *context, guint32 sequence_number); void atom_moof_free (AtomMOOF *moof); guint64 atom_moof_copy_data (AtomMOOF *moof, guint8 **buffer, guint64 *size, guint64* offset); +void atom_moof_add_traf (AtomMOOF *moof, AtomTRAF *traf); AtomTRAF * atom_traf_new (AtomsContext * context, guint32 track_ID); void atom_traf_free (AtomTRAF * traf); void atom_traf_set_base_decode_time (AtomTRAF * traf, guint64 base_decode_time); @@ -985,7 +1012,6 @@ void atom_traf_add_samples (AtomTRAF * traf, guint32 delta, guint32 size, gboolean sync, gint64 pts_offset, gboolean sdtp_sync); guint32 atom_traf_get_sample_num (AtomTRAF * traf); -void atom_moof_add_traf (AtomMOOF *moof, AtomTRAF *traf); AtomMFRA* atom_mfra_new (AtomsContext *context); void atom_mfra_free (AtomMFRA *mfra); @@ -995,6 +1021,19 @@ void atom_tfra_update_offset (AtomTFRA * tfra, guint64 offset); void atom_mfra_add_tfra (AtomMFRA *mfra, AtomTFRA *tfra); guint64 atom_mfra_copy_data (AtomMFRA *mfra, guint8 **buffer, guint64 *size, guint64* offset); +AtomFTYP* atom_styp_new (AtomsContext *context, guint32 major, + guint32 version, GList *brands); +AtomSIDX* atom_sidx_new (AtomsContext * context, guint32 reference_id, + guint64 dts, guint32 timescale, + guint64 earlist_pts, guint64 first_offset); +void atom_sidx_free (AtomSIDX * sidx); +void atom_sidx_add_entry (AtomSIDX * sidx, gboolean ref_type, + guint32 ref_size, guint32 sub_duration, + gboolean start_with_sap, guint8 sap_type, + guint32 sap_delta_time); +guint64 atom_sidx_copy_data (AtomSIDX * sidx, guint8 ** buffer, + guint64 * size, guint64 * offset); + /* media sample description related helpers */ @@ -1097,6 +1136,7 @@ AtomInfo * build_SMI_atom (const GstBuffer *seqh); AtomInfo * build_ima_adpcm_extension (gint channels, gint rate, gint blocksize); AtomInfo * build_uuid_xmp_atom (GstBuffer * xmp); +AtomInfo * build_uuid_dvr_atom (void); /* diff --git a/gst/isomp4/fourcc.h b/gst/isomp4/fourcc.h index be5c7c04e1..496e662acd 100644 --- a/gst/isomp4/fourcc.h +++ b/gst/isomp4/fourcc.h @@ -85,6 +85,7 @@ G_BEGIN_DECLS #define FOURCC__wrt GST_MAKE_FOURCC(0xa9,'w','r','t') #define FOURCC_aART GST_MAKE_FOURCC('a','A','R','T') #define FOURCC_ac_3 GST_MAKE_FOURCC('a','c','-','3') +#define FOURCC_ac_4 GST_MAKE_FOURCC('a','c','-','4') #define FOURCC_agsm GST_MAKE_FOURCC('a','g','s','m') #define FOURCC_alac GST_MAKE_FOURCC('a','l','a','c') #define FOURCC_fLaC GST_MAKE_FOURCC('f','L','a','C') @@ -124,6 +125,7 @@ G_BEGIN_DECLS #define FOURCC_dvc_ GST_MAKE_FOURCC('d','v','c',' ') #define FOURCC_dv5p GST_MAKE_FOURCC('d','v','5','p') #define FOURCC_dv5n GST_MAKE_FOURCC('d','v','5','n') +#define FOURCC_ec_3 GST_MAKE_FOURCC('e','c','-','3') #define FOURCC_edts GST_MAKE_FOURCC('e','d','t','s') #define FOURCC_elst GST_MAKE_FOURCC('e','l','s','t') #define FOURCC_enda GST_MAKE_FOURCC('e','n','d','a') @@ -217,7 +219,6 @@ G_BEGIN_DECLS #define FOURCC_sowt GST_MAKE_FOURCC('s','o','w','t') #define FOURCC_stbl GST_MAKE_FOURCC('s','t','b','l') #define FOURCC_stco GST_MAKE_FOURCC('s','t','c','o') -#define FOURCC_stpp GST_MAKE_FOURCC('s','t','p','p') #define FOURCC_stps GST_MAKE_FOURCC('s','t','p','s') #define FOURCC_strf GST_MAKE_FOURCC('s','t','r','f') #define FOURCC_strm GST_MAKE_FOURCC('s','t','r','m') @@ -258,6 +259,15 @@ G_BEGIN_DECLS #define FOURCC_wide GST_MAKE_FOURCC('w','i','d','e') #define FOURCC_zlib GST_MAKE_FOURCC('z','l','i','b') #define FOURCC_lpcm GST_MAKE_FOURCC('l','p','c','m') +#define FOURCC_av01 GST_MAKE_FOURCC('a','v','0','1') +#define FOURCC_av1C GST_MAKE_FOURCC('a','v','1','C') +#define FOURCC_av1f GST_MAKE_FOURCC('a','v','1','f') +#define FOURCC_av1m GST_MAKE_FOURCC('a','v','1','m') +#define FOURCC_av1s GST_MAKE_FOURCC('a','v','1','s') +#define FOURCC_av1M GST_MAKE_FOURCC('a','v','1','M') +#define FOURCC_lhv1 GST_MAKE_FOURCC('l','h','v','1') +#define FOURCC_lhe1 GST_MAKE_FOURCC('l','h','e','1') +#define FOURCC_lhvC GST_MAKE_FOURCC('l','h','v','C') #define FOURCC_cfhd GST_MAKE_FOURCC('C','F','H','D') #define FOURCC_ap4x GST_MAKE_FOURCC('a','p','4','x') @@ -270,6 +280,9 @@ G_BEGIN_DECLS #define FOURCC_vivo GST_MAKE_FOURCC('v','i','v','o') #define FOURCC_saiz GST_MAKE_FOURCC('s','a','i','z') #define FOURCC_saio GST_MAKE_FOURCC('s','a','i','o') +#define FOURCC_sbgp GST_MAKE_FOURCC('s','b','g','p') +#define FOURCC_sgpd GST_MAKE_FOURCC('s','g','p','d') +#define FOURCC_seig GST_MAKE_FOURCC('s','e','i','g') #define FOURCC_3gg6 GST_MAKE_FOURCC('3','g','g','6') #define FOURCC_3gg7 GST_MAKE_FOURCC('3','g','g','7') @@ -334,6 +347,9 @@ G_BEGIN_DECLS /* MPEG DASH */ #define FOURCC_tfdt GST_MAKE_FOURCC('t','f','d','t') +#define FOURCC_styp GST_MAKE_FOURCC('s','t','y','p') +#define FOURCC_dash GST_MAKE_FOURCC('d','a','s','h') +#define FOURCC_msdh GST_MAKE_FOURCC('m','s','d','h') /* Xiph fourcc */ #define FOURCC_XdxT GST_MAKE_FOURCC('X','d','x','T') @@ -378,6 +394,21 @@ G_BEGIN_DECLS #define FOURCC_pssh GST_MAKE_FOURCC('p','s','s','h') #define FOURCC_tenc GST_MAKE_FOURCC('t','e','n','c') #define FOURCC_cenc GST_MAKE_FOURCC('c','e','n','c') +#define FOURCC_senc GST_MAKE_FOURCC('s','e','n','c') +#define FOURCC_cbc1 GST_MAKE_FOURCC('c','b','c','1') +#define FOURCC_cens GST_MAKE_FOURCC('c','e','n','s') +#define FOURCC_cbcs GST_MAKE_FOURCC('c','b','c','s') + +/* Dolby Vision */ +#define FOURCC_dvcC GST_MAKE_FOURCC('d','v','c','C') +#define FOURCC_dvvC GST_MAKE_FOURCC('d','v','v','C') +#define FOURCC_dvav GST_MAKE_FOURCC('d','v','a','v') +#define FOURCC_dvhe GST_MAKE_FOURCC('d','v','h','e') +#define FOURCC_dvh1 GST_MAKE_FOURCC('d','v','h','1') +#define FOURCC_dav1 GST_MAKE_FOURCC('d','a','v','1') + +/* MPEG-H 3D Audio stream(MHAS) */ +#define FOURCC_mhm1 GST_MAKE_FOURCC('m','h','m','1') G_END_DECLS diff --git a/gst/isomp4/gstqtmux-doc.h b/gst/isomp4/gstqtmux-doc.h index 9d1216416f..911791bec9 100644 --- a/gst/isomp4/gstqtmux-doc.h +++ b/gst/isomp4/gstqtmux-doc.h @@ -49,4 +49,5 @@ typedef struct _GstMP4Mux GstMP4Mux; typedef struct _Gst3GPPMux Gst3GPPMux; typedef struct _GstISMLMux GstISMLMux; typedef struct _GstMJ2Mux GstMJ2Mux; +typedef struct _GstDASHMux GstMP4Mux; diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index 3fa682caf1..503bb4083c 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -135,6 +135,7 @@ #include #include #include +#include #include #ifdef G_OS_WIN32 @@ -368,13 +369,47 @@ gst_qt_mux_pad_get_timescale (GstQTMuxPad * pad) return timescale; } +enum +{ + FRAGMENT_METHOD_NONE, + FRAGMENT_METHOD_TIME, + FRAGMENT_METHOD_EVENT +}; + +static GType +gst_qt_mux_fragment_method_get_type (void) +{ + static GType gst_qt_mux_fragment_method = 0; + + if (!gst_qt_mux_fragment_method) { + static const GEnumValue fragment_methods[] = { + {FRAGMENT_METHOD_NONE, "Do not fragment", "none"}, + {FRAGMENT_METHOD_TIME, "Time", "time"}, + {FRAGMENT_METHOD_EVENT, "GstForceKeyUnit events", "event"}, + {0, NULL, NULL}, + }; + + gst_qt_mux_fragment_method = + g_enum_register_static ("GstQTMuxFragmentMethods", fragment_methods); + } + + return gst_qt_mux_fragment_method; +} + +#define GST_TYPE_QT_MUX_FRAGMENT_METHOD \ + (gst_qt_mux_fragment_method_get_type ()) + + /* QTMux signals and args */ enum { - /* FILL ME */ + SIGNAL_SEND_HEADER, + LAST_SIGNAL }; +static guint gst_qt_mux_signals [LAST_SIGNAL] = { 0 }; + enum { PROP_0, @@ -397,10 +432,14 @@ enum PROP_INTERLEAVE_BYTES, PROP_INTERLEAVE_TIME, PROP_MAX_RAW_AUDIO_DRIFT, + PROP_FRAGMENT_METHOD, + PROP_SEGMENT_DURATION, + PROP_DVR_ENCRYPTION, }; /* some spare for header size as well */ #define MDAT_LARGE_FILE_LIMIT ((guint64) 1024 * 1024 * 1024 * 2) +#define RESERVED_SIDX_FREE_SPACE 100 #define DEFAULT_MOVIE_TIMESCALE 0 #define DEFAULT_TRAK_TIMESCALE 0 @@ -410,6 +449,9 @@ enum #define DEFAULT_MOOV_RECOV_FILE NULL #define DEFAULT_FRAGMENT_DURATION 0 #define DEFAULT_STREAMABLE TRUE +#define DEFAULT_FRAGMENT_METHOD FRAGMENT_METHOD_NONE +#define DEFAULT_SEGMENT_DURATION 0 +#define DEFAULT_DVR_ENCRYPTION FALSE #ifndef GST_REMOVE_DEPRECATED #define DEFAULT_DTS_METHOD DTS_METHOD_REORDER #endif @@ -421,6 +463,17 @@ enum #define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND #define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND +#define GST_QT_MUX_IS_FRAGMENTED(x) (\ + x->format == GST_QT_MUX_FORMAT_ISML || \ + x->format == GST_QT_MUX_FORMAT_DASH || \ + x->format == GST_QT_MUX_FORMAT_MP4) + +#define BUFFER_REPLACE_TAKE(p,b) G_STMT_START { \ + if ((p) != NULL) \ + gst_buffer_unref (p); \ + p = b; \ +} G_STMT_END + static void gst_qt_mux_finalize (GObject * object); static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element, @@ -440,11 +493,16 @@ static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad); /* event */ static gboolean gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data, GstEvent * event, gpointer user_data); +static gboolean gst_qt_mux_src_event (GstPad * pad, GstObject * parent, + GstEvent * event); static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data); static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf); +static void gst_qt_mux_dequeue_force_key_unit_event (GstQTMux * mux, + GstQTPad * pad); +static GstFlowReturn gst_qt_mux_queue_extra_atoms (GstQTMux * qtmux); static GstFlowReturn gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux); @@ -514,10 +572,15 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) GObjectClass *gobject_class; GstElementClass *gstelement_class; GParamFlags streamable_flags; - const gchar *streamable_desc; + const gchar *streamable_desc, *fragment_duration_desc; + const gchar *segment_duration_desc; gboolean streamable; #define STREAMABLE_DESC "If set to true, the output should be as if it is to "\ "be streamed and hence no indexes written or duration written." +#define FRAGMENT_DURATION_DESC "Fragment durations in ms (used when the "\ + "fragment-method='time')" +#define SEGMENT_DURATION_DESC "Segment durations in ms (used when the "\ + "segment-method='time')" gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; @@ -529,12 +592,18 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) gobject_class->set_property = gst_qt_mux_set_property; streamable_flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT; - if (klass->format == GST_QT_MUX_FORMAT_ISML) { + if (GST_QT_MUX_IS_FRAGMENTED (klass)) { streamable_desc = STREAMABLE_DESC; + fragment_duration_desc = FRAGMENT_DURATION_DESC; + segment_duration_desc = SEGMENT_DURATION_DESC; streamable = DEFAULT_STREAMABLE; } else { streamable_desc = - STREAMABLE_DESC " (DEPRECATED, only valid for fragmented MP4)"; + STREAMABLE_DESC " (DEPRECATED, only validr for fragmented MP4)"; + fragment_duration_desc = + FRAGMENT_DURATION_DESC " (DEPRECATED, only valid for fragmented MP4)"; + segment_duration_desc = + SEGMENT_DURATION_DESC " (DEPRECATED, only valid for fragmented MP4)"; streamable_flags |= G_PARAM_DEPRECATED; streamable = FALSE; } @@ -583,13 +652,15 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION, g_param_spec_uint ("fragment-duration", "Fragment duration", - "Fragment durations in ms (produce a fragmented file if > 0)", - 0, G_MAXUINT32, klass->format == GST_QT_MUX_FORMAT_ISML ? - 2000 : DEFAULT_FRAGMENT_DURATION, + fragment_duration_desc, 0, G_MAXUINT32, DEFAULT_FRAGMENT_DURATION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_STREAMABLE, g_param_spec_boolean ("streamable", "Streamable", streamable_desc, streamable, streamable_flags | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SEGMENT_DURATION, + g_param_spec_uint ("segment-duration", "Segment duration", + segment_duration_desc, 0, G_MAXUINT32, DEFAULT_SEGMENT_DURATION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RESERVED_MAX_DURATION, g_param_spec_uint64 ("reserved-max-duration", "Reserved maximum file duration (ns)", @@ -639,6 +710,24 @@ gst_qt_mux_class_init (GstQTMuxClass * klass) "Maximum allowed drift of raw audio samples vs. timestamps in nanoseconds", 0, G_MAXUINT64, DEFAULT_MAX_RAW_AUDIO_DRIFT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DVR_ENCRYPTION, + g_param_spec_boolean ("dvr-encryption", "encrypted dvr stream case", + "Configure dvr uuid atom before moov atom to provide decryption information", + DEFAULT_DVR_ENCRYPTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + if (GST_QT_MUX_IS_FRAGMENTED (klass)) { + g_object_class_install_property (gobject_class, PROP_FRAGMENT_METHOD, + g_param_spec_enum ("fragment-method", "fragment-method", + "Method used to delimit fragments boundaries", + GST_TYPE_QT_MUX_FRAGMENT_METHOD, DEFAULT_FRAGMENT_METHOD, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + } + + gst_qt_mux_signals [SIGNAL_SEND_HEADER] = + g_signal_new ("send-header", G_TYPE_FROM_CLASS (klass), + (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET (GstQTMuxClass, send_header), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad); @@ -686,6 +775,15 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) g_array_unref (qtpad->samples); qtpad->samples = NULL; + g_list_free_full (qtpad->forcekeyunit_events, + (GDestroyNotify) gst_event_unref); + qtpad->forcekeyunit_events = NULL; + qtpad->next_fragment_ts = 0; + qtpad->first_fragment = TRUE; + + qtpad->segment_start = TRUE; + qtpad->segment_duration = 0; + /* reference owned elsewhere */ qtpad->tfra = NULL; @@ -698,6 +796,14 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) if (qtpad->raw_audio_adapter) gst_object_unref (qtpad->raw_audio_adapter); qtpad->raw_audio_adapter = NULL; + + /* reference owned elsewhere */ + if (qtpad->sidx) { + atom_sidx_free (qtpad->sidx); + qtpad->sidx = NULL; + } + + qtpad->subsegment_duration = 0; } /* @@ -713,6 +819,7 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) qtmux->mdat_size = 0; qtmux->moov_pos = 0; qtmux->mdat_pos = 0; + qtmux->moov_pos = 0; qtmux->longest_chunk = GST_CLOCK_TIME_NONE; qtmux->fragment_sequence = 0; @@ -728,6 +835,15 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) atom_mfra_free (qtmux->mfra); qtmux->mfra = NULL; } + + gst_buffer_replace (&qtmux->ftyp_buf, NULL); + gst_buffer_replace (&qtmux->post_ftyp_align_free_atom_buf, NULL); + gst_buffer_replace (&qtmux->empty_free_atom_buf, NULL); + gst_buffer_replace (&qtmux->moov_buf, NULL); + gst_buffer_replace (&qtmux->moov_free_ping_atom_buf, NULL); + gst_buffer_replace (&qtmux->moov_free_pong_atom_buf, NULL); + gst_buffer_replace (&qtmux->extra_atoms_buf, NULL); + if (qtmux->fast_start_file) { fclose (qtmux->fast_start_file); g_remove (qtmux->fast_start_file_path); @@ -779,6 +895,13 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc) qtmux->last_moov_update = GST_CLOCK_TIME_NONE; qtmux->muxed_since_last_update = 0; qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE; + + if (qtmux->sidx) { + atom_sidx_free (qtmux->sidx); + qtmux->sidx = NULL; + } + qtmux->sidx_pos = 0; + qtmux->reserved_sidx_size = 0; } static void @@ -789,6 +912,8 @@ gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass) templ = gst_element_class_get_pad_template (klass, "src"); qtmux->srcpad = gst_pad_new_from_template (templ, "src"); + gst_pad_set_event_function (qtmux->srcpad, + (GstPadEventFunction) gst_qt_mux_src_event); gst_pad_use_fixed_caps (qtmux->srcpad); gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad); @@ -802,6 +927,11 @@ gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux); /* properties set to default upon construction */ + if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML) { + qtmux->fragment_method = DEFAULT_FRAGMENT_METHOD; + } else { + qtmux->fragment_method = FRAGMENT_METHOD_NONE; + } qtmux->reserved_max_duration = DEFAULT_RESERVED_MAX_DURATION; qtmux->reserved_moov_update_period = DEFAULT_RESERVED_MOOV_UPDATE_PERIOD; @@ -1486,6 +1616,17 @@ static const GstTagToFourcc tag_matches_3gp[] = { /* qtdemux produces these for atoms it cannot parse */ #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag" +static void +gst_qt_mux_add_dvr_encryption_info (GstQTMux * qtmux) +{ + AtomInfo *ainfo; + ainfo = build_uuid_dvr_atom (); + + if (ainfo) { + qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo); + } +} + static void gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list) { @@ -1880,21 +2021,22 @@ gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos, } static GstFlowReturn -gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off) +gst_qt_mux_queue_ftyp (GstQTMux * qtmux) { GstBuffer *buf; guint64 size = 0, offset = 0; guint8 *data = NULL; - GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom"); + GST_DEBUG_OBJECT (qtmux, "Queueing ftyp atom"); if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset)) goto serialize_error; buf = _gst_buffer_new_take_data (data, offset); - GST_LOG_OBJECT (qtmux, "Pushing ftyp"); - return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + BUFFER_REPLACE_TAKE (qtmux->ftyp_buf, buf); + return GST_FLOW_OK; /* ERRORS */ serialize_error: @@ -1934,9 +2076,8 @@ gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp, } static GstFlowReturn -gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux) +gst_qt_mux_prepare_and_queue_ftyp (GstQTMux * qtmux) { - GstFlowReturn ret = GST_FLOW_OK; GstBuffer *prefix = NULL; GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom"); @@ -1948,39 +2089,209 @@ gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux) } gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix); if (prefix) { - ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE); - if (ret != GST_FLOW_OK) - return ret; + BUFFER_REPLACE_TAKE (qtmux->prefix_buf, prefix); } - return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size); + return gst_qt_mux_queue_ftyp (qtmux); } -static void -gst_qt_mux_set_header_on_caps (GstQTMux * mux, GstBuffer * buf) +static GstFlowReturn +gst_qt_mux_prepare_and_send_styp (GstQTMux * qtmux, guint32 major, + GList * brands) { - GstStructure *structure; - GValue array = { 0 }; - GValue value = { 0 }; - GstCaps *caps, *tcaps; + AtomFTYP *styp = NULL; + GstBuffer *buf; + guint64 size = 0, offset = 0; + guint8 *data = NULL; - tcaps = gst_pad_get_current_caps (mux->srcpad); - caps = gst_caps_copy (tcaps); - gst_caps_unref (tcaps); + GST_DEBUG_OBJECT (qtmux, "Preparing to send styp atom"); - structure = gst_caps_get_structure (caps, 0); + styp = atom_styp_new (qtmux->context, major, 0, brands); + if (styp == NULL) + goto create_error; - g_value_init (&array, GST_TYPE_ARRAY); + GST_DEBUG_OBJECT (qtmux, "Sending styp atom"); - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); - g_value_init (&value, GST_TYPE_BUFFER); - gst_value_take_buffer (&value, gst_buffer_ref (buf)); - gst_value_array_append_value (&array, &value); - g_value_unset (&value); + if (!atom_ftyp_copy_data (styp, &data, &size, &offset)) { + atom_ftyp_free (styp); + goto serialize_error; + } - gst_structure_set_value (structure, "streamheader", &array); - g_value_unset (&array); - gst_pad_set_caps (mux->srcpad, caps); - gst_caps_unref (caps); + buf = _gst_buffer_new_take_data (data, offset); + + GST_LOG_OBJECT (qtmux, "Pushing styp"); + atom_ftyp_free (styp); + return gst_qt_mux_send_buffer (qtmux, buf, &qtmux->header_size, FALSE); + + /* ERRORS */ +create_error: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to create styp")); + return GST_FLOW_ERROR; + } + +serialize_error: + { + GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), + ("Failed to serialize styp")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_qt_mux_send_headers (GstQTMux * mux, gboolean set_caps, + gboolean send_prefix_ftyp, gboolean send_moov_extra_atoms) +{ + GstFlowReturn ret = GST_FLOW_OK; + + if (set_caps) { + GstStructure *structure; + GValue array = { 0 }; + GValue value = { 0 }; + GstCaps *caps, *tcaps; + GstBuffer *buf; + + g_return_val_if_fail (mux->ftyp_buf != NULL && mux->moov_buf != NULL, + GST_FLOW_ERROR); + + tcaps = gst_pad_get_current_caps (mux->srcpad); + caps = gst_caps_copy (tcaps); + gst_caps_unref (tcaps); + + structure = gst_caps_get_structure (caps, 0); + + g_value_init (&array, GST_TYPE_ARRAY); + + if (mux->prefix_buf) { + buf = mux->prefix_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + + buf = mux->ftyp_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + + if (mux->post_ftyp_align_free_atom_buf) { + buf = mux->post_ftyp_align_free_atom_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + + if (mux->empty_free_atom_buf) { + buf = mux->empty_free_atom_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + + buf = mux->moov_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + + if (mux->moov_free_ping_atom_buf) { + buf = mux->moov_free_ping_atom_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + if (mux->moov_free_pong_atom_buf) { + buf = mux->moov_free_pong_atom_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + + if (mux->extra_atoms_buf) { + buf = mux->extra_atoms_buf; + g_value_init (&value, GST_TYPE_BUFFER); + gst_value_take_buffer (&value, gst_buffer_ref (buf)); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + } + + gst_structure_set_value (structure, "streamheader", &array); + g_value_unset (&array); + gst_pad_set_caps (mux->srcpad, caps); + gst_caps_unref (caps); + } + + if (send_prefix_ftyp) { + if (mux->prefix_buf) { + ret = gst_qt_mux_send_buffer (mux, gst_buffer_ref (mux->prefix_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + ret = gst_qt_mux_send_buffer (mux, gst_buffer_ref (mux->ftyp_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + if (mux->post_ftyp_align_free_atom_buf) { + ret = + gst_qt_mux_send_buffer (mux, + gst_buffer_ref (mux->post_ftyp_align_free_atom_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + if (mux->empty_free_atom_buf) { + ret = + gst_qt_mux_send_buffer (mux, + gst_buffer_ref (mux->empty_free_atom_buf), &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + mux->moov_pos = mux->header_size; + } + if (send_moov_extra_atoms) { + if (mux->dvr_encryption == TRUE && mux->extra_atoms_buf != NULL) { + ret = gst_qt_mux_send_buffer (mux, gst_buffer_ref (mux->extra_atoms_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + + gst_buffer_replace (&mux->extra_atoms_buf, NULL); + } + + ret = gst_qt_mux_send_buffer (mux, gst_buffer_ref (mux->moov_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + if (mux->moov_free_ping_atom_buf) { + ret = + gst_qt_mux_send_buffer (mux, + gst_buffer_ref (mux->moov_free_ping_atom_buf), &mux->header_size, + FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + if (mux->moov_free_pong_atom_buf) { + ret = + gst_qt_mux_send_buffer (mux, + gst_buffer_ref (mux->moov_free_pong_atom_buf), &mux->header_size, + FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + if (mux->extra_atoms_buf) { + ret = gst_qt_mux_send_buffer (mux, gst_buffer_ref (mux->extra_atoms_buf), + &mux->header_size, FALSE); + if (ret != GST_FLOW_OK) + return ret; + } + } + return ret; } /* @@ -1988,14 +2299,13 @@ gst_qt_mux_set_header_on_caps (GstQTMux * mux, GstBuffer * buf) * size, but a smaller buffer is sent */ static GstFlowReturn -gst_qt_mux_send_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size, - gboolean fsync_after) +gst_qt_mux_queue_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size, + gboolean fsync_after, GstBuffer ** dest) { Atom *node_header; GstBuffer *buf; guint8 *data = NULL; guint64 offset = 0, bsize = 0; - GstFlowReturn ret; GST_DEBUG_OBJECT (qtmux, "Sending free atom header of size %u", size); @@ -2008,18 +2318,21 @@ gst_qt_mux_send_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size, node_header->size = size; bsize = offset = 0; + data = g_malloc (size); if (atom_copy_data (node_header, &data, &bsize, &offset) == 0) goto serialize_error; - buf = _gst_buffer_new_take_data (data, offset); + buf = _gst_buffer_new_take_data (data, size); g_free (node_header); if (fsync_after) GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER); GST_LOG_OBJECT (qtmux, "Pushing free atom"); - ret = gst_qt_mux_send_buffer (qtmux, buf, off, FALSE); + BUFFER_REPLACE_TAKE (*dest, buf); +#if 0 + /* We lost this optimization when we had to put stuff in streamheaders */ if (off) { GstSegment segment; @@ -2030,8 +2343,9 @@ gst_qt_mux_send_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size, segment.start = *off; gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); } +#endif - return ret; + return GST_FLOW_OK; /* ERRORS */ too_small: @@ -2074,7 +2388,7 @@ gst_qt_mux_configure_moov (GstQTMux * qtmux) } static GstFlowReturn -gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset, +gst_qt_mux_queue_moov (GstQTMux * qtmux, guint64 * _offset, guint64 padded_moov_size, gboolean mind_fast, gboolean fsync_after) { guint64 offset = 0, size = 0; @@ -2108,22 +2422,25 @@ gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset, buf = _gst_buffer_new_take_data (data, offset); GST_DEBUG_OBJECT (qtmux, "Pushing moov atoms"); +#if 0 /* If at EOS, this is the final moov, put in the streamheader * (apparently used by a flumotion util) */ if (qtmux->state == GST_QT_MUX_STATE_EOS) gst_qt_mux_set_header_on_caps (qtmux, buf); +#endif if (fsync_after) GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER); - ret = gst_qt_mux_send_buffer (qtmux, buf, _offset, mind_fast); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + BUFFER_REPLACE_TAKE (qtmux->moov_buf, buf); /* Write out a free atom if needed */ if (ret == GST_FLOW_OK && offset < padded_moov_size) { GST_LOG_OBJECT (qtmux, "Writing out free atom of size %u", (guint32) (padded_moov_size - offset)); ret = - gst_qt_mux_send_free_atom (qtmux, _offset, padded_moov_size - offset, - fsync_after); + gst_qt_mux_queue_free_atom (qtmux, _offset, padded_moov_size - offset, + fsync_after, &qtmux->moov_free_ping_atom_buf); } return ret; @@ -2144,37 +2461,36 @@ gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset, /* either calculates size of extra atoms or pushes them */ static GstFlowReturn -gst_qt_mux_send_extra_atoms (GstQTMux * qtmux, gboolean send, guint64 * offset, - gboolean mind_fast) +gst_qt_mux_queue_extra_atoms (GstQTMux * qtmux) { GSList *walk; guint64 loffset = 0, size = 0; guint8 *data; GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buf; + + gst_buffer_replace (&qtmux->extra_atoms_buf, NULL); for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) { AtomInfo *ainfo = (AtomInfo *) walk->data; loffset = size = 0; data = NULL; - if (!ainfo->copy_data_func (ainfo->atom, - send ? &data : NULL, &size, &loffset)) + if (!ainfo->copy_data_func (ainfo->atom, &data, &size, &loffset)) goto serialize_error; - if (send) { - GstBuffer *buf; - - GST_DEBUG_OBJECT (qtmux, - "Pushing extra top-level atom %" GST_FOURCC_FORMAT, - GST_FOURCC_ARGS (ainfo->atom->type)); - buf = _gst_buffer_new_take_data (data, loffset); - ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE); - if (ret != GST_FLOW_OK) - break; + GST_DEBUG_OBJECT (qtmux, + "Pushing extra top-level atom %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (ainfo->atom->type)); + buf = _gst_buffer_new_take_data (data, loffset); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + if (qtmux->extra_atoms_buf == NULL) { + qtmux->extra_atoms_buf = gst_buffer_copy (buf); } else { - if (offset) - *offset += loffset; + gst_buffer_copy_into (qtmux->extra_atoms_buf, buf, + GST_BUFFER_COPY_MEMORY, 0, -1); } + gst_buffer_unref (buf); } return ret; @@ -2714,6 +3030,136 @@ gst_qt_mux_prefill_samples (GstQTMux * qtmux) return TRUE; } +static void +gst_qt_mux_queue_dummy_sidx_atom (GstQTMux * qtmux, guint32 entry_num, + GstBuffer ** dest) +{ + AtomSIDX *dummy_sidx; + GstBuffer *buf; + guint i; + guint8 *data = NULL; + guint64 offset = 0, size = 0; + + GST_DEBUG_OBJECT (qtmux, "make dummy sidx buffer"); + dummy_sidx = atom_sidx_new (qtmux->context, 0, 0, 0, 0, 0); + + for (i = 0; i < entry_num; i++) { + atom_sidx_add_entry (dummy_sidx, FALSE, 0, 0, FALSE, 0, 0); + } + atom_sidx_copy_data (dummy_sidx, &data, &size, &offset); + buf = _gst_buffer_new_take_data (data, offset); + qtmux->reserved_sidx_size = gst_buffer_get_size (buf); + + atom_sidx_free (dummy_sidx); + + BUFFER_REPLACE_TAKE (*dest, buf); + + return; +} + +static GstFlowReturn +gst_qt_mux_prepare_and_send_dummy_sidx (GstQTMux * qtmux, GstQTPad * pad, + gint64 dts) +{ + GstBuffer *buffer = NULL; + guint entry_num = 0; + + GST_DEBUG_OBJECT (qtmux, "Preparing sidx atom"); + qtmux->sidx = atom_sidx_new (qtmux->context, atom_trak_get_id (pad->trak), + dts, atom_trak_get_timescale (pad->trak), + gst_util_uint64_scale (pad->first_ts, atom_trak_get_timescale (pad->trak), + GST_SECOND), 0); + qtmux->sidx_pos = qtmux->header_size; + + entry_num = qtmux->segment_duration / qtmux->fragment_duration; + GST_LOG_OBJECT (qtmux, "expected sidx box entry num[%d]", entry_num); + + gst_qt_mux_queue_dummy_sidx_atom (qtmux, entry_num, &buffer); + if (buffer == NULL) + return GST_FLOW_ERROR; + + GST_LOG_OBJECT (qtmux, "Pushing dummy sidx"); + return gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE); +} + +/* + * seek back to sidx start offset and overwrite with the real value + */ +static GstFlowReturn +gst_qt_mux_update_sidx (GstQTMux * qtmux, guint64 sidx_pos) +{ + GstSegment segment; + guint8 *data = NULL; + guint64 offset = 0, size = 0; + guint32 actual_sidx_size = 0; + guint free_atom_size = 0; + GstBuffer *buf = NULL, *free_atom_buf = NULL; + GstFlowReturn ret = GST_FLOW_OK; + + /* We must have recorded the mdat position for this to work */ + g_assert (sidx_pos != 0); + + /* seek and rewrite the header */ + gst_segment_init (&segment, GST_FORMAT_BYTES); + segment.start = sidx_pos; + gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); + + if (!atom_sidx_copy_data (qtmux->sidx, &data, &size, &offset)) + return GST_FLOW_ERROR; + + buf = _gst_buffer_new_take_data (data, offset); + actual_sidx_size = gst_buffer_get_size (buf); + + if (actual_sidx_size > qtmux->reserved_sidx_size + RESERVED_SIDX_FREE_SPACE) + return GST_FLOW_ERROR; + + if (actual_sidx_size == qtmux->reserved_sidx_size + RESERVED_SIDX_FREE_SPACE) { + return gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE); + } else if (actual_sidx_size == qtmux->reserved_sidx_size) { + free_atom_size = RESERVED_SIDX_FREE_SPACE; + } else if (actual_sidx_size > qtmux->reserved_sidx_size) { + free_atom_size = + RESERVED_SIDX_FREE_SPACE - (actual_sidx_size - + qtmux->reserved_sidx_size); + } else if (actual_sidx_size < qtmux->reserved_sidx_size) { + free_atom_size = + RESERVED_SIDX_FREE_SPACE + (qtmux->reserved_sidx_size - + actual_sidx_size); + } + + GST_DEBUG_OBJECT (qtmux, "adjusted sidx first_offset size [%d]", + free_atom_size); + qtmux->sidx->first_offset = free_atom_size; + gst_buffer_unref (buf); + buf = NULL; + data = NULL; + size = 0; + offset = 0; + + if (!atom_sidx_copy_data (qtmux->sidx, &data, &size, &offset)) + return GST_FLOW_ERROR; + + buf = _gst_buffer_new_take_data (data, offset); + ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE); + + if (ret != GST_FLOW_OK) + return GST_FLOW_ERROR; + + ret = gst_qt_mux_queue_free_atom (qtmux, NULL, free_atom_size, + FALSE, &free_atom_buf); + + if (ret == GST_FLOW_OK) { + ret = gst_qt_mux_send_buffer (qtmux, free_atom_buf, NULL, FALSE); + /* seek to recover write position */ + gst_segment_init (&segment, GST_FORMAT_BYTES); + segment.start = qtmux->header_size; + gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); + return ret; + } + + return ret; +} + static GstFlowReturn gst_qt_mux_start_file (GstQTMux * qtmux) { @@ -2753,7 +3199,8 @@ gst_qt_mux_start_file (GstQTMux * qtmux) qtmux->fragment_duration == 0) goto invalid_isml; - if (qtmux->fragment_duration > 0) { + if (qtmux->fragment_duration > 0 + || qtmux->fragment_method != FRAGMENT_METHOD_NONE) { if (qtmux->streamable) qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE; else @@ -2860,10 +3307,11 @@ gst_qt_mux_start_file (GstQTMux * qtmux) */ switch (qtmux->mux_mode) { case GST_QT_MUX_MODE_MOOV_AT_END: - ret = gst_qt_mux_prepare_and_send_ftyp (qtmux); + ret = gst_qt_mux_prepare_and_queue_ftyp (qtmux); if (ret != GST_FLOW_OK) break; + gst_qt_mux_send_headers (qtmux, FALSE, TRUE, FALSE); /* Store this as the mdat offset for later updating * when we write the moov */ qtmux->mdat_pos = qtmux->header_size; @@ -2874,7 +3322,7 @@ gst_qt_mux_start_file (GstQTMux * qtmux) FALSE); break; case GST_QT_MUX_MODE_ROBUST_RECORDING: - ret = gst_qt_mux_prepare_and_send_ftyp (qtmux); + ret = gst_qt_mux_prepare_and_queue_ftyp (qtmux); if (ret != GST_FLOW_OK) break; @@ -2888,8 +3336,8 @@ gst_qt_mux_start_file (GstQTMux * qtmux) guint padding = (guint) (16 - (qtmux->header_size % 8)); GST_LOG_OBJECT (qtmux, "Rounding ftyp by %u bytes", padding); ret = - gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, padding, - FALSE); + gst_qt_mux_queue_free_atom (qtmux, &qtmux->header_size, padding, + FALSE, &qtmux->post_ftyp_align_free_atom_buf); if (ret != GST_FLOW_OK) return ret; } @@ -2904,11 +3352,13 @@ gst_qt_mux_start_file (GstQTMux * qtmux) gst_qt_mux_configure_moov (qtmux); gst_qt_mux_setup_metadata (qtmux); /* Empty free atom to begin, starting on an 8-byte boundary */ - ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, 8, FALSE); + ret = + gst_qt_mux_queue_free_atom (qtmux, &qtmux->header_size, 8, FALSE, + &qtmux->empty_free_atom_buf); if (ret != GST_FLOW_OK) return ret; /* Moov header, not padded yet */ - ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE); if (ret != GST_FLOW_OK) return ret; /* The moov we just sent contains the 'base' size of the moov, before @@ -2942,24 +3392,28 @@ gst_qt_mux_start_file (GstQTMux * qtmux) /* Now that we know how much reserved space is targetted, * output a free atom to fill the extra reserved */ - ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, - qtmux->reserved_moov_size - qtmux->base_moov_size, FALSE); + ret = gst_qt_mux_queue_free_atom (qtmux, &qtmux->header_size, + qtmux->reserved_moov_size - qtmux->base_moov_size, FALSE, + &qtmux->moov_free_ping_atom_buf); if (ret != GST_FLOW_OK) return ret; /* Then a free atom containing 'pong' buffer, with an * extra 8 bytes to account for the free atom header itself */ - ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, - qtmux->reserved_moov_size + 8, FALSE); + ret = gst_qt_mux_queue_free_atom (qtmux, &qtmux->header_size, + qtmux->reserved_moov_size + 8, FALSE, + &qtmux->moov_free_pong_atom_buf); if (ret != GST_FLOW_OK) return ret; /* extra atoms go after the free/moov(s), before the mdat */ - ret = - gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE); + ret = gst_qt_mux_queue_extra_atoms (qtmux); if (ret != GST_FLOW_OK) return ret; + /* Send the whole header (prefix + ftyp + moov + extra_atoms */ + gst_qt_mux_send_headers (qtmux, TRUE, TRUE, TRUE); + qtmux->mdat_pos = qtmux->header_size; /* extended atom in case we go over 4GB while writing and need * the full 64-bit atom */ @@ -2968,7 +3422,7 @@ gst_qt_mux_start_file (GstQTMux * qtmux) FALSE); break; case GST_QT_MUX_MODE_ROBUST_RECORDING_PREFILL: - ret = gst_qt_mux_prepare_and_send_ftyp (qtmux); + ret = gst_qt_mux_prepare_and_queue_ftyp (qtmux); if (ret != GST_FLOW_OK) break; @@ -2989,7 +3443,7 @@ gst_qt_mux_start_file (GstQTMux * qtmux) gst_qt_mux_setup_metadata (qtmux); /* Moov header with pre-filled samples */ - ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE); if (ret != GST_FLOW_OK) return ret; @@ -3001,15 +3455,19 @@ gst_qt_mux_start_file (GstQTMux * qtmux) /* Send an additional free atom at the end so we definitely have space * to rewrite the moov header at the end and remove the samples that * were not actually written */ + /* FIXME : gst_qt_mux_send_free_atom api is changed to + gst_qt_mux_queue_free_atom */ + /* &qtmux->moov_buf is delivered to the api. need to fix it. */ ret = - gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, - 12 * g_slist_length (qtmux->sinkpads) + 8, FALSE); + gst_qt_mux_queue_free_atom (qtmux, &qtmux->header_size, + 12 * g_slist_length (qtmux->sinkpads) + 8, FALSE, &qtmux->moov_buf); if (ret != GST_FLOW_OK) return ret; /* extra atoms go after the free/moov(s), before the mdat */ - ret = - gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE); + /* FIXME : gst_qt_mux_send_extra_atoms api is changed to + gst_qt_mux_queue_extra_atoms. maybe need to fix it. */ + ret = gst_qt_mux_queue_extra_atoms (qtmux); if (ret != GST_FLOW_OK) return ret; @@ -3031,7 +3489,7 @@ gst_qt_mux_start_file (GstQTMux * qtmux) segment.start = qtmux->moov_pos; gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); - ret = gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, NULL, 0, FALSE, FALSE); if (ret != GST_FLOW_OK) return ret; @@ -3069,31 +3527,47 @@ gst_qt_mux_start_file (GstQTMux * qtmux) break; case GST_QT_MUX_MODE_FRAGMENTED: case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE: - ret = gst_qt_mux_prepare_and_send_ftyp (qtmux); + ret = gst_qt_mux_prepare_and_queue_ftyp (qtmux); if (ret != GST_FLOW_OK) break; /* store the moov pos so we can update the duration later * in non-streamable mode */ qtmux->moov_pos = qtmux->header_size; - GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers", - qtmux->fragment_duration); + if (qtmux->fragment_method != FRAGMENT_METHOD_NONE) { + if (qtmux->fragment_method == FRAGMENT_METHOD_TIME) { + GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers", + qtmux->fragment_duration); + } else if (qtmux->fragment_method == FRAGMENT_METHOD_EVENT) { + GST_DEBUG_OBJECT (qtmux, + "fragment with GstForceKeyUnit events, writing headers"); + } + } /* also used as snapshot marker to indicate fragmented file */ qtmux->fragment_sequence = 1; /* prepare moov and/or tags */ gst_qt_mux_configure_moov (qtmux); gst_qt_mux_setup_metadata (qtmux); - ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, &qtmux->header_size, 0, FALSE, TRUE); if (ret != GST_FLOW_OK) return ret; + + /* prepare dvr encryption uuid info */ + if (qtmux->dvr_encryption == TRUE) + gst_qt_mux_add_dvr_encryption_info (qtmux); /* extra atoms */ - ret = - gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE); + ret = gst_qt_mux_queue_extra_atoms (qtmux); if (ret != GST_FLOW_OK) break; /* prepare index if not streamable */ if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED) qtmux->mfra = atom_mfra_new (qtmux->context); + + ret = gst_qt_mux_send_headers (qtmux, TRUE, TRUE, TRUE); + if (ret == GST_FLOW_OK) + g_signal_emit (qtmux, gst_qt_mux_signals[SIGNAL_SEND_HEADER], 0, + NULL); + break; } @@ -3366,6 +3840,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) { /* Streamable mode; no need to write duration or MFRA */ GST_DEBUG_OBJECT (qtmux, "streamable file; nothing to stop"); + return GST_FLOW_OK; } @@ -3409,7 +3884,10 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) segment.start = qtmux->moov_pos; gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); /* no need to seek back */ - return gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, NULL, 0, FALSE, FALSE); + if (ret == GST_FLOW_OK) + ret = gst_qt_mux_send_headers (qtmux, TRUE, FALSE, TRUE); + return ret; } case GST_QT_MUX_MODE_ROBUST_RECORDING:{ ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux); @@ -3580,15 +4058,19 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); ret = - gst_qt_mux_send_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE, - FALSE); + gst_qt_mux_queue_moov (qtmux, NULL, qtmux->reserved_moov_size, + FALSE, FALSE); if (ret != GST_FLOW_OK) return ret; if (qtmux->reserved_moov_size > qtmux->last_moov_size) { + /* FIXME : gst_qt_mux_send_free_atom api is changed to + gst_qt_mux_queue_free_atom */ + /* &qtmux->moov_buf is delivered to the api. need to fix it. */ ret = - gst_qt_mux_send_free_atom (qtmux, NULL, - qtmux->reserved_moov_size - qtmux->last_moov_size, TRUE); + gst_qt_mux_queue_free_atom (qtmux, NULL, + qtmux->reserved_moov_size - qtmux->last_moov_size, TRUE, + &qtmux->moov_buf); } if (ret != GST_FLOW_OK) @@ -3620,7 +4102,7 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) * Also, send the ftyp */ offset = size = 0; - ret = gst_qt_mux_prepare_and_send_ftyp (qtmux); + ret = gst_qt_mux_prepare_and_queue_ftyp (qtmux); if (ret != GST_FLOW_OK) { goto ftyp_error; } @@ -3632,9 +4114,12 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) offset += qtmux->header_size + (large_file ? 16 : 8); /* sum up with the extra atoms size */ - ret = gst_qt_mux_send_extra_atoms (qtmux, FALSE, &offset, FALSE); +#if 0 + /* FIXME calculate extra atoms size and add to offset */ + ret = gst_qt_mux_queue_extra_atoms (qtmux); if (ret != GST_FLOW_OK) return ret; +#endif break; } default: @@ -3642,6 +4127,10 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) break; } + ret = gst_qt_mux_queue_extra_atoms (qtmux); + if (ret != GST_FLOW_OK) + return ret; + /* Now that we know the size of moov + extra atoms, we can adjust * the chunk offsets stored into the moov */ atom_moov_chunks_set_offset (qtmux->moov, offset); @@ -3649,14 +4138,14 @@ gst_qt_mux_stop_file (GstQTMux * qtmux) /* write out moov and extra atoms */ /* note: as of this point, we no longer care about tracking written data size, * since there is no more use for it anyway */ - ret = gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE); + ret = gst_qt_mux_queue_moov (qtmux, NULL, 0, FALSE, FALSE); if (ret != GST_FLOW_OK) return ret; - /* extra atoms */ - ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE); - if (ret != GST_FLOW_OK) - return ret; + /* Send the remaing header buffers (moov + extra atoms) + * and if it's a fast_start file send the whole header. + * Also update streamheader in caps */ + gst_qt_mux_send_headers (qtmux, TRUE, qtmux->fast_start, TRUE); switch (qtmux->mux_mode) { case GST_QT_MUX_MODE_MOOV_AT_END: @@ -3708,6 +4197,8 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, guint32 delta, guint32 size, gboolean sync, gint64 pts_offset) { GstFlowReturn ret = GST_FLOW_OK; + gboolean is_kf, pad_sync, event_sync, time_sync; + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); /* setup if needed */ if (G_UNLIKELY (!pad->traf || force)) @@ -3716,13 +4207,58 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, flush: /* flush pad fragment if threshold reached, * or at new keyframe if we should be minding those in the first place */ - if (G_UNLIKELY (force || (sync && pad->sync) || - pad->fragment_duration < (gint64) delta)) { + is_kf = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + /* if qtmux has segment_duration value(use sidx atom), + * video stream's pad_sync value should not be triggered + * to match the number of audio and video sidx entries (fragmented data) */ + pad_sync = qtmux->fragment_method != FRAGMENT_METHOD_EVENT && (sync + && pad->sync) && (qtmux->segment_duration == 0); + event_sync = is_kf && pad->forcekeyunit_events != NULL && + GST_BUFFER_PTS (buf) >= pad->next_fragment_ts; + time_sync = (is_kf || qtmux->segment_duration != 0) && + pad->fragment_duration < (guint64) delta; + + if (event_sync && pad->first_fragment) { + pad->first_fragment = FALSE; + gst_qt_mux_dequeue_force_key_unit_event (qtmux, pad); + goto init; + } + + if (G_UNLIKELY (force || pad_sync || time_sync || event_sync)) { AtomMOOF *moof; guint64 size = 0, offset = 0; guint8 *data = NULL; GstBuffer *buffer; guint i, total_size; + guint32 referenced_size = 0; + + if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { + GST_WARNING ("Next fragment will not start with a keyframe"); + } + + GST_LOG_OBJECT (qtmux, "pushing previous fragment"); + + if (qtmux_klass->format == GST_QT_MUX_FORMAT_DASH + && pad->segment_start == TRUE) { + GList *brands = NULL; + gst_qt_mux_prepare_and_send_styp (qtmux, FOURCC_msdh, brands); + + if (qtmux->segment_duration != 0) { + GstBuffer *free_atom_buf = NULL; + pad->segment_start = FALSE; + pad->segment_duration = gst_util_uint64_scale (qtmux->segment_duration, + atom_trak_get_timescale (pad->trak), 1000); + gst_qt_mux_prepare_and_send_dummy_sidx (qtmux, pad, dts); + /* reserve free space */ + if (gst_qt_mux_queue_free_atom (qtmux, NULL, RESERVED_SIDX_FREE_SPACE, + FALSE, &free_atom_buf) != GST_FLOW_OK) + return GST_FLOW_ERROR; + + if (gst_qt_mux_send_buffer (qtmux, free_atom_buf, + &qtmux->header_size, FALSE) != GST_FLOW_OK) + return GST_FLOW_ERROR; + } + } /* now we know where moof ends up, update offset in tfra */ if (pad->tfra) @@ -3736,6 +4272,7 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, buffer = _gst_buffer_new_take_data (data, offset); GST_LOG_OBJECT (qtmux, "writing moof size %" G_GSIZE_FORMAT, gst_buffer_get_size (buffer)); + referenced_size += gst_buffer_get_size (buffer); ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE); /* and actual data */ @@ -3747,6 +4284,8 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d", atom_array_get_len (&pad->fragment_buffers), total_size); + /* add mdat header + data size */ + referenced_size = referenced_size + total_size + 8; if (ret == GST_FLOW_OK) ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size, FALSE, FALSE); @@ -3759,6 +4298,15 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i)); } + if (qtmux->sidx) { + atom_sidx_add_entry (qtmux->sidx, FALSE, referenced_size, + pad->subsegment_duration, FALSE, 0, 0); + pad->subsegment_duration = 0; + ret = gst_qt_mux_update_sidx (qtmux, qtmux->sidx_pos); + } + + gst_qt_mux_dequeue_force_key_unit_event (qtmux, pad); + atom_array_clear (&pad->fragment_buffers); atom_moof_free (moof); qtmux->fragment_sequence++; @@ -3770,8 +4318,13 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GST_LOG_OBJECT (qtmux, "setting up new fragment"); pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak)); atom_array_init (&pad->fragment_buffers, 512); - pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration, - atom_trak_get_timescale (pad->trak), 1000); + if (qtmux->fragment_method == FRAGMENT_METHOD_TIME) + pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration, + atom_trak_get_timescale (pad->trak), 1000); + else { + /* use a the highest value here so that this condition is never met */ + pad->fragment_duration = G_MAXUINT32; + } if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) { pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak)); @@ -3785,6 +4338,13 @@ gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad, pad->sync && sync); atom_array_append (&pad->fragment_buffers, buf, 256); pad->fragment_duration -= delta; + pad->segment_duration -= delta; + pad->subsegment_duration += delta; + + if (pad->fragment_duration < 0) + pad->fragment_duration = 0; + if (pad->segment_duration <= 0) + pad->segment_start = TRUE; if (pad->tfra) { guint32 sn = atom_traf_get_sample_num (pad->traf); @@ -3866,7 +4426,7 @@ gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux) gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); ret = - gst_qt_mux_send_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE, + gst_qt_mux_queue_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE, TRUE); if (ret != GST_FLOW_OK) return ret; @@ -3911,7 +4471,9 @@ gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux) segment.start = freeA_offset; gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment)); - ret = gst_qt_mux_send_free_atom (qtmux, NULL, new_freeA_size, TRUE); + ret = + gst_qt_mux_queue_free_atom (qtmux, NULL, new_freeA_size, TRUE, + &qtmux->moov_free_ping_atom_buf); return ret; } @@ -4019,6 +4581,21 @@ gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad, GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Unexpected values in sample %" G_GUINT64_FORMAT, pad->sample_offset + 1)); + GST_ERROR_OBJECT (qtmux, "Expected: samples %u, delta %u, size %u, " + "chunk offset %" G_GUINT64_FORMAT ", " + "pts offset %" G_GUINT64_FORMAT ", sync %d", + sample_entry->nsamples, + sample_entry->delta, + sample_entry->size, + sample_entry->chunk_offset, + sample_entry->pts_offset, sample_entry->sync); + GST_ERROR_OBJECT (qtmux, "Got: samples %u, delta %u, size %u, " + "chunk offset %" G_GUINT64_FORMAT ", " + "pts offset %" G_GUINT64_FORMAT ", sync %d", + nsamples, + (guint) scaled_duration, + sample_size, chunk_offset, pts_offset, sync); + gst_buffer_unref (buffer); return GST_FLOW_ERROR; } @@ -4815,6 +5392,11 @@ check_field (GQuark field_id, const GValue * value, gpointer user_data) const GValue *other = gst_structure_id_get_value (structure, field_id); if (other == NULL) return FALSE; + else if (strcmp (g_quark_to_string (field_id), "framerate") == 0) + return TRUE; + else if (strcmp (g_quark_to_string (field_id), "codec_data") == 0) + return TRUE; + return gst_value_compare (value, other) == GST_VALUE_EQUAL; } @@ -4838,7 +5420,10 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) const gchar *mimetype; gint rate, channels; const GValue *value = NULL; - const GstBuffer *codec_data = NULL; + GstBuffer *codec_data = NULL; + guint8 codec_data_arr[2]; + guint16 codec_data_data; + gint sample_rate_idx; GstQTMuxFormat format; AudioSampleEntry entry = { 0, }; AtomInfo *ext_atom = NULL; @@ -4946,7 +5531,6 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) if (strcmp (stream_format, "raw") != 0) { GST_WARNING_OBJECT (qtmux, "Unsupported AAC stream-format %s, " "please use 'raw'", stream_format); - goto refuse_caps; } } else { GST_WARNING_OBJECT (qtmux, "No stream-format present in caps, " @@ -4955,7 +5539,26 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) if (!codec_data || gst_buffer_get_size ((GstBuffer *) codec_data) < 2) { GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio"); - goto refuse_caps; + + sample_rate_idx = + gst_codec_utils_aac_get_index_from_sample_rate (rate); + if (sample_rate_idx < 0) { + GST_WARNING_OBJECT (qtmux, "not_a_known_rate"); + goto refuse_caps; + } + codec_data_data = (sample_rate_idx << 7) | (channels << 3); + GST_WRITE_UINT16_BE (codec_data_arr, codec_data_data); + + if (codec_data) { + GST_DEBUG_OBJECT (qtmux, "free original codec data"); + g_free (codec_data); + } + codec_data = gst_buffer_new_and_alloc (2); + + gst_buffer_fill (codec_data, 0, codec_data_arr, 2); + GST_DEBUG_OBJECT (qtmux, + "make codec_data rate=%d, channel=%d, codec_data=%x%x", + rate, channels, codec_data_arr[0], codec_data_arr[1]); } else { guint8 profile; @@ -4982,6 +5585,8 @@ gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) default: break; } + } else if (strcmp (mimetype, "audio/mpeg-h") == 0) { + entry.fourcc = FOURCC_mhm1; } else if (strcmp (mimetype, "audio/AMR") == 0) { entry.fourcc = FOURCC_samr; entry.sample_size = 16; @@ -5758,6 +6363,154 @@ gst_qt_mux_video_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) } } +static gint +_sort_fku_events (GstEvent * e1, GstEvent * e2) +{ + GstClockTime ts1, ts2; + + gst_structure_get_clock_time (gst_event_get_structure (e1), + "running-time", &ts1); + gst_structure_get_clock_time (gst_event_get_structure (e2), + "running-time", &ts2); + + return (gint) (ts1 - ts2); +} + +static gint +_compare_fku_events (GstEvent * ev1, GstEvent * ev2) +{ + guint event_count_1, event_count_2; + + gst_structure_get_uint (gst_event_get_structure (ev1), + "count", &event_count_1); + gst_structure_get_uint (gst_event_get_structure (ev2), + "count", &event_count_2); + + return event_count_2 - event_count_1; +} + +static void +gst_qt_mux_enqueue_force_key_unit_event (GstQTMux * qtmux, GstEvent * event, + GstQTPad * pad) +{ + GSList *walk; + + GST_DEBUG_OBJECT (qtmux, "Queueing ForceKeyUnit event %p", event); + + GST_OBJECT_LOCK (qtmux); + for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) { + GstClockTime event_ts; + const GstStructure *s; + GstQTPad *qtpad = (GstQTPad *) walk->data; + + /* Downstream events applies to a single pad */ + if (pad != NULL && qtpad != pad) + continue; + + if (g_list_find_custom (qtpad->forcekeyunit_events, event, + (GCompareFunc) _compare_fku_events) != NULL) { + GST_DEBUG_OBJECT (qtmux, "Dropping duplicated ForceKeyUnit event %p", + event); + continue; + } + + gst_structure_get_clock_time (gst_event_get_structure (event), + "running-time", &event_ts); + if (qtpad->last_dts > event_ts) { + GST_DEBUG_OBJECT (qtmux, "Dropping late ForceKeyUnit event %p", event); + continue; + } + qtpad->forcekeyunit_events = + g_list_insert_sorted (qtpad->forcekeyunit_events, gst_event_ref (event), + (GCompareFunc) _sort_fku_events); + + s = gst_event_get_structure ((GstEvent *) + g_list_first (qtpad->forcekeyunit_events)->data); + gst_structure_get_clock_time (s, "running-time", &qtpad->next_fragment_ts); + GST_DEBUG_OBJECT (qtmux, "Next fragment will start at %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtpad->next_fragment_ts)); + } + gst_event_unref (event); + GST_OBJECT_UNLOCK (qtmux); +} + +static void +gst_qt_mux_dequeue_force_key_unit_event (GstQTMux * qtmux, GstQTPad * pad) +{ + GSList *walk; + GstEvent *event; + + if (pad->forcekeyunit_events == NULL) { + GST_WARNING_OBJECT (qtmux, "No events queued"); + return; + } + + GST_LOG_OBJECT (qtmux, "resending GstForceKeyUnit event downstream"); + event = (GstEvent *) g_list_first (pad->forcekeyunit_events)->data; + gst_event_ref (event); + + /* Remove the event from all the sink pads now that we have created the + * fragment */ + for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) { + GstEvent *dup_ev; + GstQTPad *qtpad = (GstQTPad *) walk->data; + + dup_ev = (GstEvent *) g_list_find_custom (qtpad->forcekeyunit_events, event, + (GCompareFunc) _compare_fku_events)->data; + + if (dup_ev != NULL) { + qtpad->forcekeyunit_events = g_list_remove (qtpad->forcekeyunit_events, + dup_ev); + gst_event_unref (dup_ev); + } + } + + event->type = GST_EVENT_CUSTOM_DOWNSTREAM; + gst_pad_push_event (qtmux->srcpad, event); + if (pad->forcekeyunit_events == NULL) { + pad->next_fragment_ts = GST_CLOCK_TIME_NONE; + } else { + event = (GstEvent *) g_list_first (pad->forcekeyunit_events)->data; + gst_structure_get_clock_time (gst_event_get_structure (event), + "running-time", &pad->next_fragment_ts); + } +} + +static gboolean +gst_qt_mux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + GstQTMux *qtmux; + gboolean ret = TRUE; + + qtmux = GST_QT_MUX_CAST (parent); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_UPSTREAM:{ + if (qtmux->fragment_method == FRAGMENT_METHOD_EVENT) { + if (gst_video_event_is_force_key_unit (event)) { + gst_qt_mux_enqueue_force_key_unit_event (qtmux, + gst_event_ref (event), NULL); + } + } + break; + } + default: + break; + } + + if (event != NULL) { + GSList *walk; + + for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) { + GstQTPad *qtpad = (GstQTPad *) walk->data; + gst_event_ref (event); + ret &= gst_pad_push_event (qtpad->collect.pad, event); + } + gst_event_unref (event); + } + + return ret; +} + static gboolean gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) { @@ -5923,6 +6676,17 @@ gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data, ret = TRUE; break; } + case GST_EVENT_CUSTOM_DOWNSTREAM:{ + if (qtmux->fragment_method == FRAGMENT_METHOD_EVENT) { + if (gst_video_event_is_force_key_unit (event)) { + GstQTPad *qtpad = gst_pad_get_element_private (pad); + gst_qt_mux_enqueue_force_key_unit_event (qtmux, event, qtpad); + event = NULL; + ret = TRUE; + } + } + break; + } default: break; } @@ -6060,6 +6824,7 @@ gst_qt_mux_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstQTMux *qtmux = GST_QT_MUX_CAST (object); + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); GST_OBJECT_LOCK (qtmux); switch (prop_id) { @@ -6087,10 +6852,15 @@ gst_qt_mux_get_property (GObject * object, g_value_set_string (value, qtmux->moov_recov_file_path); break; case PROP_FRAGMENT_DURATION: - g_value_set_uint (value, qtmux->fragment_duration); + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + g_value_set_uint (value, qtmux->fragment_duration); + + } break; case PROP_STREAMABLE: - g_value_set_boolean (value, qtmux->streamable); + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + g_value_set_boolean (value, qtmux->streamable); + } break; case PROP_RESERVED_MAX_DURATION: g_value_set_uint64 (value, qtmux->reserved_max_duration); @@ -6132,6 +6902,19 @@ gst_qt_mux_get_property (GObject * object, case PROP_MAX_RAW_AUDIO_DRIFT: g_value_set_uint64 (value, qtmux->max_raw_audio_drift); break; + case PROP_FRAGMENT_METHOD: + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + g_value_set_enum (value, qtmux->fragment_method); + } + break; + case PROP_SEGMENT_DURATION: + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + g_value_set_uint (value, qtmux->segment_duration); + } + break; + case PROP_DVR_ENCRYPTION: + g_value_set_boolean (value, qtmux->dvr_encryption); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -6157,6 +6940,7 @@ gst_qt_mux_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstQTMux *qtmux = GST_QT_MUX_CAST (object); + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); GST_OBJECT_LOCK (qtmux); switch (prop_id) { @@ -6190,12 +6974,16 @@ gst_qt_mux_set_property (GObject * object, qtmux->moov_recov_file_path = g_value_dup_string (value); break; case PROP_FRAGMENT_DURATION: - qtmux->fragment_duration = g_value_get_uint (value); + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + qtmux->fragment_duration = g_value_get_uint (value); + + if (qtmux->fragment_duration > 0) + /* automatically set fragmented-method to duration */ + qtmux->fragment_method = FRAGMENT_METHOD_TIME; + } break; case PROP_STREAMABLE:{ - GstQTMuxClass *qtmux_klass = - (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); - if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML) { + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { qtmux->streamable = g_value_get_boolean (value); } break; @@ -6223,6 +7011,19 @@ gst_qt_mux_set_property (GObject * object, case PROP_MAX_RAW_AUDIO_DRIFT: qtmux->max_raw_audio_drift = g_value_get_uint64 (value); break; + case PROP_FRAGMENT_METHOD: + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + qtmux->fragment_method = g_value_get_enum (value); + } + break; + case PROP_SEGMENT_DURATION: + if (GST_QT_MUX_IS_FRAGMENTED (qtmux_klass)) { + qtmux->segment_duration = g_value_get_uint (value); + } + break; + case PROP_DVR_ENCRYPTION: + qtmux->dvr_encryption = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h index c9ef21ddf4..974e39b8ba 100644 --- a/gst/isomp4/gstqtmux.h +++ b/gst/isomp4/gstqtmux.h @@ -136,6 +136,14 @@ struct _GstQTPad gint64 fragment_duration; /* optional fragment index book-keeping */ AtomTFRA *tfra; + /* List of GstForceKeyUnit event requesting a new fragmet for this fragment */ + GList *forcekeyunit_events; + GstClockTime next_fragment_ts; + gboolean first_fragment; + + /* for segmented dash format */ + gboolean segment_start; + gint64 segment_duration; /* Set when tags are received, cleared when written to moov */ gboolean tags_changed; @@ -158,6 +166,11 @@ struct _GstQTPad GstAdapter *raw_audio_adapter; guint64 raw_audio_adapter_offset; GstClockTime raw_audio_adapter_pts; + + /* dash unique atom box for fragment index book-keeping */ + AtomSIDX *sidx; + + guint32 subsegment_duration; }; typedef enum _GstQTMuxState @@ -228,6 +241,16 @@ struct _GstQTMux /* Set when tags are received, cleared when written to moov */ gboolean tags_changed; + /* Header buffers */ + GstBuffer *prefix_buf; + GstBuffer *ftyp_buf; + GstBuffer *post_ftyp_align_free_atom_buf; + GstBuffer *empty_free_atom_buf; + GstBuffer *moov_buf; + GstBuffer *moov_free_ping_atom_buf; + GstBuffer *moov_free_pong_atom_buf; + GstBuffer *extra_atoms_buf; + /* fragmented file index */ AtomMFRA *mfra; @@ -256,6 +279,10 @@ struct _GstQTMux * seek to rewrite headers - only valid for fragmented * mode. */ gboolean streamable; + gint fragment_method; + + /* for segmented dash format */ + guint32 segment_duration; /* Requested target maximum duration */ GstClockTime reserved_max_duration; @@ -291,12 +318,25 @@ struct _GstQTMux /* for request pad naming */ guint video_pads, audio_pads, subtitle_pads; + + /* for send dvr encryption uuid atom */ + gboolean dvr_encryption; + + /* dash unique atom box for fragment index book-keeping */ + AtomSIDX *sidx; + /* position of sidx atom header (for later updating data) in + * fragmented and segmented dash format */ + guint64 sidx_pos; + guint32 reserved_sidx_size; }; struct _GstQTMuxClass { GstElementClass parent_class; + /* signals */ + void (*send_header) (GstQTMux *qtmux); + GstQTMuxFormat format; }; diff --git a/gst/isomp4/gstqtmuxmap.c b/gst/isomp4/gstqtmuxmap.c index 4e34e137f9..68d0ea730e 100644 --- a/gst/isomp4/gstqtmuxmap.c +++ b/gst/isomp4/gstqtmuxmap.c @@ -128,10 +128,13 @@ "layer = (int) [1, 3], " \ COMMON_AUDIO_CAPS (2, MAX) +#define MPEGH_CAPS \ + "audio/mpeg-h" + #define AAC_CAPS \ "audio/mpeg, " \ "mpegversion = (int) 4, " \ - "stream-format = (string) raw, " \ + "stream-format = (string) {raw, loas}, " \ COMMON_AUDIO_CAPS (8, MAX) #define AC3_CAPS \ @@ -222,7 +225,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = { GST_STATIC_CAPS ("video/quicktime, variant = (string) iso"), GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS ";" H265_CAPS ";" "video/x-mp4-part," COMMON_VIDEO_CAPS), - GST_STATIC_CAPS (MP123_CAPS "; " + GST_STATIC_CAPS (MP123_CAPS "; " MPEGH_CAPS ";" AAC_CAPS " ; " AC3_CAPS " ; " ALAC_CAPS " ; " OPUS_CAPS), GST_STATIC_CAPS (TEXT_UTF8)} , @@ -239,6 +242,18 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = { GST_STATIC_CAPS (MP3_CAPS "; " AAC_CAPS), GST_STATIC_CAPS_NONE} , + /* DASH (ISO 14496-12/AMD 3) */ + { + GST_QT_MUX_FORMAT_DASH, + GST_RANK_PRIMARY, + "mp4dashmux", + "DASH", + "GstDASHMux", + GST_STATIC_CAPS ("video/quicktime, variant = (string) dash-fragmented"), + GST_STATIC_CAPS (H264_CAPS ";" H265_CAPS), + GST_STATIC_CAPS (MP123_CAPS "; " AAC_CAPS ";" MPEGH_CAPS), + GST_STATIC_CAPS_NONE} + , /* 3GPP Technical Specification 26.244 V7.3.0 * (extended in 3GPP2 File Formats for Multimedia Services) */ { @@ -327,6 +342,7 @@ gst_qt_mux_map_format_to_header (GstQTMuxFormat format, GstBuffer ** _prefix, static const guint32 mp4_brands[] = { FOURCC_mp41, FOURCC_isom, FOURCC_iso2, 0 }; static const guint32 isml_brands[] = { FOURCC_iso2, 0 }; + static const guint32 dash_brands[] = { FOURCC_iso2, FOURCC_dash, 0 }; static const guint32 gpp_brands[] = { FOURCC_isom, FOURCC_iso2, 0 }; static const guint32 mjp2_brands[] = { FOURCC_isom, FOURCC_iso2, 0 }; static const guint8 mjp2_prefix[] = @@ -355,6 +371,10 @@ gst_qt_mux_map_format_to_header (GstQTMuxFormat format, GstBuffer ** _prefix, major = FOURCC_isml; comp = isml_brands; break; + case GST_QT_MUX_FORMAT_DASH: + major = FOURCC_isom; + comp = dash_brands; + break; case GST_QT_MUX_FORMAT_3GP: { gint video, audio; diff --git a/gst/isomp4/gstqtmuxmap.h b/gst/isomp4/gstqtmuxmap.h index e578a36740..cca34c972e 100644 --- a/gst/isomp4/gstqtmuxmap.h +++ b/gst/isomp4/gstqtmuxmap.h @@ -56,7 +56,8 @@ typedef enum _GstQTMuxFormat GST_QT_MUX_FORMAT_MP4, GST_QT_MUX_FORMAT_3GP, GST_QT_MUX_FORMAT_MJ2, - GST_QT_MUX_FORMAT_ISML + GST_QT_MUX_FORMAT_ISML, + GST_QT_MUX_FORMAT_DASH } GstQTMuxFormat; typedef struct _GstQTMuxFormatProp diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 705bdf7ca6..b3cbcb6ee5 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -80,7 +80,7 @@ #endif /* max. size considered 'sane' for non-mdat atoms */ -#define QTDEMUX_MAX_ATOM_SIZE (25*1024*1024) +#define QTDEMUX_MAX_ATOM_SIZE (32*1024*1024) /* if the sample index is larger than this, something is likely wrong */ #define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (200*1024*1024) @@ -93,10 +93,14 @@ #define QTDEMUX_TREE_NODE_FOURCC(n) (QT_FOURCC(((guint8 *) (n)->data) + 4)) -#define STREAM_IS_EOS(s) (s->time_position == GST_CLOCK_TIME_NONE) +#define STREAM_IS_EOS(s) ((s)->time_position == GST_CLOCK_TIME_NONE) #define ABSDIFF(x, y) ( (x) > (y) ? ((x) - (y)) : ((y) - (x)) ) +#define QTDEMUX_FIRST_STREAM(demux) ((QtDemuxStream *)(demux)->active_streams \ + ? (QtDemuxStream *)(demux)->active_streams->data : NULL) +#define QTDEMUX_STREAM(s) ((QtDemuxStream *)(s)) + GST_DEBUG_CATEGORY (qtdemux_debug); #define GST_CAT_DEFAULT qtdemux_debug @@ -104,6 +108,7 @@ typedef struct _QtDemuxSegment QtDemuxSegment; typedef struct _QtDemuxSample QtDemuxSample; typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo; +typedef struct _QtDemuxSampleToGroup QtDemuxSampleToGroup; struct _QtDemuxSample { @@ -133,6 +138,30 @@ struct _QtDemuxSample #define QTSAMPLE_KEYFRAME(stream,sample) ((stream)->all_keyframe || (sample)->keyframe) +#define QTDEMUX_EXPOSE_GET_LOCK(demux) (&((demux)->expose_lock)) +#define QTDEMUX_EXPOSE_LOCK(demux) G_STMT_START { \ + GST_TRACE("Locking from thread %p", g_thread_self()); \ + g_mutex_lock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \ + GST_TRACE("Locked from thread %p", g_thread_self()); \ + } G_STMT_END + +#define QTDEMUX_EXPOSE_UNLOCK(demux) G_STMT_START { \ + GST_TRACE("Unlocking from thread %p", g_thread_self()); \ + g_mutex_unlock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \ + } G_STMT_END + +#define QTDEMUX_SEEKEVENT_GET_LOCK(demux) (&((demux)->seekevent_lock)) +#define QTDEMUX_SEEKEVENT_LOCK(demux) G_STMT_START { \ + GST_TRACE("seekevent locking from thread %p", g_thread_self()); \ + g_mutex_lock (QTDEMUX_SEEKEVENT_GET_LOCK (demux)); \ + GST_TRACE("seekevent locked from thread %p", g_thread_self()); \ + } G_STMT_END + +#define QTDEMUX_SEEKEVENT_UNLOCK(demux) G_STMT_START { \ + GST_TRACE("seekevent unlocking from thread %p", g_thread_self()); \ + g_mutex_unlock (QTDEMUX_SEEKEVENT_GET_LOCK (demux)); \ + } G_STMT_END + /* * Quicktime has tracks and segments. A track is a continuous piece of * multimedia content. The track is not always played from start to finish but @@ -207,7 +236,10 @@ struct _QtDemuxSegment #define QTSEGMENT_IS_EMPTY(s) ((s)->media_start == GST_CLOCK_TIME_NONE) -/* Used with fragmented MP4 files (mfra atom) */ +/* Used with fragmented MP4 files (mfra atom) + * Due to the similarity between this and sidx entry, + * sidx box also use this structure for the purpose of unification + */ typedef struct { GstClockTime ts; @@ -257,6 +289,8 @@ struct _QtDemuxStream { GstPad *pad; + gchar *stream_id; + QtDemuxStreamStsdEntry *stsd_entries; guint stsd_entries_length; guint cur_stsd_entry_index; @@ -386,6 +420,7 @@ struct _QtDemuxStream guint32 stts_sample_index; guint64 stts_time; guint32 stts_duration; + guint64 stts_total_duration; /* To ensure stream duration */ /* stss */ gboolean stss_present; guint32 n_sample_syncs; @@ -419,6 +454,10 @@ struct _QtDemuxStream gboolean disabled; + /* sidx */ + QtDemuxRandomAccessEntry *seg_idx_entries; + guint n_seg_idx_entries; + /* stereoscopic video streams */ GstVideoMultiviewMode multiview_mode; GstVideoMultiviewFlags multiview_flags; @@ -429,6 +468,13 @@ struct _QtDemuxStream guint32 protection_scheme_version; gpointer protection_scheme_info; /* specific to the protection scheme */ GQueue protection_scheme_event_queue; + + /* SampleToGroup information structure */ + GList *sample_to_group; + + guint64 first_chunk_offset; + guint64 last_chunk_offset; + guint8 length_size_avcC; }; /* Contains properties and cryptographic info for a set of samples from a @@ -437,10 +483,33 @@ struct _QtDemuxCencSampleSetInfo { GstStructure *default_properties; + /* overriding default_properties if any */ + GPtrArray *sample_properties; + /* @crypto_info holds one GstStructure per sample */ GPtrArray *crypto_info; }; +typedef struct _QtDemuxSampleToGroupEntry +{ + guint32 sample_count; + guint32 group_description_idx; + + /* grouping_type specific data */ + gpointer data; +} QtDemuxSampleToGroupEntry; + +struct _QtDemuxSampleToGroup +{ + guint32 grouping_type; + guint32 grouping_type_parameter; /* version 1 only */ + + QtDemuxSampleToGroupEntry *sbgp_entries; + guint32 sbgp_entries_length; + + GDestroyNotify entry_free_func; +}; + static const gchar * qt_demux_state_string (enum QtDemuxState state) { @@ -552,14 +621,15 @@ static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name); +static inline QtDemuxStream *qtdemux_find_stream (GstQTDemux * qtdemux, + guint32 id); static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n); static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux); -static void gst_qtdemux_stream_free (GstQTDemux * qtdemux, - QtDemuxStream * stream); -static void gst_qtdemux_stream_clear (GstQTDemux * qtdemux, +static void gst_qtdemux_stream_free (QtDemuxStream * stream); +static void gst_qtdemux_stream_clear (QtDemuxStream * stream); +static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux, QtDemuxStream * stream); -static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int index); static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux); static void qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream); @@ -581,6 +651,20 @@ static GstStructure *qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux, static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux, const gchar * id); static void qtdemux_gst_structure_free (GstStructure * gststructure); +static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard); + +#ifdef DOLBYHDR_SUPPORT +static gboolean qtdemux_prepare_dolby_track (GstQTDemux * qtdemux, + QtDemuxStream * stream); +#endif + +#ifdef MP4_PUSHMODE_TRICK +static gboolean gst_qtdemux_seek_push (GstQTDemux * demux, + guint64 start, guint64 stop); +static gboolean gst_qtdemux_handle_trick (GstQTDemux * demux); +#endif + +static gboolean is_common_enc_scheme_type (guint32 scheme_type); static void gst_qtdemux_class_init (GstQTDemuxClass * klass) @@ -620,6 +704,32 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass) gst_riff_init (); } +static void +get_property_cb (GstPad * pad, GstPad * peer, gpointer usr_data) +{ + GstSmartPropertiesReturn res; + GstQTDemux *qtdemux = GST_QTDEMUX (usr_data); + + res = gst_element_get_smart_properties (GST_ELEMENT_CAST (qtdemux), +#ifdef DOLBYHDR_SUPPORT + "dolby-vision-support", &qtdemux->dolby_vision_support, +#endif + "dlna-opval", &qtdemux->dlna_opval, + "adaptive-mode", &qtdemux->adaptive_mode, NULL); + + if (res != GST_SMART_PROPERTIES_OK) { + GST_WARNING_OBJECT (qtdemux, "failed to get properties"); + } +#ifdef DOLBYHDR_SUPPORT + GST_INFO_OBJECT (qtdemux, "dolby-vision-support = %d", + qtdemux->dolby_vision_support); +#endif + /* NOTE: dlna opval does not used now + * we does not support time mode dlna operation*/ + GST_INFO_OBJECT (qtdemux, "dlna-opval = %X", qtdemux->dlna_opval); + GST_INFO_OBJECT (qtdemux, "adaptive-mode = %d", qtdemux->adaptive_mode); +} + static void gst_qtdemux_init (GstQTDemux * qtdemux) { @@ -632,39 +742,25 @@ gst_qtdemux_init (GstQTDemux * qtdemux) gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event); gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad); - qtdemux->state = QTDEMUX_STATE_INITIAL; - qtdemux->pullbased = FALSE; - qtdemux->posted_redirect = FALSE; - qtdemux->neededbytes = 16; - qtdemux->todrop = 0; qtdemux->adapter = gst_adapter_new (); - qtdemux->offset = 0; - qtdemux->first_mdat = -1; - qtdemux->got_moov = FALSE; - qtdemux->mdatoffset = -1; - qtdemux->mdatbuffer = NULL; - qtdemux->restoredata_buffer = NULL; - qtdemux->restoredata_offset = -1; - qtdemux->fragment_start = -1; - qtdemux->fragment_start_offset = -1; - qtdemux->media_caps = NULL; - qtdemux->exposed = FALSE; - qtdemux->mss_mode = FALSE; - qtdemux->pending_newsegment = NULL; - qtdemux->upstream_format_is_time = FALSE; - qtdemux->have_group_id = FALSE; - qtdemux->group_id = G_MAXUINT; - qtdemux->cenc_aux_info_offset = 0; - qtdemux->cenc_aux_info_sizes = NULL; - qtdemux->cenc_aux_sample_count = 0; - qtdemux->protection_system_ids = NULL; g_queue_init (&qtdemux->protection_event_queue); - gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); - qtdemux->tag_list = gst_tag_list_new_empty (); - gst_tag_list_set_scope (qtdemux->tag_list, GST_TAG_SCOPE_GLOBAL); qtdemux->flowcombiner = gst_flow_combiner_new (); + g_mutex_init (&qtdemux->expose_lock); + g_mutex_init (&qtdemux->seekevent_lock); GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE); + + gst_qtdemux_reset (qtdemux, TRUE); + + /* NOTE: Do not set smart-properties variables on _qtdemux_reset () */ +#ifdef DOLBYHDR_SUPPORT + qtdemux->dolby_vision_support = FALSE; +#endif + qtdemux->dlna_opval = 0x111; + qtdemux->adaptive_mode = FALSE; + + g_signal_connect (G_OBJECT (qtdemux->sinkpad), "linked", + (GCallback) get_property_cb, qtdemux); } static void @@ -677,6 +773,7 @@ gst_qtdemux_dispose (GObject * object) qtdemux->adapter = NULL; } gst_tag_list_unref (qtdemux->tag_list); + gst_tag_list_unref (qtdemux->upstream_tag_list); gst_flow_combiner_free (qtdemux->flowcombiner); g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, NULL); @@ -684,6 +781,8 @@ gst_qtdemux_dispose (GObject * object) g_free (qtdemux->cenc_aux_info_sizes); qtdemux->cenc_aux_info_sizes = NULL; + g_mutex_clear (&qtdemux->expose_lock); + g_mutex_clear (&qtdemux->seekevent_lock); G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -913,6 +1012,28 @@ gst_qtdemux_handle_src_query (GstPad * pad, GstObject * parent, gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); res = TRUE; break; + case GST_QUERY_CUSTOM:{ + gboolean trickable = TRUE; + GstStructure *s; + + s = (GstStructure *) gst_query_get_structure (query); + + if (gst_structure_has_name (s, "custom-trickable")) { + if (!qtdemux->pullbased && qtdemux->isBigData) + trickable = FALSE; + + gst_structure_set (s, "trickable", G_TYPE_BOOLEAN, trickable, NULL); + res = TRUE; + break; + } else { + res = gst_pad_query_default (pad, parent, query); + break; + } + + gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); + res = TRUE; + break; + } case GST_QUERY_SEEKING:{ GstFormat fmt; gboolean seekable; @@ -979,6 +1100,17 @@ gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream) GST_DEBUG_OBJECT (qtdemux, "Checking pad %s:%s for tags", GST_DEBUG_PAD_NAME (stream->pad)); + if (qtdemux->upstream_tag_list) { + GST_LOG_OBJECT (qtdemux, "Merging upstream tags to stream tags."); + if (gst_tag_list_is_empty (stream->stream_tags)) { + stream->stream_tags = gst_tag_list_copy (qtdemux->upstream_tag_list); + } else { + stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags); + gst_tag_list_insert (stream->stream_tags, qtdemux->upstream_tag_list, + GST_TAG_MERGE_REPLACE); + } + } + if (!gst_tag_list_is_empty (stream->stream_tags)) { GST_DEBUG_OBJECT (qtdemux, "Sending tags %" GST_PTR_FORMAT, stream->stream_tags); @@ -1000,17 +1132,17 @@ gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream) static void gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event) { - guint n; gboolean has_valid_stream = FALSE; GstEventType etype = GST_EVENT_TYPE (event); + GList *iter; GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads", GST_EVENT_TYPE_NAME (event)); - for (n = 0; n < qtdemux->n_streams; n++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { GstPad *pad; - QtDemuxStream *stream = qtdemux->streams[n]; - GST_DEBUG_OBJECT (qtdemux, "pushing on pad %i", n); + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id); if ((pad = stream->pad)) { has_valid_stream = TRUE; @@ -1193,6 +1325,12 @@ gst_qtdemux_find_keyframe (GstQTDemux * qtdemux, QtDemuxStream * str, goto beach; } + /* only one video key frame, return zero index */ + if (str->subtype == FOURCC_vide && str->n_sample_syncs < 2) { + new_index = 0; + goto beach; + } + /* all keyframes, return index */ if (str->all_keyframe) { new_index = index; @@ -1305,15 +1443,15 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time, { guint64 min_offset; gint64 min_byte_offset = -1; - gint n; + GList *iter; min_offset = desired_time; /* for each stream, find the index of the sample in the segment * and move back to the previous keyframe. */ - for (n = 0; n < qtdemux->n_streams; n++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { QtDemuxStream *str; - guint32 index, kindex; + guint32 index, kindex = 0; guint32 seg_idx; GstClockTime media_start; GstClockTime media_time; @@ -1321,7 +1459,7 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time, QtDemuxSegment *seg; gboolean empty_segment = FALSE; - str = qtdemux->streams[n]; + str = QTDEMUX_STREAM (iter->data); if (CUR_STREAM (str)->sparse && !use_sparse) continue; @@ -1403,6 +1541,10 @@ gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time, if (min_byte_offset < 0 || str->samples[index].offset < min_byte_offset) min_byte_offset = str->samples[index].offset; + + /* if keyframe is at a start position, we need to change segment position */ + if (!kindex && !qtdemux->fragmented) + qtdemux->isStartKeyFrame = TRUE; } if (key_time) @@ -1451,6 +1593,7 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) gint64 byte_cur; gint64 original_stop; guint32 seqnum; + GstSegment seeksegment; GST_DEBUG_OBJECT (qtdemux, "doing push-based seek"); @@ -1458,9 +1601,25 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) &cur_type, &cur, &stop_type, &stop); seqnum = gst_event_get_seqnum (event); - /* only forward streaming and seeking is possible */ - if (rate <= 0) - goto unsupported_seek; +#ifdef MP4_PUSHMODE_TRICK + qtdemux->demux_rate = rate; + qtdemux->segment.rate = rate; + qtdemux->pushed_Iframe = FALSE; + /* Handles trick condition */ + if (flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + memcpy (&seeksegment, &qtdemux->segment, sizeof (GstSegment)); + gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, + stop_type, stop, FALSE); + memcpy (&qtdemux->segment, &seeksegment, sizeof (GstSegment)); + } + + /* Do not forward byte format negative rate seek */ + if (rate < 0.0) + rate = 1.0; + + cur = qtdemux->segment.position = (qtdemux->segment.rate < 0.0) ? stop : cur; +#endif + /* convert to TIME if needed and possible */ if (!gst_qtdemux_convert_seek (pad, &format, cur_type, &cur, @@ -1502,6 +1661,10 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) } GST_OBJECT_UNLOCK (qtdemux); +#ifdef MP4_PUSHMODE_TRICK + qtdemux->byte_seeking = TRUE; +#endif + /* BYTE seek event */ event = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, cur_type, byte_cur, stop_type, stop); @@ -1517,11 +1680,6 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) "seek aborted."); return FALSE; } -unsupported_seek: - { - GST_DEBUG_OBJECT (qtdemux, "unsupported seek, seek aborted."); - return FALSE; - } no_format: { GST_DEBUG_OBJECT (qtdemux, "unsupported format given, seek aborted."); @@ -1529,6 +1687,76 @@ gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) } } +#ifdef MP4_PUSHMODE_TRICK +static gboolean +gst_qtdemux_seek_push (GstQTDemux * demux, guint64 start, guint64 stop) +{ + GstEvent *seek_event; + gboolean ret = FALSE; + + seek_event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_SKIP | GST_SEEK_FLAG_FLUSH | + GST_SEEK_FLAG_TRICKMODE_KEY_UNITS | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO, + GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE, stop); + ret = gst_pad_push_event (demux->sinkpad, seek_event); + return ret; +} + +static gboolean +gst_qtdemux_handle_trick (GstQTDemux * demux) +{ + QtDemuxStream *stream = NULL; + gint64 offset; + guint32 next_idx, kindex; + gboolean forward = demux->segment.rate > 0.0; + gboolean ret = FALSE; + GList *iter; + + demux->byte_seeking = FALSE; + + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); + if (stream->subtype == FOURCC_vide) + break; + } + + if (!stream || stream->subtype != FOURCC_vide) + return FALSE; + + next_idx = stream->sample_index; + if (next_idx == -1) + next_idx = 0; + if (next_idx < 2 && !forward) + goto eos; + + /* Here, we are already advanced to the next index of keyframe */ + if (!forward) + next_idx -= 2; + + kindex = gst_qtdemux_find_keyframe (demux, stream, next_idx, forward); + + if (kindex == -1 || kindex >= stream->n_samples) + goto eos; + + GST_LOG_OBJECT (demux, "current index %" G_GUINT32_FORMAT ", target index %" + G_GUINT32_FORMAT, stream->sample_index, kindex); + + offset = stream->samples[kindex].offset; + QTDEMUX_SEEKEVENT_LOCK (demux); + ret = gst_qtdemux_seek_push (demux, offset, -1); + QTDEMUX_SEEKEVENT_UNLOCK (demux); + +done: + return ret; + +eos: + GST_DEBUG_OBJECT (demux, "Reached end of stream"); + gst_qtdemux_push_event (demux, gst_event_new_eos ()); + goto done; +} +#endif + /* perform the seek. * * We set all segment_indexes in the streams to unknown and @@ -1550,7 +1778,7 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment, guint32 seqnum, GstSeekFlags flags) { gint64 desired_offset; - gint n; + GList *iter; desired_offset = segment->position; @@ -1560,7 +1788,8 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment, /* may not have enough fragmented info to do this adjustment, * and we can't scan (and probably should not) at this time with * possibly flushing upstream */ - if ((flags & GST_SEEK_FLAG_KEY_UNIT) && !qtdemux->fragmented) { + if (((flags & GST_SEEK_FLAG_KEY_UNIT) && !qtdemux->fragmented) + || qtdemux->is_dolby_hdr) { gint64 min_offset; gboolean next, before, after; @@ -1577,11 +1806,18 @@ gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment, desired_offset = min_offset; } + if (qtdemux->isStartKeyFrame) { + GST_DEBUG_OBJECT (qtdemux, + "keyframe is at a start position, so we need to update segment information."); + desired_offset = segment->position = segment->start = segment->time = 0; + qtdemux->isStartKeyFrame = FALSE; + } + /* and set all streams to the final position */ gst_flow_combiner_reset (qtdemux->flowcombiner); qtdemux->segment_seqnum = seqnum; - for (n = 0; n < qtdemux->n_streams; n++) { - QtDemuxStream *stream = qtdemux->streams[n]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); stream->time_position = desired_offset; stream->accumulated_base = 0; @@ -1725,26 +1961,23 @@ gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) static gboolean qtdemux_ensure_index (GstQTDemux * qtdemux) { - guint i; + GList *iter; GST_DEBUG_OBJECT (qtdemux, "collecting all metadata for all streams"); /* Build complete index */ - for (i = 0; i < qtdemux->n_streams; i++) { - QtDemuxStream *stream = qtdemux->streams[i]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); - if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) - goto parse_error; + if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) { + GST_LOG_OBJECT (qtdemux, + "Building complete index of track-id %u for seeking failed!", + stream->track_id); + return FALSE; + } } - return TRUE; - /* ERRORS */ -parse_error: - { - GST_LOG_OBJECT (qtdemux, - "Building complete index of stream %u for seeking failed!", i); - return FALSE; - } + return TRUE; } static gboolean @@ -1753,6 +1986,7 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent, { gboolean res = TRUE; GstQTDemux *qtdemux = GST_QTDEMUX (parent); + gdouble rate = 0.0; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: @@ -1787,14 +2021,40 @@ gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent, "Time taken to parse index %" GST_TIME_FORMAT, GST_TIME_ARGS (ts)); #endif } + gst_event_parse_seek (event, &rate, NULL, NULL, NULL, NULL, NULL, NULL); + + if (qtdemux->fragmented && rate < 0.0) { + GList *iter; + gboolean has_ra_entries = FALSE; + + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + if (stream->ra_entries) { + has_ra_entries = TRUE; + break; + } + } + /* If there is no tfra, we cannot do REW */ + if (!has_ra_entries) { + res = FALSE; + gst_event_unref (event); + GST_DEBUG_OBJECT (qtdemux, + "Cannot do rewind for fragmented file without tfra"); + goto done; + } + } + if (qtdemux->pullbased) { res = gst_qtdemux_do_seek (qtdemux, pad, event); - } else if (gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) { - GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked"); - res = TRUE; } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams && !qtdemux->fragmented) { + QTDEMUX_SEEKEVENT_LOCK (qtdemux); res = gst_qtdemux_do_push_seek (qtdemux, pad, event); + QTDEMUX_SEEKEVENT_UNLOCK (qtdemux); + } else if ((rate == 1) && + gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) { + GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked"); + res = TRUE; } else { GST_DEBUG_OBJECT (qtdemux, "ignoring seek in push mode in current state"); @@ -1838,20 +2098,21 @@ static void gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw, gboolean set, QtDemuxStream ** _stream, gint * _index, gint64 * _time) { - gint i, n, index; + gint i, index; gint64 time, min_time; QtDemuxStream *stream; + GList *iter; min_time = -1; stream = NULL; index = -1; - for (n = 0; n < qtdemux->n_streams; ++n) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { QtDemuxStream *str; gint inc; gboolean set_sample; - str = qtdemux->streams[n]; + str = QTDEMUX_STREAM (iter->data); set_sample = !set; if (fw) { @@ -1882,8 +2143,8 @@ gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw, if (!CUR_STREAM (str)->sparse) { /* determine min/max time */ time = QTSAMPLE_PTS (str, &str->samples[i]); - if (min_time == -1 || (!fw && time > min_time) || - (fw && time < min_time)) { + if ((min_time == -1 || (!fw && time > min_time) || + (fw && time < min_time)) && str->samples[i].keyframe) { min_time = time; } @@ -1894,6 +2155,14 @@ gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw, stream = str; index = i; } + + /* add a condition to fix keyframe problem in sample of stream + * for some sample which has not keyframe */ + if (min_time < 0) { + GST_DEBUG_OBJECT (qtdemux, "Setting min time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (time)); + min_time = time; + } } break; } @@ -1911,12 +2180,58 @@ gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw, *_index = index; } +/* Copied from mpegtsbase code */ +static gchar * +_get_upstream_id (GstQTDemux * demux) +{ + gchar *upstream_id = gst_pad_get_stream_id (demux->sinkpad); + + if (!upstream_id) { + /* Try to create one from the upstream URI, else use a randome number */ + GstQuery *query; + gchar *uri = NULL; + + /* Try to generate one from the URI query and + * if it fails take a random number instead */ + query = gst_query_new_uri (); + if (gst_element_query (GST_ELEMENT_CAST (demux), query)) { + gst_query_parse_uri (query, &uri); + } + + if (uri) { + GChecksum *cs; + + /* And then generate an SHA256 sum of the URI */ + cs = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (cs, (const guchar *) uri, strlen (uri)); + g_free (uri); + upstream_id = g_strdup (g_checksum_get_string (cs)); + g_checksum_free (cs); + } else { + /* Just get some random number if the URI query fails */ + GST_FIXME_OBJECT (demux, "Creating random stream-id, consider " + "implementing a deterministic way of creating a stream-id"); + upstream_id = + g_strdup_printf ("%08x%08x%08x%08x", g_random_int (), g_random_int (), + g_random_int (), g_random_int ()); + } + + gst_query_unref (query); + } + return upstream_id; +} + static QtDemuxStream * -_create_stream (void) +_create_stream (GstQTDemux * qtdemux, guint32 track_id) { QtDemuxStream *stream; + gchar *upstream_id; stream = g_new0 (QtDemuxStream, 1); + stream->track_id = track_id; + upstream_id = _get_upstream_id (qtdemux); + stream->stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id); + g_free (upstream_id); /* new streams always need a discont */ stream->discont = TRUE; /* we enable clipping for raw audio/video streams */ @@ -1975,15 +2290,15 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps) /* TODO update when stream changes during playback */ if (demux->n_streams == 0) { - stream = _create_stream (); - demux->streams[demux->n_streams] = stream; + stream = _create_stream (demux, 1); + demux->active_streams = g_list_append (demux->active_streams, stream); demux->n_streams = 1; /* mss has no stsd/stsd entry, use id 0 as default */ stream->stsd_entries_length = 1; stream->stsd_sample_description_id = stream->cur_stsd_entry_index = 0; stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, 1); } else { - stream = demux->streams[0]; + stream = QTDEMUX_FIRST_STREAM (demux); } timescale_v = gst_structure_get_value (structure, "timescale"); @@ -2032,7 +2347,7 @@ gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps) static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) { - gint n; + GList *iter; GST_DEBUG_OBJECT (qtdemux, "Resetting demux"); gst_pad_stop_task (qtdemux->sinkpad); @@ -2096,17 +2411,26 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, NULL); g_queue_clear (&qtdemux->protection_event_queue); + if (qtdemux->upstream_tag_list) { + gst_tag_list_unref (qtdemux->upstream_tag_list); + qtdemux->upstream_tag_list = NULL; + } } qtdemux->offset = 0; gst_adapter_clear (qtdemux->adapter); gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); qtdemux->segment_seqnum = GST_SEQNUM_INVALID; +#ifdef MP4_PUSHMODE_TRICK + qtdemux->pushed_Iframe = FALSE; +#endif if (hard) { - for (n = 0; n < qtdemux->n_streams; n++) { - gst_qtdemux_stream_free (qtdemux, qtdemux->streams[n]); - qtdemux->streams[n] = NULL; - } + g_list_free_full (qtdemux->active_streams, + (GDestroyNotify) gst_qtdemux_stream_free); + g_list_free_full (qtdemux->old_streams, + (GDestroyNotify) gst_qtdemux_stream_free); + qtdemux->active_streams = NULL; + qtdemux->old_streams = NULL; qtdemux->n_streams = 0; qtdemux->n_video_streams = 0; qtdemux->n_audio_streams = 0; @@ -2117,20 +2441,41 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) gst_caps_replace (&qtdemux->media_caps, NULL); qtdemux->timescale = 0; qtdemux->got_moov = FALSE; + qtdemux->cenc_aux_info_offset = 0; + qtdemux->cenc_aux_info_sizes = NULL; + qtdemux->cenc_aux_sample_count = 0; if (qtdemux->protection_system_ids) { g_ptr_array_free (qtdemux->protection_system_ids, TRUE); qtdemux->protection_system_ids = NULL; } + qtdemux->streams_aware = GST_OBJECT_PARENT (qtdemux) + && GST_OBJECT_FLAG_IS_SET (GST_OBJECT_PARENT (qtdemux), + GST_BIN_FLAG_STREAMS_AWARE); +#ifdef DOLBYHDR_SUPPORT + /* Dolby HDR */ + qtdemux->is_dolby_hdr = FALSE; + qtdemux->has_dolby_bl_cand = FALSE; + qtdemux->has_dolby_el_cand = FALSE; + + qtdemux->dv_profile = -1; + qtdemux->rpu_present_flag = FALSE; + qtdemux->el_present_flag = FALSE; + qtdemux->bl_present_flag = FALSE; +#endif + qtdemux->isInterleaved = TRUE; + qtdemux->isBigData = FALSE; + qtdemux->configure_dvr = FALSE; } else if (qtdemux->mss_mode) { gst_flow_combiner_reset (qtdemux->flowcombiner); - for (n = 0; n < qtdemux->n_streams; n++) - gst_qtdemux_stream_clear (qtdemux, qtdemux->streams[n]); + g_list_foreach (qtdemux->active_streams, + (GFunc) gst_qtdemux_stream_clear, NULL); } else { gst_flow_combiner_reset (qtdemux->flowcombiner); - for (n = 0; n < qtdemux->n_streams; n++) { - qtdemux->streams[n]->sent_eos = FALSE; - qtdemux->streams[n]->time_position = 0; - qtdemux->streams[n]->accumulated_base = 0; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + stream->sent_eos = FALSE; + stream->time_position = 0; + stream->accumulated_base = 0; } if (!qtdemux->pending_newsegment) { qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); @@ -2152,12 +2497,15 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) static void gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment) { - gint n, i; + gint i; + GList *iter; - for (n = 0; n < qtdemux->n_streams; n++) { - QtDemuxStream *stream = qtdemux->streams[n]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); stream->time_position = segment->start; + if (segment->rate < 0) + stream->time_position = segment->stop; /* in push mode we should be guaranteed that we will have empty segments * at the beginning and then one segment after, other scenarios are not @@ -2168,9 +2516,26 @@ gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment) stream->time_position); if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) { /* push the empty segment and move to the next one */ - gst_qtdemux_send_gap_for_segment (qtdemux, stream, i, - stream->time_position); + if (stream->segment.rate > 0.0) + gst_qtdemux_send_gap_for_segment (qtdemux, stream, i, + stream->time_position); + + /* accumulate previous segments */ + if (GST_CLOCK_TIME_IS_VALID (stream->segment.stop)) + stream->accumulated_base += + (stream->segment.stop - + stream->segment.start) / ABS (stream->segment.rate); + continue; + } else if (stream->pad && stream->subtype != FOURCC_vide && + (qtdemux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS || + qtdemux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO)) { + GstEvent *gap; + gap = gst_event_new_gap (stream->segment.start, GST_CLOCK_TIME_NONE); + + GST_DEBUG_OBJECT (stream->pad, "Pushing gap for trickmode-no-audio" + "segment: %" GST_PTR_FORMAT, gap); + gst_pad_push_event (stream->pad, gap); } g_assert (i == stream->n_segments - 1); @@ -2275,13 +2640,26 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, segment.duration = demux->segment.duration; segment.base = gst_segment_to_running_time (&demux->segment, GST_FORMAT_TIME, demux->segment.position); +#ifdef MP4_PUSHMODE_TRICK + if (demux->demux_rate < 0.0) { + segment.stop = segment.start; + segment.start = segment.time = 0; + segment.position = segment.stop; + } + segment.rate = demux->demux_rate; +#endif } gst_segment_copy_into (&segment, &demux->segment); GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); /* map segment to internal qt segments and push on each stream */ +#ifdef MP4_PUSHMODE_TRICK + if (demux->n_streams + && (demux->byte_seeking || demux->upstream_format_is_time)) { +#else if (demux->n_streams) { +#endif if (demux->fragmented) { GstEvent *segment_event = gst_event_new_segment (&segment); @@ -2327,12 +2705,40 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, gst_event_unref (event); goto drop; } +#ifdef MP4_PUSHMODE_TRICK + demux->demux_rate = demux->segment.rate; + demux->flushing = TRUE; + + if (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS && + !demux->upstream_format_is_time && !demux->byte_seeking) { + gst_event_unref (event); + res = TRUE; + goto drop; + } else { + QTDEMUX_EXPOSE_LOCK (demux); + gst_qtdemux_push_event (demux, event); + QTDEMUX_EXPOSE_UNLOCK (demux); + res = TRUE; + goto drop; + } +#endif break; } case GST_EVENT_FLUSH_STOP: { guint64 dur; +#ifdef MP4_PUSHMODE_TRICK + demux->flushing = FALSE; + + if (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS && + !demux->upstream_format_is_time && !demux->byte_seeking) { + gst_adapter_clear (demux->adapter); + gst_event_unref (event); + goto drop; + } +#endif + dur = demux->segment.duration; gst_qtdemux_reset (demux, FALSE); demux->segment.duration = dur; @@ -2347,10 +2753,10 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, /* If we are in push mode, and get an EOS before we've seen any streams, * then error out - we have nowhere to send the EOS */ if (!demux->pullbased) { - gint i; + GList *iter; gboolean has_valid_stream = FALSE; - for (i = 0; i < demux->n_streams; i++) { - if (demux->streams[i]->pad != NULL) { + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + if (QTDEMUX_STREAM (iter->data)->pad != NULL) { has_valid_stream = TRUE; break; } @@ -2390,6 +2796,39 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, res = TRUE; goto drop; } + case GST_EVENT_STREAM_START: + { + res = TRUE; + gst_event_unref (event); + + /* Drain all the buffers */ + gst_qtdemux_process_adapter (demux, TRUE); + gst_qtdemux_reset (demux, FALSE); + /* We expect new moov box after new stream-start event */ + demux->old_streams = + g_list_concat (demux->old_streams, demux->active_streams); + demux->active_streams = NULL; + goto drop; + } + case GST_EVENT_TAG: + { + if (demux->adaptive_mode) { + GstTagList *taglist; + + gst_event_parse_tag (event, &taglist); + GST_DEBUG_OBJECT (demux, + "Storing upstream tags for later use: %" GST_PTR_FORMAT, event); + if (demux->upstream_tag_list == NULL) { + demux->upstream_tag_list = gst_tag_list_copy (taglist); + } else { + demux->upstream_tag_list = + gst_tag_list_make_writable (demux->upstream_tag_list); + gst_tag_list_insert (demux->upstream_tag_list, taglist, + GST_TAG_MERGE_REPLACE); + } + } + break; + } default: break; } @@ -2459,18 +2898,47 @@ gst_qtdemux_stbl_free (QtDemuxStream * stream) } static void -gst_qtdemux_stream_flush_segments_data (GstQTDemux * qtdemux, - QtDemuxStream * stream) +gst_qtdemux_stream_flush_segments_data (QtDemuxStream * stream) { g_free (stream->segments); stream->segments = NULL; stream->segment_index = -1; stream->accumulated_base = 0; + stream->dummy_segment = FALSE; + + g_free (stream->seg_idx_entries); + stream->seg_idx_entries = NULL; + stream->n_seg_idx_entries = 0; } static void -gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux, - QtDemuxStream * stream) +gst_qtdemux_sample_to_group_free_internal (QtDemuxSampleToGroup * + sample_to_group) +{ + if (sample_to_group->entry_free_func) { + guint i; + for (i = 0; i < sample_to_group->sbgp_entries_length; i++) { + QtDemuxSampleToGroupEntry *entry = &sample_to_group->sbgp_entries[i]; + sample_to_group->entry_free_func (entry->data); + } + } + + g_free (sample_to_group->sbgp_entries); + g_free (sample_to_group); +} + +static void +gst_qtdemux_sample_to_group_free (QtDemuxStream * stream) +{ + if (stream->sample_to_group) + g_list_free_full (stream->sample_to_group, + (GDestroyNotify) gst_qtdemux_sample_to_group_free_internal); + + stream->sample_to_group = NULL; +} + +static void +gst_qtdemux_stream_flush_samples_data (QtDemuxStream * stream) { g_free (stream->samples); stream->samples = NULL; @@ -2489,10 +2957,12 @@ gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux, stream->n_samples_moof = 0; stream->duration_moof = 0; stream->duration_last_moof = 0; + + gst_qtdemux_sample_to_group_free (stream); } static void -gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream) +gst_qtdemux_stream_clear (QtDemuxStream * stream) { gint i; if (stream->allocator) @@ -2510,7 +2980,9 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream) entry->sparse = FALSE; } - gst_tag_list_unref (stream->stream_tags); + if (stream->stream_tags) + gst_tag_list_unref (stream->stream_tags); + stream->stream_tags = gst_tag_list_new_empty (); gst_tag_list_set_scope (stream->stream_tags, GST_TAG_SCOPE_STREAM); g_free (stream->redirect_uri); @@ -2518,7 +2990,7 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream) stream->sent_eos = FALSE; stream->protected = FALSE; if (stream->protection_scheme_info) { - if (stream->protection_scheme_type == FOURCC_cenc) { + if (is_common_enc_scheme_type (stream->protection_scheme_type)) { QtDemuxCencSampleSetInfo *info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; if (info->default_properties) @@ -2534,15 +3006,15 @@ gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream) g_queue_foreach (&stream->protection_scheme_event_queue, (GFunc) gst_event_unref, NULL); g_queue_clear (&stream->protection_scheme_event_queue); - gst_qtdemux_stream_flush_segments_data (qtdemux, stream); - gst_qtdemux_stream_flush_samples_data (qtdemux, stream); + gst_qtdemux_stream_flush_segments_data (stream); + gst_qtdemux_stream_flush_samples_data (stream); } static void -gst_qtdemux_stream_reset (GstQTDemux * qtdemux, QtDemuxStream * stream) +gst_qtdemux_stream_reset (QtDemuxStream * stream) { gint i; - gst_qtdemux_stream_clear (qtdemux, stream); + gst_qtdemux_stream_clear (stream); for (i = 0; i < stream->stsd_entries_length; i++) { QtDemuxStreamStsdEntry *entry = &stream->stsd_entries[i]; if (entry->caps) { @@ -2557,25 +3029,32 @@ gst_qtdemux_stream_reset (GstQTDemux * qtdemux, QtDemuxStream * stream) static void -gst_qtdemux_stream_free (GstQTDemux * qtdemux, QtDemuxStream * stream) +gst_qtdemux_stream_free (QtDemuxStream * stream) { - gst_qtdemux_stream_reset (qtdemux, stream); + gst_qtdemux_stream_reset (stream); gst_tag_list_unref (stream->stream_tags); if (stream->pad) { - gst_element_remove_pad (GST_ELEMENT_CAST (qtdemux), stream->pad); - gst_flow_combiner_remove_pad (qtdemux->flowcombiner, stream->pad); + GstElement *qtdemux = gst_pad_get_parent_element (stream->pad); + + if (G_UNLIKELY (!qtdemux)) { + GST_WARNING_OBJECT (stream->pad, "Pad has no parent"); + } else { + gst_element_remove_pad (qtdemux, stream->pad); + gst_flow_combiner_remove_pad (GST_QTDEMUX_CAST (qtdemux)->flowcombiner, + stream->pad); + gst_object_unref (qtdemux); + } } + g_free (stream->stream_id); + g_free (stream->stsd_entries); g_free (stream); } static void -gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int i) +gst_qtdemux_remove_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) { - g_assert (i >= 0 && i < qtdemux->n_streams && qtdemux->streams[i] != NULL); - - gst_qtdemux_stream_free (qtdemux, qtdemux->streams[i]); - qtdemux->streams[i] = qtdemux->streams[qtdemux->n_streams - 1]; - qtdemux->streams[qtdemux->n_streams - 1] = NULL; + qtdemux->active_streams = g_list_remove (qtdemux->active_streams, stream); + gst_qtdemux_stream_free (stream); qtdemux->n_streams--; } @@ -2586,7 +3065,8 @@ gst_qtdemux_change_state (GstElement * element, GstStateChange transition) GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_qtdemux_reset (qtdemux, TRUE); break; default: break; @@ -2647,6 +3127,43 @@ qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist, } } +static gboolean +qtdemux_parse_cenc_subsample (GstQTDemux * qtdemux, GstByteReader * br, + GstStructure * properties, guint sample_index) +{ + guint8 *data; + GstBuffer *buf; + guint16 n_subsamples; + + g_return_val_if_fail (br != NULL, FALSE); + g_return_val_if_fail (properties != NULL, FALSE); + + if (!gst_byte_reader_get_uint16_be (br, &n_subsamples) + || n_subsamples == 0) { + GST_ERROR_OBJECT (qtdemux, + "failed to get subsample count for sample %u", sample_index); + return FALSE; + } + + if (!gst_byte_reader_dup_data (br, n_subsamples * 6, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", + sample_index); + return FALSE; + } + + buf = gst_buffer_new_wrapped (data, n_subsamples * 6); + + if (!buf) + return FALSE; + + gst_structure_set (properties, + "subsample_count", G_TYPE_UINT, n_subsamples, + "subsamples", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + + return TRUE; +} + static void qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, guint offset) @@ -2663,11 +3180,10 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, gboolean uses_sub_sample_encryption = FALSE; guint32 sample_count; - if (qtdemux->n_streams == 0) + stream = QTDEMUX_FIRST_STREAM (qtdemux); + if (!stream) return; - stream = qtdemux->streams[0]; - structure = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0); if (!gst_structure_has_name (structure, "application/x-cenc")) { GST_WARNING_OBJECT (qtdemux, @@ -2675,9 +3191,13 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, return; } - gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, - G_TYPE_STRING, &system_id, NULL); - gst_qtdemux_append_protection_system_id (qtdemux, system_id); + if (qtdemux->protection_system_ids && qtdemux->protection_system_ids->len > 0) { + GST_DEBUG_OBJECT (qtdemux, "we've got protection_system id's already"); + } else { + gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, + G_TYPE_STRING, &system_id, NULL); + gst_qtdemux_append_protection_system_id (qtdemux, system_id); + } stream->protected = TRUE; stream->protection_scheme_type = FOURCC_cenc; @@ -2789,29 +3309,11 @@ qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, gst_buffer_unref (buf); if (uses_sub_sample_encryption) { - guint16 n_subsamples; - - if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples) - || n_subsamples == 0) { - GST_ERROR_OBJECT (qtdemux, - "failed to get subsample count for sample %u", i); - gst_structure_free (properties); - qtdemux->cenc_aux_sample_count = i; - return; - } - GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples); - if (!gst_byte_reader_dup_data (&br, n_subsamples * 6, &data)) { - GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", - i); + if (!qtdemux_parse_cenc_subsample (qtdemux, &br, properties, i)) { gst_structure_free (properties); qtdemux->cenc_aux_sample_count = i; return; } - buf = gst_buffer_new_wrapped (data, n_subsamples * 6); - gst_structure_set (properties, - "subsample_count", G_TYPE_UINT, n_subsamples, - "subsamples", GST_TYPE_BUFFER, buf, NULL); - gst_buffer_unref (buf); } else { gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); } @@ -2840,6 +3342,13 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length) 0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4 }; + /* 4747982f-82a3-44a8-bb1d-a8c2ba5dcf9d + * NOTE: it's big-endian */ + static const guint8 dvr_uuid[] = { + 0x47, 0x47, 0x98, 0x2f, 0x82, 0xa3, 0x44, 0xa8, + 0xbb, 0x1d, 0xa8, 0xc2, 0xba, 0x5d, 0xcf, 0x9d + }; + guint offset; /* counts as header data */ @@ -2847,7 +3356,7 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length) offset = (QT_UINT32 (buffer) == 0) ? 16 : 8; - if (length <= offset + 16) { + if (length < offset + 16) { GST_DEBUG_OBJECT (qtdemux, "uuid atom is too short, skipping"); return; } @@ -2881,8 +3390,12 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length) GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (_("Cannot play stream because it is encrypted with PlayReady DRM.")), (NULL)); - } else if (memcmp (buffer + offset, piff_sample_encryption_uuid, 16) == 0) { + } else if ((memcmp (buffer + offset, piff_sample_encryption_uuid, 16) == 0) && + qtdemux->mss_mode) { qtdemux_parse_piff (qtdemux, buffer, length, offset); + } else if (memcmp (buffer + offset, dvr_uuid, 16) == 0) { + GST_DEBUG_OBJECT (qtdemux, "DVR uuid detected"); + qtdemux->configure_dvr = TRUE; } else { GST_DEBUG_OBJECT (qtdemux, "Ignoring unknown uuid: %08x-%08x-%08x-%08x", GST_READ_UINT32_LE (buffer + offset), @@ -2906,8 +3419,50 @@ qtdemux_parse_sidx (GstQTDemux * qtdemux, const guint8 * buffer, gint length) &consumed); GST_DEBUG_OBJECT (qtdemux, "sidx parse result: %d", res); if (res == GST_ISOFF_QT_PARSER_DONE) { - check_update_duration (qtdemux, sidx_parser.cumulative_pts); - } + gint i, n_parsed_entries, n_entries = 0; + QtDemuxStream *stream; + GstSidxBoxEntry *entries; + QtDemuxRandomAccessEntry *seg_entries; + + stream = qtdemux_find_stream (qtdemux, sidx_parser.sidx.ref_id); + n_entries = n_parsed_entries = sidx_parser.sidx.entries_count; + entries = sidx_parser.sidx.entries; + + for (i = 0; i < n_parsed_entries; i++) { + /* FIXME: non-zero ref_type means the corresponding sidx entry is pointing + * to another sidx box. We do not consider recursive sidx structure. + * This might be used in the future. But currently there is no use case + * So, let's ignore such a type of sidx entries. */ + if (entries[i].ref_type) { + n_entries--; + } + } + + GST_LOG_OBJECT (qtdemux, "sidx anchor offset = %" G_GUINT64_FORMAT, + qtdemux->offset); + if (stream && n_entries > 0) { + if (stream->n_seg_idx_entries == 0) { + g_assert (stream->seg_idx_entries == NULL); + stream->seg_idx_entries = g_try_new0 (QtDemuxRandomAccessEntry, + n_entries); + } else { + stream->seg_idx_entries = g_try_renew (QtDemuxRandomAccessEntry, + stream->seg_idx_entries, stream->n_seg_idx_entries + n_entries); + } + + seg_entries = stream->seg_idx_entries + stream->n_seg_idx_entries; + for (i = 0; i < n_parsed_entries; i++) { + if (entries[i].ref_type) + continue; + seg_entries->ts = entries[i].pts; + seg_entries->moof_offset = entries[i].offset + qtdemux->offset; + seg_entries++; + } + stream->n_seg_idx_entries += n_entries; + } + + check_update_duration (qtdemux, sidx_parser.cumulative_pts); + } gst_isoff_qt_sidx_parser_clear (&sidx_parser); } @@ -2924,9 +3479,11 @@ extract_initial_length_and_fourcc (const guint8 * data, guint size, fourcc = QT_FOURCC (data + 4); GST_DEBUG ("atom type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); - if (length == 0) { + /* Add fourcc condition to stop parse header at defective contents. + Forest Gump 1994-KickASS(MicroStar RG).mp4 from NC3 */ + if (length == 0 && fourcc != 0) { length = G_MAXUINT64; - } else if (length == 1 && size >= 16) { + } else if (length == 1 && size >= 16 && fourcc != 0) { /* this means we have an extended size, which is the 64 bit value of * the next 8 bytes */ length = QT_UINT64 (data + 8); @@ -3044,6 +3601,7 @@ check_update_duration (GstQTDemux * qtdemux, GstClockTime duration) guint i; guint64 movdur; GstClockTime prevdur; + GList *iter; movdur = GSTTIME_TO_QTTIME (qtdemux, duration); @@ -3068,38 +3626,38 @@ check_update_duration (GstQTDemux * qtdemux, GstClockTime duration) qtdemux->segment.stop = fixeddur; } } - for (i = 0; i < qtdemux->n_streams; i++) { - QtDemuxStream *stream = qtdemux->streams[i]; - if (stream) { - movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration); - if (movdur > stream->duration) { - GST_DEBUG_OBJECT (qtdemux, - "Updating stream #%d duration to %" GST_TIME_FORMAT, i, - GST_TIME_ARGS (duration)); - stream->duration = movdur; - /* internal duration tracking state has been updated above, so */ - /* preserve an open-ended dummy segment rather than repeatedly updating - * it and spamming downstream accordingly with segment events */ - if (stream->dummy_segment && - GST_CLOCK_TIME_IS_VALID (stream->segments[0].duration)) { - /* Update all dummy values to new duration */ - stream->segments[0].stop_time = duration; - stream->segments[0].duration = duration; - stream->segments[0].media_stop = duration; - - /* let downstream know we possibly have a new stop time */ - if (stream->segment_index != -1) { - GstClockTime pos; - - if (qtdemux->segment.rate >= 0) { - pos = stream->segment.start; - } else { - pos = stream->segment.stop; - } + for (iter = qtdemux->active_streams, i = 0; iter; + iter = g_list_next (iter), i++) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); - gst_qtdemux_stream_update_segment (qtdemux, stream, - stream->segment_index, pos, NULL, NULL); + movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration); + if (movdur > stream->duration) { + GST_DEBUG_OBJECT (qtdemux, + "Updating stream #%d duration to %" GST_TIME_FORMAT, i, + GST_TIME_ARGS (duration)); + stream->duration = movdur; + /* internal duration tracking state has been updated above, so */ + /* preserve an open-ended dummy segment rather than repeatedly updating + * it and spamming downstream accordingly with segment events */ + if (stream->dummy_segment && + GST_CLOCK_TIME_IS_VALID (stream->segments[0].duration)) { + /* Update all dummy values to new duration */ + stream->segments[0].stop_time = duration; + stream->segments[0].duration = duration; + stream->segments[0].media_stop = duration; + + /* let downstream know we possibly have a new stop time */ + if (stream->segment_index != -1) { + GstClockTime pos; + + if (qtdemux->segment.rate >= 0) { + pos = stream->segment.start; + } else { + pos = stream->segment.stop; } + + gst_qtdemux_stream_update_segment (qtdemux, stream, + stream->segment_index, pos, NULL, NULL); } } } @@ -3124,7 +3682,7 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, gboolean ismv = FALSE; gint64 initial_offset; - GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; " + GST_LOG_OBJECT (qtdemux, "parsing trun track-id %d; " "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT ", " "decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration, d_sample_size, d_sample_flags, *base_offset, decode_ts); @@ -3339,6 +3897,8 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, /* ismv seems to use 0x40 for keyframe, 0xc0 for non-keyframe, * now idea how it relates to bitfield other than massive LE/BE confusion */ sample->keyframe = ismv ? ((sflags & 0xff) == 0x40) : !(sflags & 0x10000); + if (sample->keyframe) + stream->n_sample_syncs++; *running_offset += size; timestamp += dur; stream->duration_moof += dur; @@ -3389,7 +3949,7 @@ static inline QtDemuxStream * qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id) { QtDemuxStream *stream; - gint i; + GList *iter; /* check */ if (G_UNLIKELY (!id)) { @@ -3397,22 +3957,14 @@ qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id) return NULL; } - /* try to get it fast and simple */ - if (G_LIKELY (id <= qtdemux->n_streams)) { - stream = qtdemux->streams[id - 1]; - if (G_LIKELY (stream->track_id == id)) - return stream; - } - - /* linear search otherwise */ - for (i = 0; i < qtdemux->n_streams; i++) { - stream = qtdemux->streams[i]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); if (stream->track_id == id) return stream; } if (qtdemux->mss_mode) { /* mss should have only 1 stream anyway */ - return qtdemux->streams[0]; + return QTDEMUX_FIRST_STREAM (qtdemux); } return NULL; @@ -3538,6 +4090,68 @@ qtdemux_parse_tfdt (GstQTDemux * qtdemux, GstByteReader * br, } } +static void +qtdemux_prepare_cenc_sample_properties (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + QtDemuxCencSampleSetInfo *info = NULL; + QtDemuxSampleToGroup *target_group = NULL; + guint i, j; + guint32 n_samples = 0; + + g_return_if_fail (stream != NULL); + g_return_if_fail (stream->protected); + g_return_if_fail (stream->protection_scheme_info != NULL); + + info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (info->sample_properties) + g_ptr_array_free (info->sample_properties, TRUE); + + info->sample_properties = NULL; + + if (!info->default_properties) { + GST_ERROR_OBJECT (qtdemux, "Default properties is not defined"); + return; + } + + if (stream->sample_to_group) { + GList *iter; + for (iter = stream->sample_to_group; iter; iter = g_list_next (iter)) { + QtDemuxSampleToGroup *group = (QtDemuxSampleToGroup *) iter->data; + if (group->grouping_type == FOURCC_seig) { + target_group = group; + break; + } + } + } + + if (!target_group) + return; + + for (i = 0; i < target_group->sbgp_entries_length; i++) { + QtDemuxSampleToGroupEntry *group_entry = &target_group->sbgp_entries[i]; + n_samples += group_entry->sample_count; + if (!info->sample_properties) { + info->sample_properties = + g_ptr_array_new_full (n_samples, + (GDestroyNotify) qtdemux_gst_structure_free); + } else { + g_ptr_array_set_size (info->sample_properties, n_samples); + } + + for (j = 0; j < group_entry->sample_count; j++) { + GstStructure *properties; + + /* group->data might be NULL if group_description_index was zero */ + properties = group_entry->data ? + group_entry->data : info->default_properties; + g_ptr_array_add (info->sample_properties, + gst_structure_copy (properties)); + } + } +} + /* Returns a pointer to a GstStructure containing the properties of * the stream sample identified by @sample_index. The caller must unref * the returned object after use. Returns NULL if unsuccessful. */ @@ -3546,6 +4160,7 @@ qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux, QtDemuxStream * stream, guint sample_index) { QtDemuxCencSampleSetInfo *info = NULL; + GstStructure *properties = NULL; g_return_val_if_fail (stream != NULL, NULL); g_return_val_if_fail (stream->protected, NULL); @@ -3553,9 +4168,14 @@ qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux, info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; - /* Currently, cenc properties for groups of samples are not supported, so - * simply return a copy of the default sample properties */ - return gst_structure_copy (info->default_properties); + if (info->sample_properties && sample_index < info->sample_properties->len) { + properties = g_ptr_array_index (info->sample_properties, sample_index); + g_ptr_array_index (info->sample_properties, sample_index) = NULL; + } + if (!properties) + properties = gst_structure_copy (info->default_properties); + + return properties; } /* Parses the sizes of sample auxiliary information contained within a stream, @@ -3676,6 +4296,86 @@ qtdemux_parse_saio (GstQTDemux * qtdemux, QtDemuxStream * stream, return TRUE; } +/* Parse Sample Encryption (senc) which contains sample auxiliary information + * like combination of saio and saiz (but much simpler than them) */ +static gboolean +qtdemux_parse_senc (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br) +{ + QtDemuxCencSampleSetInfo *ss_info = NULL; + gint i; + guint32 flags; + gboolean uses_sub_sample_encryption = FALSE; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + g_return_val_if_fail (stream->protected, FALSE); + g_return_val_if_fail (stream->protection_scheme_info != NULL, FALSE); + + ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (ss_info->crypto_info) { + GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info"); + g_ptr_array_free (ss_info->crypto_info, TRUE); + } + + if (!gst_byte_reader_skip (br, 1) + || !gst_byte_reader_get_uint24_be (br, &flags)) { + GST_ERROR_OBJECT (qtdemux, "failed to get flags"); + return FALSE; + } + + uses_sub_sample_encryption = flags & 0x000002; + + if (!gst_byte_reader_get_uint32_be (br, &qtdemux->cenc_aux_sample_count)) { + GST_ERROR_OBJECT (qtdemux, "failed to get sample_count for sample"); + return FALSE; + } + + ss_info->crypto_info = + g_ptr_array_new_full (qtdemux->cenc_aux_sample_count, + (GDestroyNotify) qtdemux_gst_structure_free); + + qtdemux_prepare_cenc_sample_properties (qtdemux, stream); + + for (i = 0; i < qtdemux->cenc_aux_sample_count; ++i) { + GstStructure *properties; + guint8 *data; + guint iv_size; + GstBuffer *buf; + + properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i); + if (properties == NULL) { + GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i); + return FALSE; + } + if (!gst_structure_get_uint (properties, "iv_size", &iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get iv_size for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + if (!gst_byte_reader_dup_data (br, iv_size, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get IV for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + buf = gst_buffer_new_wrapped (data, iv_size); + gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + if (uses_sub_sample_encryption) { + if (!qtdemux_parse_cenc_subsample (qtdemux, br, properties, i)) { + gst_structure_free (properties); + return FALSE; + } + } else { + gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); + } + g_ptr_array_add (ss_info->crypto_info, properties); + } + return TRUE; +} + static void qtdemux_gst_structure_free (GstStructure * gststructure) { @@ -3738,9 +4438,11 @@ qtdemux_parse_cenc_aux_info (GstQTDemux * qtdemux, QtDemuxStream * stream, g_ptr_array_free (old_crypto_info, TRUE); } + /* FIXME: how to handle old_crypto_info ? */ + qtdemux_prepare_cenc_sample_properties (qtdemux, stream); + for (i = 0; i < sample_count; ++i) { GstStructure *properties; - guint16 n_subsamples = 0; guint8 *data; guint iv_size; GstBuffer *buf; @@ -3765,94 +4467,335 @@ qtdemux_parse_cenc_aux_info (GstQTDemux * qtdemux, QtDemuxStream * stream, gst_buffer_unref (buf); size = info_sizes[i]; if (size > iv_size) { - if (!gst_byte_reader_get_uint16_be (br, &n_subsamples) - || !(n_subsamples > 0)) { - gst_structure_free (properties); - GST_ERROR_OBJECT (qtdemux, - "failed to get subsample count for sample %u", i); - return FALSE; - } - GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples); - if (!gst_byte_reader_dup_data (br, n_subsamples * 6, &data)) { - GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", - i); + if (!qtdemux_parse_cenc_subsample (qtdemux, br, properties, i)) { gst_structure_free (properties); return FALSE; } - buf = gst_buffer_new_wrapped (data, n_subsamples * 6); - if (!buf) { - gst_structure_free (properties); - return FALSE; - } - gst_structure_set (properties, - "subsample_count", G_TYPE_UINT, n_subsamples, - "subsamples", GST_TYPE_BUFFER, buf, NULL); - gst_buffer_unref (buf); } else { gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); } g_ptr_array_add (ss_info->crypto_info, properties); } - return TRUE; -} - -/* Converts a UUID in raw byte form to a string representation, as defined in - * RFC 4122. The caller takes ownership of the returned string and is - * responsible for freeing it after use. */ -static gchar * -qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes) -{ - const guint8 *uuid = (const guint8 *) uuid_bytes; + return TRUE; +} + +/* Converts a UUID in raw byte form to a string representation, as defined in + * RFC 4122. The caller takes ownership of the returned string and is + * responsible for freeing it after use. */ +static gchar * +qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes) +{ + const guint8 *uuid = (const guint8 *) uuid_bytes; + + return g_strdup_printf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); +} + +/* Parses a Protection System Specific Header box (pssh), as defined in the + * Common Encryption (cenc) standard (ISO/IEC 23001-7), which contains + * information needed by a specific content protection system in order to + * decrypt cenc-protected tracks. Returns TRUE if successful; FALSE + * otherwise. */ +static gboolean +qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node) +{ + gchar *sysid_string; + guint32 pssh_size = QT_UINT32 (node->data); + GstBuffer *pssh = NULL; + GstEvent *event = NULL; + guint32 parent_box_type; + GList *iter; + + if (G_UNLIKELY (pssh_size < 32U)) { + GST_ERROR_OBJECT (qtdemux, "invalid box size"); + return FALSE; + } + + sysid_string = + qtdemux_uuid_bytes_to_string ((const guint8 *) node->data + 12); + + if (qtdemux->protection_system_ids && qtdemux->protection_system_ids->len > 0) { + GST_DEBUG_OBJECT (qtdemux, "we've got protection_system id's already"); + } else { + gst_qtdemux_append_protection_system_id (qtdemux, sysid_string); + } + + pssh = gst_buffer_new_wrapped (g_memdup (node->data, pssh_size), pssh_size); + GST_LOG_OBJECT (qtdemux, "cenc pssh size: %" G_GSIZE_FORMAT, + gst_buffer_get_size (pssh)); + + parent_box_type = QT_FOURCC ((const guint8 *) node->parent->data + 4); + + /* Push an event containing the pssh box onto the queues of all streams. */ + event = gst_event_new_protection (sysid_string, pssh, + (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof"); + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + g_queue_push_tail (&stream->protection_scheme_event_queue, + gst_event_ref (event)); + } + g_free (sysid_string); + gst_event_unref (event); + gst_buffer_unref (pssh); + return TRUE; +} + +static gboolean +qtdemux_parse_sbgp (GstQTDemux * qtdemux, GstByteReader * br, + QtDemuxStream * stream) +{ + guint32 version, grouping_type, entry_count; + guint32 grouping_type_parameter = 0; + guint i; + QtDemuxSampleToGroup *sample_to_group = NULL; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + + if (!gst_byte_reader_get_uint32_be (br, &version)) + return FALSE; + + version >>= 24; + if (version == 1) { + if (!gst_byte_reader_get_uint32_be (br, &grouping_type_parameter)) { + return FALSE; + } + } + + if (!qt_atom_parser_get_fourcc (br, &grouping_type)) + return FALSE; + + switch (grouping_type) { + case FOURCC_seig: + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { + GST_ERROR_OBJECT (qtdemux, "Common Encryption is required for seig"); + return FALSE; + } + break; + default: + /* FIXME: now we consider only seig type for protected stream. + * There might be informal types more */ + GST_DEBUG_OBJECT (qtdemux, "Unknown grouping_type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (grouping_type)); + return FALSE; + } + + if (!gst_byte_reader_get_uint32_be (br, &entry_count)) + return FALSE; + + sample_to_group = g_new0 (QtDemuxSampleToGroup, 1); + sample_to_group->sbgp_entries = + g_new0 (QtDemuxSampleToGroupEntry, entry_count); + sample_to_group->sbgp_entries_length = entry_count; + sample_to_group->entry_free_func = + (GDestroyNotify) qtdemux_gst_structure_free; + + sample_to_group->grouping_type = grouping_type; + sample_to_group->grouping_type_parameter = grouping_type_parameter; + + GST_DEBUG_OBJECT (qtdemux, "grouping_type: %" GST_FOURCC_FORMAT + ", entry_count: %" G_GUINT32_FORMAT, + GST_FOURCC_ARGS (grouping_type), entry_count); + + for (i = 0; i < entry_count; i++) { + QtDemuxSampleToGroupEntry *entry = &sample_to_group->sbgp_entries[i]; + guint32 sample_count; + guint32 group_description_idx; + + if (!gst_byte_reader_get_uint32_be (br, &sample_count)) + goto failed; + if (!gst_byte_reader_get_uint32_be (br, &group_description_idx)) + goto failed; + + GST_LOG_OBJECT (qtdemux, "entry %u (sample_count: %" G_GUINT32_FORMAT "), " + "group_description_index: %" G_GUINT32_FORMAT, + i, sample_count, group_description_idx); + + entry->sample_count = sample_count; + entry->group_description_idx = group_description_idx; + } + + stream->sample_to_group = + g_list_append (stream->sample_to_group, sample_to_group); + + return TRUE; + +failed: + GST_ERROR_OBJECT (qtdemux, "Failed to parse SampleToGroup box"); + if (sample_to_group) + gst_qtdemux_sample_to_group_free_internal (sample_to_group); + + return FALSE; +} + +static gboolean +qtdemux_parse_seig (GstQTDemux * qtdemux, GstByteReader * br, + QtDemuxSampleToGroupEntry * group_entry, QtDemuxStream * stream) +{ + guint8 crypt_byte_block = 0; + guint8 skip_byte_block = 0; + guint8 *kid; + guint8 is_protected, iv_size; + GstBuffer *kid_buf; + guint8 constant_iv_size = 0; + guint8 *constant_iv = NULL; + GstBuffer *constant_iv_buf = NULL; + + + if (!gst_byte_reader_skip (br, 1)) + return FALSE; + + if (stream->protection_scheme_type == FOURCC_cens || + stream->protection_scheme_type == FOURCC_cbcs) { + crypt_byte_block = QT_UINT8 ((guint8 *) br->data) >> 4; + skip_byte_block = QT_UINT8 ((guint8 *) br->data) & 0x0F; + } + + if (!gst_byte_reader_get_uint8 (br, &is_protected)) { + GST_ERROR_OBJECT (qtdemux, "failed to get is protected"); + return FALSE; + } + + if (!gst_byte_reader_get_uint8 (br, &iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get iv size"); + return FALSE; + } + + if (!gst_byte_reader_dup_data (br, 16, &kid)) { + GST_ERROR_OBJECT (qtdemux, "failed to get kid"); + return FALSE; + } + + kid_buf = gst_buffer_new_wrapped (kid, 16); + + if (iv_size == 0) { + if (!gst_byte_reader_get_uint8 (br, &constant_iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get constant_iv_size"); + return FALSE; + } + + if (!gst_byte_reader_dup_data (br, constant_iv_size, &constant_iv)) { + GST_ERROR_OBJECT (qtdemux, "failed to get constant iv"); + return FALSE; + } + + constant_iv_buf = gst_buffer_new_wrapped (constant_iv, constant_iv_size); + } + + if (G_UNLIKELY (group_entry->data)) { + gst_structure_free ((GstStructure *) group_entry->data); + } + + group_entry->data = + gst_structure_new ("application/x-cenc", + "crypt_byte_block", G_TYPE_UINT, crypt_byte_block, + "skip_byte_block", G_TYPE_UINT, skip_byte_block, + "iv_size", G_TYPE_UINT, iv_size, + "encrypted", G_TYPE_BOOLEAN, (is_protected == 1), + "kid", GST_TYPE_BUFFER, kid_buf, NULL); + + if (constant_iv_size != 0 && constant_iv_buf != NULL) { + gst_structure_set (group_entry->data, + "constant_iv_size", G_TYPE_UINT, constant_iv_size, + "constant_iv", GST_TYPE_BUFFER, constant_iv_buf, NULL); + gst_buffer_unref (constant_iv_buf); + } + + GST_DEBUG_OBJECT (qtdemux, "sample properties from seig: " + "is_encrypted=%u, iv_size=%u", is_protected, iv_size); + gst_buffer_unref (kid_buf); + + return TRUE; +} + +static gboolean +qtdemux_parse_sgpd (GstQTDemux * qtdemux, GstByteReader * br, + QtDemuxStream * stream) +{ + guint32 version = 0; + guint32 grouping_type; + guint32 default_length = 0, default_sample_description_idx = 0; + guint32 entry_count; + guint i; + QtDemuxSampleToGroup *ref_sbgp = NULL; + QtDemuxSampleToGroupEntry *group_entry; + GList *iter; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + + if (!stream->n_samples_moof) + return FALSE; + + if (!gst_byte_reader_get_uint32_be (br, &version)) + return FALSE; + + if (!qt_atom_parser_get_fourcc (br, &grouping_type)) + return FALSE; + + switch (grouping_type) { + case FOURCC_seig: + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { + GST_ERROR_OBJECT (qtdemux, "Common Encryption is required for seig"); + return FALSE; + } + break; + default: + /* FIXME: now we consider only seig type for protected stream. + * There might be informal types more */ + GST_DEBUG_OBJECT (qtdemux, "Unknown grouping_type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (grouping_type)); + return FALSE; + } - return g_strdup_printf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-" - "%02x%02x-%02x%02x%02x%02x%02x%02x", - uuid[0], uuid[1], uuid[2], uuid[3], - uuid[4], uuid[5], uuid[6], uuid[7], - uuid[8], uuid[9], uuid[10], uuid[11], - uuid[12], uuid[13], uuid[14], uuid[15]); -} + for (iter = stream->sample_to_group; iter; iter = g_list_next (iter)) { + QtDemuxSampleToGroup *sbgp = (QtDemuxSampleToGroup *) iter->data; + if (sbgp->grouping_type == grouping_type) { + ref_sbgp = sbgp; + break; + } + } -/* Parses a Protection System Specific Header box (pssh), as defined in the - * Common Encryption (cenc) standard (ISO/IEC 23001-7), which contains - * information needed by a specific content protection system in order to - * decrypt cenc-protected tracks. Returns TRUE if successful; FALSE - * otherwise. */ -static gboolean -qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node) -{ - gchar *sysid_string; - guint32 pssh_size = QT_UINT32 (node->data); - GstBuffer *pssh = NULL; - GstEvent *event = NULL; - guint32 parent_box_type; - gint i; + if (!ref_sbgp) { + GST_ERROR_OBJECT (qtdemux, "Cannot find matching SampleToGroup"); + return FALSE; + } - if (G_UNLIKELY (pssh_size < 32U)) { - GST_ERROR_OBJECT (qtdemux, "invalid box size"); + version >>= 24; + if (version == 1 && !gst_byte_reader_get_uint32_be (br, &default_length)) { + return FALSE; + } else if (version > 1 && + !gst_byte_reader_get_uint32_be (br, &default_sample_description_idx)) { return FALSE; } - sysid_string = - qtdemux_uuid_bytes_to_string ((const guint8 *) node->data + 12); + if (!gst_byte_reader_get_uint32_be (br, &entry_count)) + return FALSE; - gst_qtdemux_append_protection_system_id (qtdemux, sysid_string); + GST_DEBUG_OBJECT (qtdemux, "grouping_type: %" GST_FOURCC_FORMAT + ", entry_count: %" G_GUINT32_FORMAT, + GST_FOURCC_ARGS (grouping_type), entry_count); - pssh = gst_buffer_new_wrapped (g_memdup (node->data, pssh_size), pssh_size); - GST_LOG_OBJECT (qtdemux, "cenc pssh size: %" G_GSIZE_FORMAT, - gst_buffer_get_size (pssh)); + group_entry = ref_sbgp->sbgp_entries; - parent_box_type = QT_FOURCC ((const guint8 *) node->parent->data + 4); + for (i = 0; i < entry_count; i++) { + /* group_description_idx of value zero indicates + * that this sample is a member of no group of this type */ + while (group_entry->group_description_idx == 0) + group_entry++; - /* Push an event containing the pssh box onto the queues of all streams. */ - event = gst_event_new_protection (sysid_string, pssh, - (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof"); - for (i = 0; i < qtdemux->n_streams; ++i) { - g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue, - gst_event_ref (event)); + if (!qtdemux_parse_seig (qtdemux, br, group_entry, stream)) + return FALSE; + + group_entry++; } - g_free (sysid_string); - gst_event_unref (event); - gst_buffer_unref (pssh); + return TRUE; } @@ -3863,8 +4806,8 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node, *mfhd_node; GNode *uuid_node; GstByteReader mfhd_data, trun_data, tfhd_data, tfdt_data; - GNode *saiz_node, *saio_node, *pssh_node; - GstByteReader saiz_data, saio_data; + GNode *saiz_node, *saio_node, *pssh_node, *senc_node, *sbgp_node, *sgpd_node; + GstByteReader saiz_data, saio_data, senc_data, sbgp_data, sgpd_data; guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; gint64 base_offset, running_offset; guint32 frag_num; @@ -3900,13 +4843,49 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, &ds_size, &ds_flags, &base_offset)) goto missing_tfhd; + sbgp_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sbgp, + &sbgp_data); + while (sbgp_node) { + qtdemux_parse_sbgp (qtdemux, &sbgp_data, stream); + /* iterate all siblings */ + sbgp_node = qtdemux_tree_get_sibling_by_type_full (sbgp_node, FOURCC_sbgp, + &sbgp_data); + } + + sgpd_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sgpd, + &sgpd_data); + while (sgpd_node) { + qtdemux_parse_sgpd (qtdemux, &sgpd_data, stream); + /* iterate all siblings */ + sgpd_node = qtdemux_tree_get_sibling_by_type_full (sbgp_node, FOURCC_sgpd, + &sgpd_data); + } + + senc_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_senc, + &senc_data); + if (senc_node) { + if (!qtdemux_parse_senc (qtdemux, stream, &senc_data)) { + GST_WARNING_OBJECT (qtdemux, "failed to parse senc box"); + senc_node = NULL; + } + } + + if (!senc_node) { + saiz_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saiz, + &saiz_data); + } else { + /* Use senc instead of saiz/saio */ + saiz_node = NULL; + } + /* The following code assumes at most a single set of sample auxiliary * data in the fragment (consisting of a saiz box and a corresponding saio * box); in theory, however, there could be multiple sets of sample * auxiliary data in a fragment. */ - saiz_node = - qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saiz, - &saiz_data); if (saiz_node) { guint32 info_type = 0; guint64 offset = 0; @@ -3940,7 +4919,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, } if (base_offset > -1 && base_offset > qtdemux->moof_offset) offset += (guint64) (base_offset - qtdemux->moof_offset); - if (info_type == FOURCC_cenc && info_type_parameter == 0U) { + if (is_common_enc_scheme_type (info_type) && info_type_parameter == 0U) { GstByteReader br; if (offset > length) { GST_DEBUG_OBJECT (qtdemux, "cenc auxiliary info stored out of moof"); @@ -3989,7 +4968,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, goto lost_offset; if (qtdemux->upstream_format_is_time) - gst_qtdemux_stream_flush_samples_data (qtdemux, stream); + gst_qtdemux_stream_flush_samples_data (stream); /* initialise moof sample data */ stream->n_samples_moof = 0; @@ -4476,7 +5455,9 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) if (ret == GST_FLOW_EOS && (qtdemux->got_moov || qtdemux->media_caps)) { /* digested all data, show what we have */ qtdemux_prepare_streams (qtdemux); + QTDEMUX_EXPOSE_LOCK (qtdemux); ret = qtdemux_expose_streams (qtdemux); + QTDEMUX_EXPOSE_UNLOCK (qtdemux); qtdemux->state = QTDEMUX_STATE_MOVIE; GST_DEBUG_OBJECT (qtdemux, "switching state to STATE_MOVIE (%d)", @@ -4493,7 +5474,6 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) static GstFlowReturn gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) { - guint8 n = 0; guint32 seg_idx = 0, k_index = 0; guint32 ref_seg_idx, ref_k_index; GstClockTime k_pos = 0, last_stop = 0; @@ -4501,12 +5481,13 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) QtDemuxStream *ref_str = NULL; guint64 seg_media_start_mov; /* segment media start time in mov format */ guint64 target_ts; + GList *iter; /* Now we choose an arbitrary stream, get the previous keyframe timestamp * and finally align all the other streams on that timestamp with their * respective keyframes */ - for (n = 0; n < qtdemux->n_streams; n++) { - QtDemuxStream *str = qtdemux->streams[n]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *str = QTDEMUX_STREAM (iter->data); /* No candidate yet, take the first stream */ if (!ref_str) { @@ -4601,10 +5582,10 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) ref_k_index = k_index; /* Align them all on this */ - for (n = 0; n < qtdemux->n_streams; n++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { guint32 index = 0; GstClockTime seg_time = 0; - QtDemuxStream *str = qtdemux->streams[n]; + QtDemuxStream *str = QTDEMUX_STREAM (iter->data); /* aligning reference stream again might lead to backing up to yet another * keyframe (due to timestamp rounding issues), @@ -4613,13 +5594,13 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) seg_idx = ref_seg_idx; seg = &str->segments[seg_idx]; k_index = ref_k_index; - GST_DEBUG_OBJECT (qtdemux, "reference stream %d segment %d, " - "sample at index %d", n, ref_str->segment_index, k_index); + GST_DEBUG_OBJECT (qtdemux, "reference track-id %u segment %d, " + "sample at index %d", str->track_id, ref_str->segment_index, k_index); } else { seg_idx = gst_qtdemux_find_segment (qtdemux, str, k_pos); GST_DEBUG_OBJECT (qtdemux, - "stream %d align segment %d for keyframe pos %" GST_TIME_FORMAT, n, - seg_idx, GST_TIME_ARGS (k_pos)); + "track-id %u align segment %d for keyframe pos %" GST_TIME_FORMAT, + str->track_id, seg_idx, GST_TIME_ARGS (k_pos)); /* get segment and time in the segment */ seg = &str->segments[seg_idx]; @@ -4633,7 +5614,7 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) /* get the index of the sample with media time */ index = gst_qtdemux_find_index_linear (qtdemux, str, seg_time); GST_DEBUG_OBJECT (qtdemux, - "stream %d sample for %" GST_TIME_FORMAT " at %u", n, + "track-id %u sample for %" GST_TIME_FORMAT " at %u", str->track_id, GST_TIME_ARGS (seg_time), index); /* find previous keyframe */ @@ -4651,8 +5632,8 @@ gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) /* Now seek back in time */ gst_qtdemux_move_stream (qtdemux, str, k_index); - GST_DEBUG_OBJECT (qtdemux, "stream %d keyframe at %u, time position %" - GST_TIME_FORMAT " playing from sample %u to %u", n, k_index, + GST_DEBUG_OBJECT (qtdemux, "track-id %u keyframe at %u, time position %" + GST_TIME_FORMAT " playing from sample %u to %u", str->track_id, k_index, GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample); } @@ -4805,6 +5786,14 @@ gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, GST_PAD_LAST_FLOW_RETURN (stream->pad) = GST_FLOW_OK; /* clear to send tags on this pad now */ gst_qtdemux_push_tags (qtdemux, stream); + if (((qtdemux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO) || + qtdemux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) && + stream->subtype == FOURCC_soun) { + GST_DEBUG_OBJECT (stream->pad, + "Push gap to audio since we are no audio trick"); + gst_pad_push_event (stream->pad, + gst_event_new_gap (stream->segment.start, GST_CLOCK_TIME_NONE)); + } } if (_start) @@ -5118,16 +6107,16 @@ gst_qtdemux_advance_sample (GstQTDemux * qtdemux, QtDemuxStream * stream) static void gst_qtdemux_sync_streams (GstQTDemux * demux) { - gint i; + GList *iter; if (demux->n_streams <= 1) return; - for (i = 0; i < demux->n_streams; i++) { + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { QtDemuxStream *stream; GstClockTime end_time; - stream = demux->streams[i]; + stream = QTDEMUX_STREAM (iter->data); if (!stream->pad) continue; @@ -5555,7 +6544,8 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, GST_TIME_ARGS (pts), GST_TIME_ARGS (duration), GST_PAD_NAME (stream->pad)); - if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) { + if (stream->protected + && is_common_enc_scheme_type (stream->protection_scheme_type)) { GstStructure *crypto_info; QtDemuxCencSampleSetInfo *info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; @@ -5605,12 +6595,20 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, static const QtDemuxRandomAccessEntry * gst_qtdemux_stream_seek_fragment (GstQTDemux * qtdemux, QtDemuxStream * stream, - GstClockTime pos, gboolean after) + GstClockTime pos, gboolean after, gboolean use_sidx) { - QtDemuxRandomAccessEntry *entries = stream->ra_entries; - guint n_entries = stream->n_ra_entries; + QtDemuxRandomAccessEntry *entries; + guint n_entries; guint i; + if (use_sidx) { + entries = stream->seg_idx_entries; + n_entries = stream->n_seg_idx_entries; + } else { + entries = stream->ra_entries; + n_entries = stream->n_ra_entries; + } + /* we assume the table is sorted */ for (i = 0; i < n_entries; ++i) { if (entries[i].ts > pos) @@ -5632,7 +6630,8 @@ static gboolean gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) { const QtDemuxRandomAccessEntry *best_entry = NULL; - guint i; + GList *iter; + gboolean use_sidx = FALSE; GST_OBJECT_LOCK (qtdemux); @@ -5640,15 +6639,23 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) /* first see if we can determine where to go to using mfra, * before we start clearing things */ - for (i = 0; i < qtdemux->n_streams; i++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { const QtDemuxRandomAccessEntry *entry; QtDemuxStream *stream; gboolean is_audio_or_video; - stream = qtdemux->streams[i]; + stream = QTDEMUX_STREAM (iter->data); - if (stream->ra_entries == NULL) - continue; + gst_qtdemux_sample_to_group_free (stream); + + if (stream->ra_entries == NULL) { + if (stream->n_seg_idx_entries > 0) { + GST_LOG_OBJECT (qtdemux, "Do fragmented seek using sidx"); + use_sidx = TRUE; + } else { + continue; + } + } if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) is_audio_or_video = TRUE; @@ -5657,7 +6664,7 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) entry = gst_qtdemux_stream_seek_fragment (qtdemux, stream, - stream->time_position, !is_audio_or_video); + stream->time_position, !is_audio_or_video, use_sidx); GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); @@ -5679,10 +6686,10 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) } /* ok, now we can prepare for processing as of located moof */ - for (i = 0; i < qtdemux->n_streams; i++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { QtDemuxStream *stream; - stream = qtdemux->streams[i]; + stream = QTDEMUX_STREAM (iter->data); g_free (stream->samples); stream->samples = NULL; @@ -5705,7 +6712,7 @@ gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, - GST_TIME_ARGS (qtdemux->streams[0]->time_position), + GST_TIME_ARGS (QTDEMUX_FIRST_STREAM (qtdemux)->time_position), best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); qtdemux->moof_offset = best_entry->moof_offset; @@ -5721,7 +6728,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) { GstFlowReturn ret = GST_FLOW_OK; GstBuffer *buf = NULL; - QtDemuxStream *stream; + QtDemuxStream *stream, *target_stream = NULL; GstClockTime min_time; guint64 offset = 0; GstClockTime dts = GST_CLOCK_TIME_NONE; @@ -5731,8 +6738,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) guint sample_size = 0; gboolean empty = 0; guint size; - gint index; - gint i; + GList *iter; gst_qtdemux_push_pending_newsegment (qtdemux); @@ -5749,21 +6755,20 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) /* Figure out the next stream sample to output, min_time is expressed in * global time and runs over the edit list segments. */ min_time = G_MAXUINT64; - index = -1; - for (i = 0; i < qtdemux->n_streams; i++) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { GstClockTime position; - stream = qtdemux->streams[i]; + stream = QTDEMUX_STREAM (iter->data); position = stream->time_position; /* position of -1 is EOS */ if (position != GST_CLOCK_TIME_NONE && position < min_time) { min_time = position; - index = i; + target_stream = stream; } } /* all are EOS */ - if (G_UNLIKELY (index == -1)) { + if (G_UNLIKELY (target_stream == NULL)) { GST_DEBUG_OBJECT (qtdemux, "all streams are EOS"); goto eos; } @@ -5773,15 +6778,15 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) && ((qtdemux->segment.rate >= 0 && qtdemux->segment.stop <= min_time) || (qtdemux->segment.rate < 0 && qtdemux->segment.start > min_time)) - && qtdemux->streams[index]->on_keyframe)) { + && target_stream->on_keyframe)) { GST_DEBUG_OBJECT (qtdemux, "we reached the end of our segment."); - qtdemux->streams[index]->time_position = GST_CLOCK_TIME_NONE; + target_stream->time_position = GST_CLOCK_TIME_NONE; goto eos_stream; } /* gap events for subtitle streams */ - for (i = 0; i < qtdemux->n_streams; i++) { - stream = qtdemux->streams[i]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); if (stream->pad && (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)) { @@ -5798,7 +6803,7 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) } } - stream = qtdemux->streams[index]; + stream = target_stream; /* fetch info for the current sample of this stream */ if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &empty, &offset, &sample_size, &dts, &pts, &duration, &keyframe))) @@ -5811,19 +6816,24 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) } /* If we're doing a keyframe-only trickmode, only push keyframes on video streams */ - if (G_UNLIKELY (qtdemux-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) { - if (stream->subtype == FOURCC_vide && !keyframe) { - GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on stream %d", index); + if (G_UNLIKELY (qtdemux->segment. + flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) { + if (stream->subtype == FOURCC_vide && !keyframe + && stream->n_sample_syncs > 1) { + GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on track-id %u", + stream->track_id); + if (qtdemux->segment.rate < 0.0) + ret = GST_FLOW_EOS; goto next; } } GST_DEBUG_OBJECT (qtdemux, - "pushing from stream %d, empty %d offset %" G_GUINT64_FORMAT + "pushing from track-id %u, empty %d offset %" G_GUINT64_FORMAT ", size %d, dts=%" GST_TIME_FORMAT ", pts=%" GST_TIME_FORMAT - ", duration %" GST_TIME_FORMAT, index, empty, offset, sample_size, - GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), GST_TIME_ARGS (duration)); + ", duration %" GST_TIME_FORMAT, stream->track_id, empty, offset, + sample_size, GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), + GST_TIME_ARGS (duration)); if (G_UNLIKELY (empty)) { /* empty segment, push a gap if there's a second or more @@ -5904,6 +6914,43 @@ gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) size / CUR_STREAM (stream)->bytes_per_frame, stream->timescale); } + if (stream->subtype == FOURCC_vide && + CUR_STREAM (stream)->fourcc == FOURCC_avc1) { + guint64 size_of_stream = 0; + guint8 length_offset = 0; + GstMapInfo map; + + gst_buffer_map (buf, &map, GST_MAP_READ); + switch (stream->length_size_avcC) { + case 1: + size_of_stream = QT_UINT8 (map.data); + length_offset = 1; + break; + case 2: + size_of_stream = QT_UINT16 (map.data); + length_offset = 2; + break; + case 4: + size_of_stream = QT_UINT32 (map.data); + length_offset = 4; + break; + default: + GST_DEBUG_OBJECT (qtdemux, "wrong size: length_size_avcC %d", + stream->length_size_avcC); + break; + } + + GST_DEBUG_OBJECT (qtdemux, "size %d, size_of_stream %" G_GUINT64_FORMAT, + sample_size, size_of_stream); + gst_buffer_unmap (buf, &map); + if ((size != size_of_stream + length_offset) + && (size_of_stream > QTDEMUX_MAX_ATOM_SIZE)) { + gst_buffer_unref (buf); + buf = NULL; + goto next; + } + } + ret = gst_qtdemux_decorate_and_push_buffer (qtdemux, stream, buf, dts, pts, duration, keyframe, min_time, offset); @@ -6086,12 +7133,12 @@ static gboolean has_next_entry (GstQTDemux * demux) { QtDemuxStream *stream; - int i; + GList *iter; GST_DEBUG_OBJECT (demux, "Checking if there are samples not played yet"); - for (i = 0; i < demux->n_streams; i++) { - stream = demux->streams[i]; + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); if (stream->sample_index == -1) { stream->sample_index = 0; @@ -6099,7 +7146,7 @@ has_next_entry (GstQTDemux * demux) } if (stream->sample_index >= stream->n_samples) { - GST_LOG_OBJECT (demux, "stream %d samples exhausted", i); + GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id); continue; } GST_DEBUG_OBJECT (demux, "Found a sample"); @@ -6119,17 +7166,16 @@ has_next_entry (GstQTDemux * demux) static guint64 next_entry_size (GstQTDemux * demux) { - QtDemuxStream *stream; - int i; - int smallidx = -1; + QtDemuxStream *stream, *target_stream = NULL; guint64 smalloffs = (guint64) - 1; QtDemuxSample *sample; + GList *iter; GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT, demux->offset); - for (i = 0; i < demux->n_streams; i++) { - stream = demux->streams[i]; + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); if (stream->sample_index == -1) { stream->sample_index = 0; @@ -6137,7 +7183,7 @@ next_entry_size (GstQTDemux * demux) } if (stream->sample_index >= stream->n_samples) { - GST_LOG_OBJECT (demux, "stream %d samples exhausted", i); + GST_LOG_OBJECT (demux, "track-id %u samples exhausted", stream->track_id); continue; } @@ -6150,25 +7196,33 @@ next_entry_size (GstQTDemux * demux) sample = &stream->samples[stream->sample_index]; GST_LOG_OBJECT (demux, - "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT - " / size:%" G_GUINT32_FORMAT ")", i, stream->sample_index, - sample->offset, sample->size); + "Checking track-id %u (sample_index:%d / offset:%" G_GUINT64_FORMAT + " / size:%" G_GUINT32_FORMAT ")", stream->track_id, + stream->sample_index, sample->offset, sample->size); + + if (!((stream->subtype == FOURCC_vide) || (stream->subtype == FOURCC_soun)) + && ((sample->offset < demux->offset) && sample->size)) { + GST_LOG_OBJECT (demux, + "ignore subtitle sample located before demux offset (track_id %u)", + stream->track_id); + continue; + } if (((smalloffs == -1) || (sample->offset < smalloffs)) && (sample->size)) { - smallidx = i; smalloffs = sample->offset; + target_stream = stream; } } - GST_LOG_OBJECT (demux, - "stream %d offset %" G_GUINT64_FORMAT " demux->offset :%" - G_GUINT64_FORMAT, smallidx, smalloffs, demux->offset); - - if (smallidx == -1) + if (!target_stream) return -1; - stream = demux->streams[smallidx]; + GST_LOG_OBJECT (demux, + "track-id %u offset %" G_GUINT64_FORMAT " demux->offset :%" + G_GUINT64_FORMAT, target_stream->track_id, smalloffs, demux->offset); + + stream = target_stream; sample = &stream->samples[stream->sample_index]; if (sample->offset >= demux->offset) { @@ -6273,12 +7327,13 @@ gst_qtdemux_check_send_pending_segment (GstQTDemux * demux) { if (G_UNLIKELY (demux->pending_newsegment)) { gint i; + GList *iter; gst_qtdemux_push_pending_newsegment (demux); /* clear to send tags on all streams */ - for (i = 0; i < demux->n_streams; i++) { - QtDemuxStream *stream; - stream = demux->streams[i]; + for (iter = demux->active_streams, i = 0; iter; + iter = g_list_next (iter), i++) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); gst_qtdemux_push_tags (demux, stream); if (CUR_STREAM (stream)->sparse) { GST_INFO_OBJECT (demux, "Sending gap event on stream %d", i); @@ -6308,30 +7363,6 @@ gst_qtdemux_send_gap_for_segment (GstQTDemux * demux, gst_pad_push_event (stream->pad, gap); } -static void -gst_qtdemux_stream_send_initial_gap_segments (GstQTDemux * demux, - QtDemuxStream * stream) -{ - gint i; - - /* Push any initial gap segments before proceeding to the - * 'real' data */ - for (i = 0; i < stream->n_segments; i++) { - gst_qtdemux_activate_segment (demux, stream, i, stream->time_position); - - if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) { - gst_qtdemux_send_gap_for_segment (demux, stream, i, - stream->time_position); - } else { - /* Only support empty segment at the beginning followed by - * one non-empty segment, this was checked when parsing the - * edts atom, arriving here is unexpected */ - g_assert (i + 1 == stream->n_segments); - break; - } - } -} - static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) { @@ -6348,12 +7379,12 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT)) { gboolean is_gap_input = FALSE; - gint i; + GList *iter; GST_DEBUG_OBJECT (demux, "Got DISCONT, marking all streams as DISCONT"); - for (i = 0; i < demux->n_streams; i++) { - demux->streams[i]->discont = TRUE; + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + QTDEMUX_STREAM (iter->data)->discont = TRUE; } /* Check if we can land back on our feet in the case where upstream is @@ -6361,21 +7392,21 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) * in the case of trick-mode DASH for example) */ if (demux->upstream_format_is_time && GST_BUFFER_OFFSET (inbuf) != GST_BUFFER_OFFSET_NONE) { - gint i; - for (i = 0; i < demux->n_streams; i++) { + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { guint32 res; + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); GST_LOG_OBJECT (demux, - "Stream #%d , checking if offset %" G_GUINT64_FORMAT - " is a sample start", i, GST_BUFFER_OFFSET (inbuf)); + "track-id #%u , checking if offset %" G_GUINT64_FORMAT + " is a sample start", stream->track_id, GST_BUFFER_OFFSET (inbuf)); res = gst_qtdemux_find_index_for_given_media_offset_linear (demux, - demux->streams[i], GST_BUFFER_OFFSET (inbuf)); + stream, GST_BUFFER_OFFSET (inbuf)); if (res != -1) { - QtDemuxSample *sample = &demux->streams[i]->samples[res]; + QtDemuxSample *sample = &stream->samples[res]; GST_LOG_OBJECT (demux, - "Checking if sample %d from stream %d is valid (offset:%" - G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res, i, - sample->offset, sample->size); + "Checking if sample %d from track-id %u is valid (offset:%" + G_GUINT64_FORMAT " size:%" G_GUINT32_FORMAT ")", res, + stream->track_id, sample->offset, sample->size); if (sample->offset == GST_BUFFER_OFFSET (inbuf)) { GST_LOG_OBJECT (demux, "new buffer corresponds to a valid sample : %" G_GUINT32_FORMAT, @@ -6384,9 +7415,9 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) /* We can go back to standard playback mode */ demux->state = QTDEMUX_STATE_MOVIE; /* Remember which sample this stream is at */ - demux->streams[i]->sample_index = res; + stream->sample_index = res; /* Finally update all push-based values to the expected values */ - demux->neededbytes = demux->streams[i]->samples[res].size; + demux->neededbytes = stream->samples[res].size; demux->offset = GST_BUFFER_OFFSET (inbuf); demux->mdatleft = demux->mdatsize - demux->offset + demux->mdatoffset; @@ -6411,8 +7442,8 @@ gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) * previously received one. */ if (!is_gap_input && demux->fragmented && demux->segment.rate < 0) { gst_qtdemux_process_adapter (demux, TRUE); - for (i = 0; i < demux->n_streams; i++) - gst_qtdemux_stream_flush_samples_data (demux, demux->streams[i]); + g_list_foreach (demux->active_streams, + (GFunc) gst_qtdemux_stream_flush_samples_data, NULL); } } @@ -6569,12 +7600,10 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) extract_initial_length_and_fourcc (data, demux->neededbytes, NULL, &fourcc); if (fourcc == FOURCC_moov) { - gint n; - /* in usual fragmented setup we could try to scan for more * and end up at the the moov (after mdat) again */ - if (demux->got_moov && demux->n_streams > 0 && - (!demux->fragmented + if (!demux->adaptive_mode && demux->got_moov && demux->n_streams > 0 + && (!demux->fragmented || demux->last_moov_offset == demux->offset)) { GST_DEBUG_OBJECT (demux, "Skipping moov atom as we have (this) one already"); @@ -6606,30 +7635,26 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) demux->last_moov_offset = demux->offset; + /* Update streams with new moov */ + demux->old_streams = + g_list_concat (demux->old_streams, demux->active_streams); + demux->active_streams = NULL; + qtdemux_parse_moov (demux, data, demux->neededbytes); qtdemux_node_dump (demux, demux->moov_node); qtdemux_parse_tree (demux); qtdemux_prepare_streams (demux); - if (!demux->got_moov) - qtdemux_expose_streams (demux); - else { - - for (n = 0; n < demux->n_streams; n++) { - QtDemuxStream *stream = demux->streams[n]; - - gst_qtdemux_configure_stream (demux, stream); - } - } + QTDEMUX_EXPOSE_LOCK (demux); + qtdemux_expose_streams (demux); + QTDEMUX_EXPOSE_UNLOCK (demux); demux->got_moov = TRUE; - gst_qtdemux_check_send_pending_segment (demux); - - /* fragmented streams headers shouldn't contain edts atoms */ - if (!demux->fragmented) { - for (n = 0; n < demux->n_streams; n++) { - gst_qtdemux_stream_send_initial_gap_segments (demux, - demux->streams[n]); - } + if (demux->fragmented) { + /* fragmented streams headers shouldn't contain edts atoms */ + gst_qtdemux_check_send_pending_segment (demux); + } else { + gst_event_replace (&demux->pending_newsegment, NULL); + gst_qtdemux_map_and_push_segments (demux, &demux->segment); } if (demux->moov_node_compressed) { @@ -6723,7 +7748,9 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) gst_event_set_seqnum (demux->pending_newsegment, demux->segment_seqnum); } + QTDEMUX_EXPOSE_LOCK (demux); qtdemux_expose_streams (demux); + QTDEMUX_EXPOSE_UNLOCK (demux); } } else { GST_DEBUG_OBJECT (demux, "Discarding [moof]"); @@ -6840,13 +7867,22 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) case QTDEMUX_STATE_MOVIE:{ QtDemuxStream *stream = NULL; QtDemuxSample *sample; - int i = -1; GstClockTime dts, pts, duration; gboolean keyframe; + GList *iter; GST_DEBUG_OBJECT (demux, "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset); +#ifdef MP4_PUSHMODE_TRICK + if (!demux->upstream_format_is_time && !demux->flushing && + demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS && + demux->pushed_Iframe) { + demux->pushed_Iframe = FALSE; + gst_qtdemux_handle_trick (demux); + goto eos; + } +#endif if (demux->fragmented) { GST_DEBUG_OBJECT (demux, "mdat remaining %" G_GUINT64_FORMAT, demux->mdatleft); @@ -6892,7 +7928,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) GST_DEBUG_OBJECT (demux, "parsing cenc auxiliary info"); data = gst_adapter_map (demux->adapter, demux->todrop); gst_byte_reader_init (&br, data + 8, demux->todrop); - if (!qtdemux_parse_cenc_aux_info (demux, demux->streams[0], &br, + if (!qtdemux_parse_cenc_aux_info (demux, + QTDEMUX_FIRST_STREAM (demux), &br, demux->cenc_aux_info_sizes, demux->cenc_aux_sample_count)) { GST_ERROR_OBJECT (demux, "failed to parse cenc auxiliary info"); ret = GST_FLOW_ERROR; @@ -6915,13 +7952,17 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) gst_qtdemux_check_send_pending_segment (demux); /* Figure out which stream this packet belongs to */ - for (i = 0; i < demux->n_streams; i++) { - stream = demux->streams[i]; - if (stream->sample_index >= stream->n_samples) + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + stream = QTDEMUX_STREAM (iter->data); + if (stream->sample_index >= stream->n_samples) { + /* reset to be checked below G_UNLIKELY (stream == NULL) */ + stream = NULL; continue; + } GST_LOG_OBJECT (demux, - "Checking stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT - " / size:%d)", i, stream->sample_index, + "Checking track-id %u (sample_index:%d / offset:%" + G_GUINT64_FORMAT " / size:%d)", stream->track_id, + stream->sample_index, stream->samples[stream->sample_index].offset, stream->samples[stream->sample_index].size); @@ -6929,8 +7970,12 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) break; } - if (G_UNLIKELY (stream == NULL || i == demux->n_streams)) + if (G_UNLIKELY (stream == NULL)) + goto unknown_stream; + + if (!demux->isInterleaved) { goto unknown_stream; + } gst_qtdemux_stream_check_and_change_stsd_index (demux, stream); @@ -6952,7 +7997,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) /* check for segment end */ if (G_UNLIKELY (demux->segment.stop != -1 - && demux->segment.stop <= pts && stream->on_keyframe)) { + && demux->segment.stop <= pts && stream->on_keyframe + && demux->segment.rate > 0.0)) { GST_DEBUG_OBJECT (demux, "we reached the end of our segment."); stream->time_position = GST_CLOCK_TIME_NONE; /* this means EOS */ @@ -6962,8 +8008,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) /* check if all streams are eos */ ret = GST_FLOW_EOS; - for (i = 0; i < demux->n_streams; i++) { - if (!STREAM_IS_EOS (demux->streams[i])) { + for (iter = demux->active_streams; iter; iter = g_list_next (iter)) { + if (!STREAM_IS_EOS (QTDEMUX_STREAM (iter->data))) { ret = GST_FLOW_OK; break; } @@ -6977,8 +8023,39 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) /* FIXME: should either be an assert or a plain check */ g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); - ret = gst_qtdemux_decorate_and_push_buffer (demux, stream, outbuf, - dts, pts, duration, keyframe, dts, demux->offset); +#ifdef DOLBYHDR_SUPPORT + /* non-interleaved (BL & EL) Dolby Vision dual track file + * needs to drop buffer when perform push-mode seek, + * because non-key frame buffer can be followed by + * key frame buffe which is the desirable seek position */ + if (G_UNLIKELY (demux->is_dolby_hdr && demux->has_dolby_bl_cand && + demux->has_dolby_el_cand && demux->segment.start > dts)) { + GST_LOG_OBJECT (demux, + "Drop buffer with dts %" GST_TIME_FORMAT ", pts %" + GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT + " on pad %s", GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), + GST_TIME_ARGS (duration), GST_PAD_NAME (stream->pad)); + gst_buffer_unref (outbuf); + ret = GST_FLOW_OK; + } else +#endif +#ifdef MP4_PUSHMODE_TRICK + if (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + if (stream->subtype == FOURCC_vide && keyframe) { + demux->pushed_Iframe = TRUE; + ret = + gst_qtdemux_decorate_and_push_buffer (demux, stream, + outbuf, dts, pts, duration, keyframe, dts, demux->offset); + } else { + gst_buffer_unref (outbuf); + ret = GST_FLOW_OK; + } + } else +#endif + { + ret = gst_qtdemux_decorate_and_push_buffer (demux, stream, outbuf, + dts, pts, duration, keyframe, dts, demux->offset); + } } /* combine flows */ @@ -7477,9 +8554,18 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, case FOURCC_avc3: case FOURCC_H265: case FOURCC_hvc1: + case FOURCC_lhv1: + case FOURCC_lhe1: case FOURCC_hev1: case FOURCC_mjp2: case FOURCC_encv: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvav: + case FOURCC_dvhe: + case FOURCC_dvh1: + case FOURCC_av01: + case FOURCC_dav1: +#endif { guint32 version; guint32 str_len; @@ -7499,6 +8585,27 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, GST_DEBUG_OBJECT (qtdemux, "parsing in %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); +#ifdef DOLBYHDR_SUPPORT + switch (fourcc) { + case FOURCC_H264: + case FOURCC_avc1: + case FOURCC_avc3: + case FOURCC_H265: + case FOURCC_hvc1: + case FOURCC_hev1: + case FOURCC_av01: + case FOURCC_dav1: + qtdemux->has_dolby_bl_cand = TRUE; + break; + case FOURCC_dvav: + case FOURCC_dvhe: + case FOURCC_dvh1: + qtdemux->has_dolby_el_cand = TRUE; + break; + default: + break; + } +#endif /* version (2 bytes) : this is set to 0, unless a compressor has changed * its data format. * revision level (2 bytes) : must be set to 0. */ @@ -7522,6 +8629,34 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, qtdemux_parse_container (qtdemux, node, buffer + 86, end); break; } +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvcC: + case FOURCC_dvvC: + { + GST_MEMDUMP_OBJECT (qtdemux, "dvcC or dvvC", buffer, end - buffer); + if (node_length != 0x20) { + GST_LOG_OBJECT (qtdemux, + "Invalid %" GST_FOURCC_FORMAT " node length %d", + GST_FOURCC_ARGS (fourcc), node_length); + break; + } + qtdemux->dv_profile = (QT_UINT8 (buffer + 10) >> 1) & 0x7f; + qtdemux->rpu_present_flag = (QT_UINT8 (buffer + 11) >> 2) & 0x01; + qtdemux->el_present_flag = (QT_UINT8 (buffer + 11) >> 1) & 0x01; + qtdemux->bl_present_flag = QT_UINT8 (buffer + 11) & 0x01; + + GST_LOG_OBJECT (qtdemux, + "%" GST_FOURCC_FORMAT ", dv_profile = %d, rpu_present_flag = %d, " + "el_present_flag = %d, bl_present_flag = %d", + GST_FOURCC_ARGS (fourcc), qtdemux->dv_profile, + qtdemux->rpu_present_flag, qtdemux->el_present_flag, + qtdemux->bl_present_flag); + + qtdemux_parse_container (qtdemux, node, buffer + 0x20, end); + qtdemux->is_dolby_hdr = TRUE; + break; + } +#endif case FOURCC_meta: { GST_DEBUG_OBJECT (qtdemux, "parsing meta atom"); @@ -7604,6 +8739,11 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, qtdemux_parse_container (qtdemux, node, buffer + 36, end); break; } + case FOURCC_ec_3: + { + qtdemux_parse_container (qtdemux, node, buffer + 36, end); + break; + } default: if (!strcmp (type->name, "unknown")) GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4); @@ -7768,7 +8908,7 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux, g_return_val_if_fail (gst_caps_get_size (CUR_STREAM (stream)->caps) == 1, FALSE); - if (stream->protection_scheme_type != FOURCC_cenc) { + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme"); return FALSE; } @@ -7799,9 +8939,38 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux, return TRUE; } +static gboolean +gst_qtdemux_configure_dvr_caps (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + GstStructure *s; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (gst_caps_get_size (CUR_STREAM (stream)->caps) == 1, + FALSE); + + if (!qtdemux->configure_dvr) { + GST_ERROR_OBJECT (qtdemux, "dvr uuid have not been detected yet"); + return FALSE; + } + + s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0); + if (!gst_structure_has_name (s, "application/x-dvr")) { + gst_structure_set (s, + "original-media-type", G_TYPE_STRING, gst_structure_get_name (s), NULL); + gst_structure_set_name (s, "application/x-dvr"); + } + return TRUE; +} + static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) { + if (!qtdemux->fragmented && stream->duration < stream->stts_total_duration) { + check_update_duration (qtdemux, QTSTREAMTIME_TO_GSTTIME (stream, + stream->stts_total_duration)); + } + if (stream->subtype == FOURCC_vide) { /* fps is calculated base on the duration of the average framerate since * qt does not have a fixed framerate. */ @@ -7813,25 +8982,26 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) CUR_STREAM (stream)->fps_n = 0; CUR_STREAM (stream)->fps_d = 1; } else { - if (stream->duration == 0 || stream->n_samples < 2) { + guint64 duration; + guint32 n_samples; + + /* duration and n_samples can be updated for fragmented format + * so, framerate of fragmented format is calculated using data in a moof */ + if (qtdemux->fragmented && stream->n_samples_moof > 0 + && stream->duration_moof > 0) { + n_samples = stream->n_samples_moof; + duration = stream->duration_moof; + } else { + n_samples = stream->n_samples; + duration = stream->duration; + } + + if (duration == 0 || n_samples < 2) { CUR_STREAM (stream)->fps_n = stream->timescale; CUR_STREAM (stream)->fps_d = 1; fps_available = FALSE; } else { GstClockTime avg_duration; - guint64 duration; - guint32 n_samples; - - /* duration and n_samples can be updated for fragmented format - * so, framerate of fragmented format is calculated using data in a moof */ - if (qtdemux->fragmented && stream->n_samples_moof > 0 - && stream->duration_moof > 0) { - n_samples = stream->n_samples_moof; - duration = stream->duration_moof; - } else { - n_samples = stream->n_samples; - duration = stream->duration; - } /* Calculate a framerate, ignoring the first sample which is sometimes truncated */ /* stream->duration is guint64, timescale, n_samples are guint32 */ @@ -7865,6 +9035,20 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) "width", G_TYPE_INT, CUR_STREAM (stream)->width, "height", G_TYPE_INT, CUR_STREAM (stream)->height, NULL); + /* if we have valid framerate from previous moof, set using it */ + if (!fps_available && qtdemux->fragmented && stream->pad) { + GstCaps *prev_caps = gst_pad_get_current_caps (stream->pad); + if (prev_caps) { + GstStructure *s = gst_caps_get_structure (prev_caps, 0); + if (s && gst_structure_has_field (s, "framerate")) { + gst_structure_get_fraction (s, "framerate", + &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d); + fps_available = TRUE; + } + gst_caps_unref (prev_caps); + } + } + /* set framerate if calculated framerate is reliable */ if (fps_available) { gst_caps_set_simple (CUR_STREAM (stream)->caps, @@ -7951,6 +9135,13 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, stream->multiview_flags, GST_FLAG_SET_MASK_EXACT, NULL); } +#ifdef DOLBYHDR_SUPPORT + /* Prepare Dolby HDR related src caps */ + if (qtdemux->is_dolby_hdr) + if (!qtdemux_prepare_dolby_track (qtdemux, stream)) + GST_DEBUG_OBJECT (qtdemux, + "Fail to define dolby HDR stream configure"); +#endif } } @@ -7990,12 +9181,16 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) "Failed to configure protected stream caps."); return FALSE; } + } else if (qtdemux->configure_dvr) { + if (!gst_qtdemux_configure_dvr_caps (qtdemux, stream)) { + GST_ERROR_OBJECT (qtdemux, "Failed to configure dvr stream caps."); + return FALSE; + } } GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, CUR_STREAM (stream)->caps); if (stream->new_stream) { - gchar *stream_id; GstEvent *event; GstStreamFlags stream_flags = GST_STREAM_FLAG_NONE; @@ -8015,10 +9210,7 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) } stream->new_stream = FALSE; - stream_id = - gst_pad_create_stream_id_printf (stream->pad, - GST_ELEMENT_CAST (qtdemux), "%03u", stream->track_id); - event = gst_event_new_stream_start (stream_id); + event = gst_event_new_stream_start (stream->stream_id); if (qtdemux->have_group_id) gst_event_set_group_id (event, qtdemux->group_id); if (stream->disabled) @@ -8030,7 +9222,6 @@ gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) } gst_event_set_stream_flags (event, stream_flags); gst_pad_push_event (stream->pad, event); - g_free (stream_id); } prev_caps = gst_pad_get_current_caps (stream->pad); @@ -8334,6 +9525,24 @@ qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl) goto corrupt_file; } + /* Some corrupted files have incorrect stream duration info in mdhd box + * So, we need to ensure actual stream duration by using stts + */ + { + gint i; + guint pos = gst_byte_reader_get_pos (&stream->stts); + guint32 samples; + guint32 duration; + stream->stts_total_duration = 0; + for (i = 0; i < stream->n_sample_times; i++) { + samples = gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + duration = gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + + stream->stts_total_duration += (guint64) (samples * duration); + } + gst_byte_reader_set_pos (&stream->stts, pos); + } + /* sync sample atom */ stream->stps_present = FALSE; if ((stream->stss_present = @@ -8609,7 +9818,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) /* different sizes for each sample */ for (cur = first; cur <= last; cur++) { cur->size = gst_byte_reader_get_uint32_be_unchecked (&stream->stsz); - GST_LOG_OBJECT (qtdemux, "sample %d has size %u", + GST_TRACE_OBJECT (qtdemux, "sample %d has size %u", (guint) (cur - samples), cur->size); } } else { @@ -8656,7 +9865,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) --stream->last_chunk; } - GST_LOG_OBJECT (qtdemux, + GST_TRACE_OBJECT (qtdemux, "entry %d has first_chunk %d, last_chunk %d, samples_per_chunk %d" "sample desc ID: %d", i, stream->first_chunk, stream->last_chunk, stream->samples_per_chunk, stream->stsd_sample_description_id); @@ -8697,7 +9906,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) qt_atom_parser_get_offset_unchecked (&stream->co_chunk, stream->co_size); - GST_LOG_OBJECT (qtdemux, "Created entry %d with offset " + GST_TRACE_OBJECT (qtdemux, "Created entry %d with offset " "%" G_GUINT64_FORMAT, j, cur->offset); if (CUR_STREAM (stream)->samples_per_frame > 0 && @@ -8737,7 +9946,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) chunk_offset = stream->chunk_offset; for (k = stream->stsc_sample_index; k < samples_per_chunk; k++) { - GST_LOG_OBJECT (qtdemux, "creating entry %d with offset %" + GST_TRACE_OBJECT (qtdemux, "creating entry %d with offset %" G_GUINT64_FORMAT " and size %d", (guint) (cur - samples), chunk_offset, cur->size); @@ -8793,7 +10002,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) stts_time = stream->stts_time; for (j = stream->stts_sample_index; j < stts_samples; j++) { - GST_DEBUG_OBJECT (qtdemux, + GST_TRACE_OBJECT (qtdemux, "sample %d: index %d, timestamp %" GST_TIME_FORMAT, (guint) (cur - samples), j, GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, stts_time))); @@ -8898,6 +10107,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) } else { /* no stss, all samples are keyframes */ stream->all_keyframe = TRUE; + stream->n_sample_syncs = stream->n_samples; GST_DEBUG_OBJECT (qtdemux, "setting all keyframes"); } } @@ -9007,7 +10217,7 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) { GNode *elst; gint n_segments; - gint i, count, entry_size; + gint segment_number, entry_size; guint64 time; GstClockTime stime; const guint8 *buffer; @@ -9042,12 +10252,11 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, /* segments always start from 0 */ time = 0; stime = 0; - count = 0; buffer += 16; - for (i = 0; i < n_segments; i++) { + for (segment_number = 0; segment_number < n_segments; segment_number++) { guint64 duration; guint64 media_time; - gboolean time_valid = TRUE; + gboolean empty_edit = FALSE; QtDemuxSegment *segment; guint32 rate_int; GstClockTime media_start = GST_CLOCK_TIME_NONE; @@ -9056,44 +10265,44 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, media_time = QT_UINT64 (buffer + 8); duration = QT_UINT64 (buffer); if (media_time == G_MAXUINT64) - time_valid = FALSE; + empty_edit = TRUE; } else { media_time = QT_UINT32 (buffer + 4); duration = QT_UINT32 (buffer); if (media_time == G_MAXUINT32) - time_valid = FALSE; + empty_edit = TRUE; } - if (time_valid) + if (!empty_edit) media_start = QTSTREAMTIME_TO_GSTTIME (stream, media_time); - segment = &stream->segments[count++]; + segment = &stream->segments[segment_number]; /* time and duration expressed in global timescale */ segment->time = stime; - /* add non scaled values so we don't cause roundoff errors */ - if (duration || media_start == GST_CLOCK_TIME_NONE) { + if (duration != 0 || empty_edit) { + /* edge case: empty edits with duration=zero are treated here. + * (files should not have these anyway). */ + + /* add non scaled values so we don't cause roundoff errors */ time += duration; stime = QTTIME_TO_GSTTIME (qtdemux, time); segment->duration = stime - segment->time; } else { /* zero duration does not imply media_start == media_stop - * but, only specify media_start.*/ - stime = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration); - if (GST_CLOCK_TIME_IS_VALID (stime) && time_valid - && stime >= media_start) { - segment->duration = stime - media_start; - } else { - segment->duration = GST_CLOCK_TIME_NONE; - } + * but, only specify media_start. The edit ends with the track. */ + stime = segment->duration = GST_CLOCK_TIME_NONE; + /* Don't allow more edits after this one. */ + n_segments = segment_number + 1; } segment->stop_time = stime; segment->trak_media_start = media_time; /* media_time expressed in stream timescale */ - if (time_valid) { + if (!empty_edit) { segment->media_start = media_start; - segment->media_stop = segment->media_start + segment->duration; + segment->media_stop = GST_CLOCK_TIME_IS_VALID (segment->duration) + ? segment->media_start + segment->duration : GST_CLOCK_TIME_NONE; media_segments_count++; } else { segment->media_start = GST_CLOCK_TIME_NONE; @@ -9115,7 +10324,7 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, ", duration %" GST_TIME_FORMAT ", media_start %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT ") , media_stop %" GST_TIME_FORMAT " stop_time %" GST_TIME_FORMAT " rate %g, (%d) timescale %u", - i, GST_TIME_ARGS (segment->time), + segment_number, GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->duration), GST_TIME_ARGS (segment->media_start), media_time, GST_TIME_ARGS (segment->media_stop), @@ -9125,15 +10334,16 @@ qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, GST_WARNING_OBJECT (qtdemux, "Segment %d " " extends to %" GST_TIME_FORMAT " past the end of the file duration %" GST_TIME_FORMAT - " it will be truncated", i, GST_TIME_ARGS (segment->stop_time), + " it will be truncated", segment_number, + GST_TIME_ARGS (segment->stop_time), GST_TIME_ARGS (qtdemux->segment.stop)); qtdemux->segment.stop = segment->stop_time; } buffer += entry_size; } - GST_DEBUG_OBJECT (qtdemux, "found %d segments", count); - stream->n_segments = count; + GST_DEBUG_OBJECT (qtdemux, "found %d segments", n_segments); + stream->n_segments = n_segments; if (media_segments_count != 1) allow_pushbased_edts = FALSE; } @@ -9572,14 +10782,20 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux, GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box"); return FALSE; } - if (stream->protection_scheme_type == FOURCC_cenc) { + if (is_common_enc_scheme_type (stream->protection_scheme_type)) { QtDemuxCencSampleSetInfo *info; GNode *tenc; const guint8 *tenc_data; + guint8 version = 0; + guint8 crypt_byte_block = 0; + guint8 skip_byte_block = 0; guint32 isEncrypted; guint8 iv_size; const guint8 *default_kid; GstBuffer *kid_buf; + guint8 constant_iv_size = 0; + const guint8 *default_constant_iv = NULL; + GstBuffer *constant_iv_buf = NULL; if (G_UNLIKELY (!stream->protection_scheme_info)) stream->protection_scheme_info = @@ -9593,19 +10809,49 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux, "which is mandatory for Common Encryption"); return FALSE; } + tenc_data = (const guint8 *) tenc->data + 12; - isEncrypted = QT_UINT24 (tenc_data); + + if (stream->protection_scheme_type == FOURCC_cens || + stream->protection_scheme_type == FOURCC_cbcs) { + version = 1; + crypt_byte_block = QT_UINT8 (tenc_data + 1) >> 4; + skip_byte_block = QT_UINT8 (tenc_data + 1) & 0x0F; + } + + isEncrypted = QT_UINT8 (tenc_data + 2); iv_size = QT_UINT8 (tenc_data + 3); default_kid = (tenc_data + 4); kid_buf = gst_buffer_new_allocate (NULL, 16, NULL); gst_buffer_fill (kid_buf, 0, default_kid, 16); + + if (iv_size == 0) { + constant_iv_size = QT_UINT8 (tenc_data + 20); + default_constant_iv = (tenc_data + 21); + constant_iv_buf = gst_buffer_new_allocate (NULL, constant_iv_size, NULL); + gst_buffer_fill (constant_iv_buf, 0, default_constant_iv, + constant_iv_size); + } + if (info->default_properties) gst_structure_free (info->default_properties); info->default_properties = gst_structure_new ("application/x-cenc", + "scheme_type", G_TYPE_UINT, stream->protection_scheme_type, + "version", G_TYPE_UINT, version, + "crypt_byte_block", G_TYPE_UINT, crypt_byte_block, + "skip_byte_block", G_TYPE_UINT, skip_byte_block, "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1), "kid", GST_TYPE_BUFFER, kid_buf, NULL); + + if (constant_iv_size != 0 && constant_iv_buf != NULL) { + gst_structure_set (info->default_properties, + "constant_iv_size", G_TYPE_UINT, constant_iv_size, + "constant_iv", GST_TYPE_BUFFER, constant_iv_buf, NULL); + gst_buffer_unref (constant_iv_buf); + } + GST_DEBUG_OBJECT (qtdemux, "default sample properties: " "is_encrypted=%u, iv_size=%u", isEncrypted, iv_size); gst_buffer_unref (kid_buf); @@ -9613,6 +10859,12 @@ qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux, return TRUE; } +static gint +qtdemux_track_id_compare_func (QtDemuxStream * stream1, QtDemuxStream * stream2) +{ + return (gint) stream1->track_id - (gint) stream2->track_id; +} + /* parse the traks. * With each track we associate a new QtDemuxStream that contains all the info * about the trak. @@ -9630,14 +10882,13 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GNode *stbl; GNode *stsd; GNode *mp4a; - GNode *mp4v; + GNode *mp4v = NULL; GNode *esds; GNode *tref; GNode *udta; GNode *svmi; QtDemuxStream *stream = NULL; - gboolean new_stream = FALSE; const guint8 *stsd_data; const guint8 *stsd_entry_data; guint remaining_stsd_len; @@ -9665,22 +10916,13 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) !gst_byte_reader_get_uint32_be (&tkhd, &track_id)) goto corrupt_file; - if (!qtdemux->got_moov) { - if (qtdemux_find_stream (qtdemux, track_id)) - goto existing_stream; - stream = _create_stream (); - stream->track_id = track_id; - new_stream = TRUE; - } else { - stream = qtdemux_find_stream (qtdemux, track_id); - if (!stream) { - GST_WARNING_OBJECT (qtdemux, "Stream not found, going to ignore it"); - goto skip_track; - } + /* Check if current moov has duplicated track_id */ + if (qtdemux_find_stream (qtdemux, track_id)) + goto existing_stream; + + stream = _create_stream (qtdemux, track_id); + stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags); - /* reset reused stream */ - gst_qtdemux_stream_reset (qtdemux, stream); - } /* need defaults for fragments */ qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy); @@ -9773,8 +11015,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) "found, assuming preview image or something; skipping track", stream->duration, stream->timescale, qtdemux->duration, qtdemux->timescale); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + gst_qtdemux_stream_free (stream); return TRUE; } } @@ -9871,8 +11112,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (stsd_len < 24) { /* .. but skip stream with empty stsd produced by some Vivotek cameras */ if (stream->subtype == FOURCC_vivo) { - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + gst_qtdemux_stream_free (stream); return TRUE; } else { goto corrupt_file; @@ -9950,6 +11190,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) entry->width, entry->height, entry->bits_per_sample, entry->color_table_id); + if (ceil (((double) entry->width / (double) 16)) * + ceil ((double) entry->height / (double) 16) >= 8704) { + qtdemux->isBigData = TRUE; + } + depth = entry->bits_per_sample; /* more than 32 bits means grayscale */ @@ -10218,10 +11463,27 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) case FOURCC_H264: case FOURCC_avc1: case FOURCC_avc3: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvav: +#endif { gint len = QT_UINT32 (stsd_entry_data) - 0x56; const guint8 *avc_data = stsd_entry_data + 0x56; +#ifdef DOLBYHDR_SUPPORT + /* if following conditions are met, let's skip this track + * - Dolby Vision cannot be supported by this platform + * - Dolby Vision with dual-track + * - This track is for EL + */ + if (!qtdemux->dolby_vision_support && qtdemux->has_dolby_bl_cand + && fourcc == FOURCC_dvav && !qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore non SDR dolby-vision track"); + qtdemux->has_dolby_el_cand = FALSE; + goto skip_track; + } +#endif + /* find avcC */ while (len >= 0x8) { gint size; @@ -10240,6 +11502,26 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) { /* parse, if found */ GstBuffer *buf; + const guint8 *codec_data_avcC; + + codec_data_avcC = avc_data + 0x8; + codec_data_avcC += 4; + + switch (*codec_data_avcC & 0x03) { + case 0: + stream->length_size_avcC = 1; + break; + case 1: + stream->length_size_avcC = 2; + break; + case 3: + stream->length_size_avcC = 4; + break; + default: + GST_WARNING_OBJECT (qtdemux, "wrong length_size = %d", + *codec_data_avcC & 0x03); + break; + } GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd"); @@ -10330,10 +11612,30 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) case FOURCC_H265: case FOURCC_hvc1: case FOURCC_hev1: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvh1: + case FOURCC_dvhe: +#endif + case FOURCC_lhv1: + case FOURCC_lhe1: { gint len = QT_UINT32 (stsd_entry_data) - 0x56; const guint8 *hevc_data = stsd_entry_data + 0x56; +#ifdef DOLBYHDR_SUPPORT + /* If following conditions are met, let's skip this track + * - Dolby Vision cannot be supported by this platform + * - Dolby Vision with dual-track + * - This track is for EL + */ + if (!qtdemux->dolby_vision_support && qtdemux->has_dolby_bl_cand + && fourcc == FOURCC_dvhe && !qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore non SDR dolby-vision track"); + qtdemux->has_dolby_el_cand = FALSE; + goto skip_track; + } +#endif + /* find hevc */ while (len >= 0x8) { gint size; @@ -10348,12 +11650,15 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) break; switch (QT_FOURCC (hevc_data + 0x4)) { + case FOURCC_lhvC: case FOURCC_hvcC: { /* parse, if found */ GstBuffer *buf; - GST_DEBUG_OBJECT (qtdemux, "found hvcC codec_data in stsd"); + GST_DEBUG_OBJECT (qtdemux, + "found codec_data in stsd, caps are %" GST_PTR_FORMAT, + entry->caps); /* First 4 bytes are the length of the atom, the next 4 bytes * are the fourcc, the next 1 byte is the version, and the @@ -10789,6 +12094,81 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } break; } + case FOURCC_av01: + { + gint len = QT_UINT32 (stsd_entry_data) - 0x56; + const guint8 *av1_data = stsd_entry_data + 0x56; + + /* find av1C */ + while (len >= 0x8) { + gint size; + + if (QT_UINT32 (av1_data) <= len) + size = QT_UINT32 (av1_data) - 0x8; + else + size = len - 0x8; + + if (size < 1) + /* No real data, so break out */ + break; + + switch (QT_FOURCC (av1_data + 0x4)) { + case FOURCC_av1C: + { + /* parse, if found */ + GstBuffer *buf; + guint8 pres_delay_field; + + GST_DEBUG_OBJECT (qtdemux, + "found av1C codec_data in stsd of size %d", size); + + /* not enough data, just ignore and hope for the best */ + if (size < 5) + break; + + /* Content is: + * 4 bytes: atom length + * 4 bytes: fourcc + * 1 byte: version + * 3 bytes: flags + * 3 bits: reserved + * 1 bits: initial_presentation_delay_present + * 4 bits: initial_presentation_delay (if present else reserved + * rest: OBUs. + */ + + if (av1_data[9] != 0) { + GST_WARNING ("Unknown version %d of av1C box", av1_data[9]); + break; + } + + /* We skip initial_presentation_delay* for now */ + pres_delay_field = *(av1_data + 12); + if (pres_delay_field & (1 << 5)) { + gst_caps_set_simple (entry->caps, + "presentation-delay", G_TYPE_INT, + (gint) (pres_delay_field & 0x0F) + 1, NULL); + } + if (size > 5) { + buf = gst_buffer_new_and_alloc (size - 5); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + gst_buffer_fill (buf, 0, av1_data + 13, size - 5); + gst_caps_set_simple (entry->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + break; + } + default: + break; + } + + len -= size + 8; + av1_data += size + 8; + } + + break; + } default: break; } @@ -11520,6 +12900,65 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } break; } + case FOURCC_ec_3: + { + GNode *ec3, *dec3; + ec3 = qtdemux_tree_get_child_by_type (stsd, FOURCC_ec_3); + if (ec3) { + dec3 = qtdemux_tree_get_child_by_type (ec3, + GST_MAKE_FOURCC ('d', 'e', 'c', '3')); + + if (dec3) { + const guint8 *dec3_data = dec3->data; + gint len = QT_UINT32 (dec3_data); + + if (len < 12) { /* minimum required bit for ATMOS */ + GST_DEBUG_OBJECT (qtdemux, + "dec3 length is too small to define ATMOS"); + } else { + /* Refer to ETSI TS 102.366 v1.3.1 Annex F */ + guint8 num_ind_sub = QT_UINT8 (dec3_data + 9) & 0x7; + guint offset = 10; /* start from the next bit of num_ind_sub */ + gint i; + + for (i = 0; i < num_ind_sub + 1; i++) { + /* skip bytes for fields followed by num_dep_sub */ + offset += 2; + /* skip byte(s) based on num_dep_sub */ + offset += (QT_UINT8 (dec3_data + offset) & 0x30) ? 2 : 1; + } + + if (len - offset > 1) { /* need at least 2 bytes to be ATMOS */ + if (QT_UINT8 (dec3_data + offset) & 0x1) { + /* flag_ec3_extension_type_a == 1 */ + GST_DEBUG_OBJECT (qtdemux, "set ATMOS in caps"); + gst_caps_set_simple (entry->caps, + "immersive", G_TYPE_STRING, "ATMOS", NULL); + } else { + GST_DEBUG_OBJECT (qtdemux, + "ec3_ext_type_a flag was not detected, not an ATMOS"); + } + } else { + GST_DEBUG_OBJECT (qtdemux, + "no room for complexity_index_type_a, not an ATMOS"); + } + } + } + } + } + break; + case GST_MAKE_FOURCC ('s', 'q', 'c', 'p'): + { + /* FIXME:SQCP audio codec is not supported as of now. Maybe this + * codec can be supported in the future. + * Fix for http://hlm.lge.com/issue/browse/TMFTASK-8017 + */ + if (!qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore track with codec SQCP "); + goto skip_track; + } + break; + } case FOURCC_lpcm: /* Fully handled elsewhere */ break; @@ -11631,6 +13070,27 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (!qtdemux_stbl_init (qtdemux, stream, stbl)) goto samples_failed; + if (stream->stco.data != NULL) { + guint32 entry_num; + + entry_num = QT_UINT32 (stream->stco.data + stream->co_size); + + if (entry_num > 1 && entry_num < G_MAXUINT32) { + stream->first_chunk_offset = + QT_UINT32 (stream->stco.data + stream->co_size * 2); + stream->last_chunk_offset = + QT_UINT32 (stream->stco.data + stream->co_size * (entry_num + 1)); + + GST_DEBUG_OBJECT (qtdemux, + "(stream %" GST_FOURCC_FORMAT ") first offset %" G_GINT64_FORMAT + ", last offset %" G_GINT64_FORMAT, GST_FOURCC_ARGS (stream->subtype), + stream->first_chunk_offset, stream->last_chunk_offset); + } else { + stream->first_chunk_offset = 0; + stream->last_chunk_offset = 0; + } + } + if (qtdemux->fragmented) { guint64 offset; @@ -11671,15 +13131,17 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) qtdemux_parse_udta (qtdemux, stream->stream_tags, udta); } - /* now we are ready to add the stream */ - if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS) - goto too_many_streams; + /* add track_id in tag */ + gst_tag_list_add (stream->stream_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_ID, stream->track_id, NULL); - if (!qtdemux->got_moov) { - qtdemux->streams[qtdemux->n_streams] = stream; - qtdemux->n_streams++; - GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams); - } + /* Insert and sort new stream in track-id order. + * This will help in comparing old/new streams during stream update check */ + qtdemux->active_streams = + g_list_insert_sorted (qtdemux->active_streams, stream, + (GCompareFunc) qtdemux_track_id_compare_func); + qtdemux->n_streams++; + GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams); return TRUE; @@ -11687,23 +13149,21 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) skip_track: { GST_INFO_OBJECT (qtdemux, "skip disabled track"); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + gst_qtdemux_stream_free (stream); return TRUE; } corrupt_file: { GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, (_("This file is corrupt and cannot be played.")), (NULL)); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + if (stream) + gst_qtdemux_stream_free (stream); return FALSE; } error_encrypted: { GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL)); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + gst_qtdemux_stream_free (stream); return FALSE; } samples_failed: @@ -11712,31 +13172,20 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) /* we posted an error already */ /* free stbl sub-atoms */ gst_qtdemux_stbl_free (stream); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); + gst_qtdemux_stream_free (stream); return FALSE; } existing_stream: { GST_INFO_OBJECT (qtdemux, "stream with track id %i already exists", track_id); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); return TRUE; } unknown_stream: { GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (stream->subtype)); - if (new_stream) - gst_qtdemux_stream_free (qtdemux, stream); - return TRUE; - } -too_many_streams: - { - GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX, - (_("This file contains too many streams. Only playing first %d"), - GST_QTDEMUX_MAX_STREAMS), (NULL)); + gst_qtdemux_stream_free (stream); return TRUE; } } @@ -11752,8 +13201,8 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux) QtDemuxStream *stream = NULL; gint64 size, sys_bitrate, sum_bitrate = 0; GstClockTime duration; - gint i; guint bitrate; + GList *iter; if (qtdemux->fragmented) return; @@ -11781,22 +13230,23 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux) return; } - for (i = 0; i < qtdemux->n_streams; i++) { - switch (qtdemux->streams[i]->subtype) { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *str = QTDEMUX_STREAM (iter->data); + switch (str->subtype) { case FOURCC_soun: case FOURCC_vide: GST_DEBUG_OBJECT (qtdemux, "checking bitrate for %" GST_PTR_FORMAT, - CUR_STREAM (qtdemux->streams[i])->caps); + CUR_STREAM (str)->caps); /* retrieve bitrate, prefer avg then max */ bitrate = 0; - if (qtdemux->streams[i]->stream_tags) { - if (gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags, + if (str->stream_tags) { + if (gst_tag_list_get_uint (str->stream_tags, GST_TAG_MAXIMUM_BITRATE, &bitrate)) GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate); - if (gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags, + if (gst_tag_list_get_uint (str->stream_tags, GST_TAG_NOMINAL_BITRATE, &bitrate)) GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate); - if (gst_tag_list_get_uint (qtdemux->streams[i]->stream_tags, + if (gst_tag_list_get_uint (str->stream_tags, GST_TAG_BITRATE, &bitrate)) GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate); } @@ -11808,7 +13258,7 @@ gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux) ">1 stream with unknown bitrate - bailing"); return; } else - stream = qtdemux->streams[i]; + stream = str; } default: @@ -11849,16 +13299,30 @@ static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux) { gint i; + guint64 last_offset = 0; GstFlowReturn ret = GST_FLOW_OK; + GList *iter, *next; GST_DEBUG_OBJECT (qtdemux, "prepare streams"); - for (i = 0; ret == GST_FLOW_OK && i < qtdemux->n_streams; i++) { - QtDemuxStream *stream = qtdemux->streams[i]; + for (iter = qtdemux->active_streams, i = 0; ret == GST_FLOW_OK && iter; + iter = next, i++) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); guint32 sample_num = 0; - GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT, - i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc)); + next = iter->next; + + GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT, + stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc)); + + if (i == 0) + last_offset = stream->last_chunk_offset; + else if (last_offset != 0 && stream->first_chunk_offset > last_offset + && ((stream->subtype == FOURCC_vide) + || stream->subtype == FOURCC_soun)) { + qtdemux->isInterleaved = FALSE; + GST_WARNING_OBJECT (qtdemux, "stream not interleaved file"); + } if (qtdemux->fragmented) { /* need all moov samples first */ @@ -11881,7 +13345,14 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux) * in push mode, we'll just have to deal with it */ if (G_UNLIKELY (qtdemux->pullbased && !stream->n_samples)) { GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding"); - gst_qtdemux_remove_stream (qtdemux, i); + gst_qtdemux_remove_stream (qtdemux, stream); + i--; + continue; + } else if (stream->track_id == qtdemux->chapters_track_id && + (stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl)) { + /* TODO - parse chapters track and expose it as GstToc; For now just ignore it + so that it doesn't look like a subtitle track */ + gst_qtdemux_remove_stream (qtdemux, stream); i--; continue; } @@ -11894,7 +13365,7 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux) } if (stream->n_samples > 0 && stream->stbl_index >= 0) { stream->first_duration = stream->samples[0].duration; - GST_LOG_OBJECT (qtdemux, "stream %d first duration %u", + GST_LOG_OBJECT (qtdemux, "track-id %u first duration %u", stream->track_id, stream->first_duration); } } @@ -11902,77 +13373,215 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux) return ret; } +static GList * +_stream_in_list (GList * list, QtDemuxStream * stream) +{ + GList *iter; + + for (iter = list; iter; iter = g_list_next (iter)) { + QtDemuxStream *tmp = QTDEMUX_STREAM (iter->data); + if (g_str_equal (tmp->stream_id, stream->stream_id)) + return iter; + } + + return NULL; +} + +static gboolean +qtdemux_is_streams_update (GstQTDemux * qtdemux) +{ + GList *new, *old; + + if (!qtdemux->active_streams) + return FALSE; + + if (qtdemux->adaptive_mode) { + GST_DEBUG ("Do not reuse stream in adaptive mode"); + return TRUE; + } + /* streams in list are sorted in track-id order */ + for (new = qtdemux->active_streams, old = qtdemux->old_streams; new && old; + new = g_list_next (new), old = g_list_next (old)) { + + /* Different stream-id, updated */ + if (g_strcmp0 (QTDEMUX_STREAM (new->data)->stream_id, + QTDEMUX_STREAM (old->data)->stream_id)) + return TRUE; + } + + /* Different length, updated */ + if (new != NULL || old != NULL) + return TRUE; + + return FALSE; +} + +static gboolean +qtdemux_reuse_and_configure_stream (GstQTDemux * qtdemux, + QtDemuxStream * oldstream, QtDemuxStream * newstream) +{ + /* Connect old stream's srcpad to new stream */ + newstream->pad = oldstream->pad; + oldstream->pad = NULL; + + /* unset new_stream to prevent stream-start event */ + newstream->new_stream = FALSE; + + return gst_qtdemux_configure_stream (qtdemux, newstream); +} + + +static gboolean +qtdemux_update_streams (GstQTDemux * qtdemux) +{ + gint i; + GList *iter, *next; + + /* At below, figure out which stream in active_streams has identical stream-id + * with that of in old_streams. If there is matching stream-id, + * corresponding newstream will not be exposed again, + * but demux will reuse srcpad of matched old stream + * + * active_streams : newly created streams from the latest moov + * old_streams : existing streams (belong to previous moov) + */ + + /* Count n_streams again */ + qtdemux->n_streams = 0; + + for (iter = qtdemux->active_streams, i = 0; iter; iter = next, i++) { + GList *tmp; + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + + next = iter->next; + GST_DEBUG_OBJECT (qtdemux, "track-id %u, fourcc %" GST_FOURCC_FORMAT, + stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc)); + + qtdemux->n_streams++; + + if (!qtdemux->adaptive_mode && qtdemux->streams_aware + && (tmp = _stream_in_list (qtdemux->old_streams, stream)) != NULL + && QTDEMUX_STREAM (tmp->data)->pad) { + QtDemuxStream *oldstream = QTDEMUX_STREAM (tmp->data); + + GST_DEBUG_OBJECT (qtdemux, "Reuse stream %d", i); + + if (!qtdemux_reuse_and_configure_stream (qtdemux, oldstream, stream)) + return FALSE; + + qtdemux->old_streams = g_list_remove (qtdemux->old_streams, oldstream); + gst_qtdemux_stream_free (oldstream); + } else { + GstTagList *list; + + /* now we have all info and can expose */ + list = stream->stream_tags; + stream->stream_tags = NULL; + if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + return FALSE; + + if (!qtdemux->pending_newsegment) { + qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (qtdemux->pending_newsegment, + qtdemux->segment_seqnum); + } + } + } + + return TRUE; +} + +/* Must be called with expose lock */ static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux) { - gint i; - GSList *oldpads = NULL; - GSList *iter; + GList *iter, *next; GST_DEBUG_OBJECT (qtdemux, "exposing streams"); - for (i = 0; i < qtdemux->n_streams; i++) { - QtDemuxStream *stream = qtdemux->streams[i]; - GstPad *oldpad = stream->pad; - GstTagList *list; - - GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT, - i, stream->track_id, GST_FOURCC_ARGS (CUR_STREAM (stream)->fourcc)); + if (!qtdemux_is_streams_update (qtdemux)) { + GList *new, *old; - if ((stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) && - stream->track_id == qtdemux->chapters_track_id) { - /* TODO - parse chapters track and expose it as GstToc; For now just ignore it - so that it doesn't look like a subtitle track */ - gst_qtdemux_remove_stream (qtdemux, i); - i--; - continue; + GST_DEBUG_OBJECT (qtdemux, "Reuse all streams"); + for (new = qtdemux->active_streams, old = qtdemux->old_streams; new && old; + new = g_list_next (new), old = g_list_next (old)) { + if (!qtdemux_reuse_and_configure_stream (qtdemux, + QTDEMUX_STREAM (old->data), QTDEMUX_STREAM (new->data))) + return GST_FLOW_ERROR; } - /* now we have all info and can expose */ - list = stream->stream_tags; - stream->stream_tags = NULL; - if (oldpad) - oldpads = g_slist_prepend (oldpads, oldpad); - if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + g_list_free_full (qtdemux->old_streams, + (GDestroyNotify) gst_qtdemux_stream_free); + qtdemux->old_streams = NULL; + + return GST_FLOW_OK; + } + + if (qtdemux->streams_aware) { + if (!qtdemux_update_streams (qtdemux)) return GST_FLOW_ERROR; + } else { + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + GstTagList *list; + + /* now we have all info and can expose */ + list = stream->stream_tags; + stream->stream_tags = NULL; + if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + return GST_FLOW_ERROR; + + if (!qtdemux->pending_newsegment) { + qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (qtdemux->pending_newsegment, + qtdemux->segment_seqnum); + } + } } gst_qtdemux_guess_bitrate (qtdemux); gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux)); - for (iter = oldpads; iter; iter = g_slist_next (iter)) { - GstPad *oldpad = iter->data; - GstEvent *event; + /* If we have still old_streams, it's no more used stream */ + for (iter = qtdemux->old_streams; iter; iter = next) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); + next = g_list_next (iter); - event = gst_event_new_eos (); - if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) - gst_event_set_seqnum (event, qtdemux->segment_seqnum); + if (stream->pad) { + GstEvent *event; + + event = gst_event_new_eos (); + if (qtdemux->segment_seqnum != GST_SEQNUM_INVALID) + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + + gst_pad_push_event (stream->pad, event); + } - gst_pad_push_event (oldpad, event); - gst_pad_set_active (oldpad, FALSE); - gst_element_remove_pad (GST_ELEMENT (qtdemux), oldpad); - gst_flow_combiner_remove_pad (qtdemux->flowcombiner, oldpad); - gst_object_unref (oldpad); + qtdemux->old_streams = g_list_remove (qtdemux->old_streams, stream); + gst_qtdemux_stream_free (stream); } /* check if we should post a redirect in case there is a single trak * and it is a redirecting trak */ - if (qtdemux->n_streams == 1 && qtdemux->streams[0]->redirect_uri != NULL) { + if (qtdemux->n_streams == 1 && + QTDEMUX_FIRST_STREAM (qtdemux)->redirect_uri != NULL) { GstMessage *m; GST_INFO_OBJECT (qtdemux, "Issuing a redirect due to a single track with " "an external content"); m = gst_message_new_element (GST_OBJECT_CAST (qtdemux), gst_structure_new ("redirect", - "new-location", G_TYPE_STRING, qtdemux->streams[0]->redirect_uri, - NULL)); + "new-location", G_TYPE_STRING, + QTDEMUX_FIRST_STREAM (qtdemux)->redirect_uri, NULL)); gst_element_post_message (GST_ELEMENT_CAST (qtdemux), m); qtdemux->posted_redirect = TRUE; } - for (i = 0; i < qtdemux->n_streams; i++) { - QtDemuxStream *stream = qtdemux->streams[i]; + for (iter = qtdemux->active_streams; iter; iter = g_list_next (iter)) { + QtDemuxStream *stream = QTDEMUX_STREAM (iter->data); qtdemux_do_allocation (qtdemux, stream); } @@ -13309,6 +14918,41 @@ read_descr_size (guint8 * ptr, guint8 * end, guint8 ** end_out) return len; } +static void +qtdemux_set_custom_video_caps (GstQTDemux * qtdemux, + QtDemuxStreamStsdEntry * entry, GstCaps * caps) +{ + char *s, fourstr[5]; + const char *container; + + g_return_if_fail (entry != NULL); + g_return_if_fail (entry->fourcc); + g_return_if_fail (caps != NULL); + + caps = gst_caps_make_writable (caps); + if (qtdemux->major_brand == FOURCC_mjp2) + container = "Motion JPEG 2000"; + else if ((qtdemux->major_brand & 0xffff) == GST_MAKE_FOURCC ('3', 'g', 0, 0)) + container = "3GP"; + else if (qtdemux->major_brand == FOURCC_qt__) + container = "Quicktime"; + else if (qtdemux->fragmented) + container = "ISO fMP4"; + else + container = "ISO MP4/M4A"; + + GST_DEBUG_OBJECT (qtdemux, "fourcc %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (entry->fourcc)); + + g_snprintf (fourstr, 5, "%" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (entry->fourcc)); + s = g_strdup_printf ("%s", g_strstrip (fourstr)); + gst_caps_set_simple (caps, "container", G_TYPE_STRING, container, + "format", G_TYPE_STRING, s, "timestamptype", G_TYPE_BOOLEAN, TRUE, NULL); + + g_free (s); +} + /* this can change the codec originally present in @list */ static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, @@ -13446,7 +15090,7 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, /* Override channels and rate based on the codec_data, as it's often * wrong. */ /* Only do so for basic setup without HE-AAC extension */ - if (data_ptr && data_len == 2) { + if (data_ptr && data_len >= 2) { guint channels, rate; channels = gst_codec_utils_aac_get_channels (data_ptr, data_len); @@ -13456,10 +15100,8 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, rate = gst_codec_utils_aac_get_sample_rate (data_ptr, data_len); if (rate > 0) entry->rate = rate; - } - /* Set level and profile if possible */ - if (data_ptr != NULL && data_len >= 2) { + /* Set level and profile if possible */ gst_codec_utils_aac_caps_set_level_and_profile (entry->caps, data_ptr, data_len); } else { @@ -13563,17 +15205,20 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_simple ("audio/x-ac3", "framed", G_TYPE_BOOLEAN, TRUE, NULL); break; - case 0xA9: /* AC3 */ - codec_name = "DTS audio"; - caps = gst_caps_new_simple ("audio/x-dts", - "framed", G_TYPE_BOOLEAN, TRUE, NULL); - break; case 0xE1: /* QCELP */ /* QCELP, the codec_data is a riff tag (little endian) with * more info (http://ftp.3gpp2.org/TSGC/Working/2003/2003-05-SanDiego/TSG-C-2003-05-San%20Diego/WG1/SWG12/C12-20030512-006%20=%20C12-20030217-015_Draft_Baseline%20Text%20of%20FFMS_R2.doc). */ caps = gst_caps_new_empty_simple ("audio/qcelp"); codec_name = "QCELP"; break; + case 0xdd: + /* test file for vorbis... this value is not defined + exactly in ISO14496-1 So, I think that we need to send mesage + "no audio" to app. */ + GST_DEBUG_OBJECT (qtdemux, "vorbis"); + caps = gst_caps_new_empty_simple ("audio/x-unknown"); + codec_name = "unknown"; + break; default: break; } @@ -13582,6 +15227,9 @@ gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, if (caps) { gst_caps_unref (entry->caps); entry->caps = caps; + + if (stream->subtype == FOURCC_vide) + qtdemux_set_custom_video_caps (qtdemux, entry, entry->caps); } if (codec_name && list) @@ -13644,7 +15292,12 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_empty_simple ("image/png"); break; case FOURCC_jpeg: - _codec ("JPEG still images"); + /* fourcc is JPEG even if the file codec is Motion Jpeg so, detec it with + duration and codec name is Motion-JPEG. 2012-09-26 */ + if (stream->duration) + _codec ("Motion-JPEG"); + else + _codec ("JPEG still images"); caps = gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE, NULL); @@ -13825,29 +15478,15 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_simple ("video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, NULL); break; - case GST_MAKE_FOURCC ('D', 'I', 'V', '3'): - _codec ("DivX 3"); - caps = gst_caps_new_simple ("video/x-divx", - "divxversion", G_TYPE_INT, 3, NULL); - break; - case GST_MAKE_FOURCC ('D', 'I', 'V', 'X'): - case GST_MAKE_FOURCC ('d', 'i', 'v', 'x'): - _codec ("DivX 4"); - caps = gst_caps_new_simple ("video/x-divx", - "divxversion", G_TYPE_INT, 4, NULL); - break; - case GST_MAKE_FOURCC ('D', 'X', '5', '0'): - _codec ("DivX 5"); - caps = gst_caps_new_simple ("video/x-divx", - "divxversion", G_TYPE_INT, 5, NULL); - break; - case GST_MAKE_FOURCC ('F', 'F', 'V', '1'): _codec ("FFV1"); caps = gst_caps_new_simple ("video/x-ffv", "ffvversion", G_TYPE_INT, 1, NULL); break; - + case GST_MAKE_FOURCC ('D', 'I', 'V', '3'): + case GST_MAKE_FOURCC ('D', 'I', 'V', 'X'): + case GST_MAKE_FOURCC ('d', 'i', 'v', 'x'): + case GST_MAKE_FOURCC ('D', 'X', '5', '0'): case GST_MAKE_FOURCC ('3', 'I', 'V', '1'): case GST_MAKE_FOURCC ('3', 'I', 'V', '2'): case FOURCC_XVID: @@ -13874,6 +15513,9 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, break; case FOURCC_H264: case FOURCC_avc1: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvav: +#endif _codec ("H.264 / AVC"); caps = gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING, "avc", @@ -13887,17 +15529,35 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, break; case FOURCC_H265: case FOURCC_hvc1: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvh1: +#endif _codec ("H.265 / HEVC"); caps = gst_caps_new_simple ("video/x-h265", "stream-format", G_TYPE_STRING, "hvc1", "alignment", G_TYPE_STRING, "au", NULL); break; + case FOURCC_lhv1: + _codec ("H265 / L-HEVC"); + caps = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "lhv1", + "alignment", G_TYPE_STRING, "au", NULL); + break; case FOURCC_hev1: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvhe: +#endif _codec ("H.265 / HEVC"); caps = gst_caps_new_simple ("video/x-h265", "stream-format", G_TYPE_STRING, "hev1", "alignment", G_TYPE_STRING, "au", NULL); break; + case FOURCC_lhe1: + _codec ("H265 / L-HEVC"); + caps = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "lhe1", + "alignment", G_TYPE_STRING, "au", NULL); + break; case FOURCC_rle_: _codec ("Run-length encoding"); caps = gst_caps_new_simple ("video/x-rle", @@ -13979,6 +15639,11 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, _codec ("AVID DNxHD"); caps = gst_caps_from_string ("video/x-dnxhd"); break; + case GST_MAKE_FOURCC ('A', 'V', 'd', 'v'): + GST_DEBUG_OBJECT (qtdemux, "AVID DVCPRO 50 "); + _codec ("AVID DVCPRO 50"); + caps = gst_caps_from_string ("video/x-avdv"); + break; case FOURCC_VP80: case FOURCC_vp08: _codec ("On2 VP8"); @@ -14034,6 +15699,11 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, caps = gst_caps_new_simple ("video/x-wmv", "wmvversion", G_TYPE_INT, 3, "format", G_TYPE_STRING, "WVC1", NULL); break; + case FOURCC_av01: + case FOURCC_dav1: + _codec ("AV1"); + caps = gst_caps_new_empty_simple ("video/x-av1"); + break; case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'): default: { @@ -14042,6 +15712,10 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, } } + if (caps) { + qtdemux_set_custom_video_caps (qtdemux, entry, caps); + } + if (format != GST_VIDEO_FORMAT_UNKNOWN) { GstVideoInfo info; @@ -14167,21 +15841,21 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, case 0x6d730002: _codec ("Microsoft ADPCM"); /* Microsoft ADPCM-ACM code 2 */ - caps = gst_caps_new_simple ("audio/x-adpcm", - "layout", G_TYPE_STRING, "microsoft", NULL); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "microsoft", "block_align", G_TYPE_INT, entry->bytes_per_frame, NULL); break; case 0x1100736d: case 0x6d730011: _codec ("DVI/IMA ADPCM"); - caps = gst_caps_new_simple ("audio/x-adpcm", - "layout", G_TYPE_STRING, "dvi", NULL); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "dvi", "block_align", G_TYPE_INT, entry->bytes_per_frame, NULL); break; case 0x1700736d: case 0x6d730017: _codec ("DVI/Intel IMA ADPCM"); /* FIXME DVI/Intel IMA ADPCM/ACM code 17 */ - caps = gst_caps_new_simple ("audio/x-adpcm", - "layout", G_TYPE_STRING, "quicktime", NULL); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "quicktime" "block_align", G_TYPE_INT, entry->bytes_per_frame, NULL); break; case 0x5500736d: case 0x6d730055: @@ -14199,7 +15873,7 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, "mpegversion", G_TYPE_INT, 1, NULL); break; case 0x20736d: - case GST_MAKE_FOURCC ('e', 'c', '-', '3'): + case FOURCC_ec_3: _codec ("EAC-3 audio"); caps = gst_caps_new_simple ("audio/x-eac3", "framed", G_TYPE_BOOLEAN, TRUE, NULL); @@ -14207,23 +15881,17 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, break; case GST_MAKE_FOURCC ('s', 'a', 'c', '3'): // Nero Recode case FOURCC_ac_3: + case GST_MAKE_FOURCC ('3', '-', 'c', 'a'): _codec ("AC-3 audio"); caps = gst_caps_new_simple ("audio/x-ac3", "framed", G_TYPE_BOOLEAN, TRUE, NULL); entry->sampled = TRUE; break; - case GST_MAKE_FOURCC ('d', 't', 's', 'c'): - case GST_MAKE_FOURCC ('D', 'T', 'S', ' '): - _codec ("DTS audio"); - caps = gst_caps_new_simple ("audio/x-dts", - "framed", G_TYPE_BOOLEAN, TRUE, NULL); - entry->sampled = TRUE; - break; - case GST_MAKE_FOURCC ('d', 't', 's', 'h'): // DTS-HD - case GST_MAKE_FOURCC ('d', 't', 's', 'l'): // DTS-HD Lossless - _codec ("DTS-HD audio"); - caps = gst_caps_new_simple ("audio/x-dts", - "framed", G_TYPE_BOOLEAN, TRUE, NULL); + case FOURCC_ac_4: + _codec ("AC-4 audio"); + caps = gst_caps_new_simple ("audio/x-ac4", + "framed", G_TYPE_BOOLEAN, TRUE, "frame-format", G_TYPE_STRING, "RAW", + NULL); entry->sampled = TRUE; break; case FOURCC_MAC3: @@ -14250,6 +15918,12 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, "mpegversion", G_TYPE_INT, 4, "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", NULL); break; + case FOURCC_mhm1: + _codec ("MPEG-H 3D audio"); + caps = gst_caps_new_simple ("audio/mpeg-h", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + entry->sampled = TRUE; + break; case GST_MAKE_FOURCC ('Q', 'D', 'M', 'C'): _codec ("QDesign Music"); caps = gst_caps_new_empty_simple ("audio/x-qdm"); @@ -14280,8 +15954,8 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, break; case FOURCC_ima4: _codec ("Quicktime IMA ADPCM"); - caps = gst_caps_new_simple ("audio/x-adpcm", - "layout", G_TYPE_STRING, "quicktime", NULL); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "quicktime", "block_align", G_TYPE_INT, entry->bytes_per_frame, NULL); break; case FOURCC_alac: _codec ("Apple lossless audio"); @@ -14392,7 +16066,7 @@ qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, name = gst_structure_get_name (s); if (g_str_has_prefix (name, "audio/x-raw")) { stream->need_clip = TRUE; - stream->max_buffer_size = 4096 * entry->bytes_per_frame; + stream->max_buffer_size = entry->rate * entry->bytes_per_frame; GST_DEBUG ("setting max buffer size to %d", stream->max_buffer_size); } return caps; @@ -14424,10 +16098,6 @@ qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, /* actual text piece needs to be extracted */ stream->need_process = TRUE; break; - case FOURCC_stpp: - _codec ("XML subtitles"); - caps = gst_caps_new_empty_simple ("application/ttml+xml"); - break; default: { caps = _get_unknown_codec_name ("text", fourcc); @@ -14477,3 +16147,74 @@ gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux, g_ptr_array_add (qtdemux->protection_system_ids, g_ascii_strdown (system_id, -1)); } + +static gboolean +is_common_enc_scheme_type (guint32 scheme_type) +{ + if (scheme_type == FOURCC_cenc || + scheme_type == FOURCC_cens || + scheme_type == FOURCC_cbc1 || scheme_type == FOURCC_cbcs) { + return TRUE; + } + + return FALSE; +} + +#ifdef DOLBYHDR_SUPPORT +static gboolean +qtdemux_prepare_dolby_track (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (qtdemux->is_dolby_hdr, FALSE); + + GST_DEBUG_OBJECT (qtdemux, "prepare dolby track"); + + /* dv_profile ref. in Table 2-1, Dolby Vision Profiles + * 0 : dvav.per, BL(SDR):EL = 1:1/4 + * 1 : dvav.pen, BL(non):EL = 1:1 + * 2 : dvhe.der, BL(SDR):EL = 1:1/4 + * 3 : dvhe.den, BL(non):EL = 1:1 + * 4 : dvhe.dtr, BL(SDR):EL = 1:1/4 + * 5 : dvhe.stn, BL(non):EL = 1:none + * 6 : dvhe.dth, BL(HDR):EL = 1:1/4 + * 7 : dvhe.dtb, BL(HDR):EL = 1:1/4 (for UHD) or 1:1 (for FHD) + * 8 : dvhe.sth, BL(HDR):EL = 1:none + * 8 : dvhe.str, BL(SDR):EL = 1:none + * 8 : dvhe.stl, BL(HLG):EL = 1:none + * 8 : dvhe.stg, BL(HLG):EL = 1:none + * 8 : dvhe.sts, BL(SDR):EL = 1:none + * 9 : dvav.ser, BL(SDR):EL = 1:none */ + + /* 1. Check profile and supportability */ + if (!qtdemux->dolby_vision_support) { + if (qtdemux->dv_profile == 1 || qtdemux->dv_profile == 3 + || qtdemux->dv_profile == 5) { + /* if following conditions are met, "no playable stream error" will be posted + * - Dolby Vision cannot be supported by this platform + * - non-backward compatible profile (i.e., profile 1, 3, and 5) + */ + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file contains no playable streams.")), + ("This profileID %d of DolbyVision is not supported cross-compatibility.", + qtdemux->dv_profile)); + return FALSE; + } + } else { + GST_DEBUG_OBJECT (qtdemux, "Set dolby-vision TRUE"); + gst_caps_set_simple (CUR_STREAM (stream)->caps, "dolby-vision", + G_TYPE_BOOLEAN, TRUE, NULL); + if (qtdemux->dv_profile != -1) + gst_caps_set_simple (CUR_STREAM (stream)->caps, "dolby-vision-profile", + G_TYPE_INT, qtdemux->dv_profile, NULL); + } + + /* 2. Check single- or dual-track */ + if (qtdemux->has_dolby_bl_cand && qtdemux->has_dolby_el_cand) { + gst_caps_set_simple (CUR_STREAM (stream)->caps, "need-compositor", + G_TYPE_BOOLEAN, TRUE, "dolby-vision-track", G_TYPE_STRING, "dual", + NULL); + } + + return TRUE; +} +#endif diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h index ad4da3ef2f..f4cb027fe4 100644 --- a/gst/isomp4/qtdemux.h +++ b/gst/isomp4/qtdemux.h @@ -45,7 +45,8 @@ G_BEGIN_DECLS #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag" #define GST_QT_DEMUX_CLASSIFICATION_TAG "classification" -#define GST_QTDEMUX_MAX_STREAMS 32 +#define DOLBYHDR_SUPPORT +#define MP4_PUSHMODE_TRICK typedef struct _GstQTDemux GstQTDemux; typedef struct _GstQTDemuxClass GstQTDemuxClass; @@ -73,7 +74,16 @@ struct _GstQTDemux { gboolean posted_redirect; - QtDemuxStream *streams[GST_QTDEMUX_MAX_STREAMS]; + /* Protect pad exposing from flush event */ + GMutex expose_lock; + + /* Avoid multiple seek event pushed at qtdemux sink pad at the same time */ + GMutex seekevent_lock; + + /* list of QtDemuxStream */ + GList *active_streams; + GList *old_streams; + gint n_streams; gint n_video_streams; gint n_audio_streams; @@ -111,6 +121,7 @@ struct _GstQTDemux { guint header_size; GstTagList *tag_list; + GstTagList *upstream_tag_list; /* configured playback region */ GstSegment segment; @@ -152,6 +163,9 @@ struct _GstQTDemux { guint8 *cenc_aux_info_sizes; guint32 cenc_aux_sample_count; + /* Whether the parent bin is streams-aware, meaning we can + * add/remove streams at any point in time */ + gboolean streams_aware; /* * ALL VARIABLES BELOW ARE ONLY USED IN PUSH-BASED MODE @@ -229,6 +243,33 @@ struct _GstQTDemux { * header start. * Note : This is not computed from the GST_BUFFER_OFFSET field */ guint64 fragment_start_offset; + +#ifdef DOLBYHDR_SUPPORT + /* Dolby HDR */ + gboolean dolby_vision_support; + gboolean is_dolby_hdr; + gboolean has_dolby_bl_cand; + gboolean has_dolby_el_cand; + + /* Dolby HDR dvcC info. */ + gint8 dv_profile; + gboolean rpu_present_flag; + gboolean el_present_flag; + gboolean bl_present_flag; +#endif + +#ifdef MP4_PUSHMODE_TRICK + gdouble demux_rate; + gboolean pushed_Iframe; + gboolean byte_seeking; + gboolean flushing; +#endif + guint32 dlna_opval; + gboolean isInterleaved; + gboolean isStartKeyFrame; + gboolean isBigData; + gboolean configure_dvr; + gboolean adaptive_mode; }; struct _GstQTDemuxClass { diff --git a/gst/isomp4/qtdemux_dump.c b/gst/isomp4/qtdemux_dump.c index 5ff2e9288c..a7ea88d358 100644 --- a/gst/isomp4/qtdemux_dump.c +++ b/gst/isomp4/qtdemux_dump.c @@ -303,6 +303,51 @@ qtdemux_dump_stsd_avc1 (GstQTDemux * qtdemux, GstByteReader * data, guint size, return TRUE; } + +static gboolean +qtdemux_dump_stsd_av01 (GstQTDemux * qtdemux, GstByteReader * data, guint size, + int depth) +{ + guint compressor_len; + char compressor_name[32]; + + /* Size of av01 = 78 bytes */ + if (size < (6 + 2 + 4 + 12 + 2 + 2 + 4 + 4 + 4 + 2 + 1 + 31 + 2 + 2)) + return FALSE; + + gst_byte_reader_skip_unchecked (data, 6); + GST_LOG_OBJECT (qtdemux, "%*s data reference:%d", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s version/rev.: %08x", depth, "", + GET_UINT32 (data)); + gst_byte_reader_skip_unchecked (data, 12); /* pre-defined & reserved */ + GST_LOG_OBJECT (qtdemux, "%*s width: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s height: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s horiz. resol: %g", depth, "", + GET_FP32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s vert. resol.: %g", depth, "", + GET_FP32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s data size: %u", depth, "", + GET_UINT32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s frame count: %u", depth, "", + GET_UINT16 (data)); + /* something is not right with this, it's supposed to be a string but it's + * not apparently, so just skip this for now */ + compressor_len = MAX (GET_UINT8 (data), 31); + memcpy (compressor_name, gst_byte_reader_get_data_unchecked (data, 31), 31); + compressor_name[compressor_len] = 0; + GST_LOG_OBJECT (qtdemux, "%*s compressor: %s", depth, "", + compressor_name); + GST_LOG_OBJECT (qtdemux, "%*s depth: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s color table ID:%u", depth, "", + GET_UINT16 (data)); + + return TRUE; +} + gboolean qtdemux_dump_stsd (GstQTDemux * qtdemux, GstByteReader * data, int depth) { @@ -353,6 +398,10 @@ qtdemux_dump_stsd (GstQTDemux * qtdemux, GstByteReader * data, int depth) if (!qtdemux_dump_unknown (qtdemux, &sub, depth + 1)) return FALSE; break; + case FOURCC_av01: + if (!qtdemux_dump_stsd_av01 (qtdemux, &sub, size, depth + 1)) + return FALSE; + break; default: /* Unknown stsd data, dump the bytes */ if (!qtdemux_dump_unknown (qtdemux, &sub, depth + 1)) @@ -983,6 +1032,93 @@ qtdemux_dump_gmin (GstQTDemux * qtdemux, GstByteReader * data, int depth) return TRUE; } +gboolean +qtdemux_dump_sbgp (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version; + guint32 grouping_type; + guint32 entry_count; + guint i; + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + if (!gst_byte_reader_get_uint32_le (data, &grouping_type)) + return FALSE; + + GST_LOG ("%*s grouping type: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (grouping_type)); + + if ((version >> 24) == 1) { + guint32 grouping_type_parm; + if (!gst_byte_reader_get_uint32_be (data, &grouping_type_parm)) + return FALSE; + GST_LOG ("%*s grouping type parameter: %" G_GUINT32_FORMAT, depth, "", + grouping_type_parm); + } + + if (!gst_byte_reader_get_uint32_be (data, &entry_count)) + return FALSE; + + GST_LOG ("%*s entry count: %" G_GUINT32_FORMAT, depth, "", entry_count); + + for (i = 0; i < entry_count; i++) { + guint32 sample_count; + guint32 group_description_idx; + if (!gst_byte_reader_get_uint32_be (data, &sample_count)) + return FALSE; + if (!gst_byte_reader_get_uint32_be (data, &group_description_idx)) + return FALSE; + GST_LOG ("%*s sample_count: %" G_GUINT32_FORMAT, depth, "", + sample_count); + GST_LOG ("%*s group_description_idx: %d", depth, "", + group_description_idx); + } + + return TRUE; +} + +gboolean +qtdemux_dump_sgpd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version; + guint32 grouping_type; + guint32 entry_count; + guint32 default_length_or_sample; + + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + if (!gst_byte_reader_get_uint32_le (data, &grouping_type)) + return FALSE; + + GST_LOG ("%*s grouping type: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (grouping_type)); + + if (!gst_byte_reader_get_uint32_be (data, &default_length_or_sample)) + return FALSE; + + if ((version >> 24) == 1) { + GST_LOG ("%*s default length: %" G_GUINT32_FORMAT, depth, "", + default_length_or_sample); + } else if ((version >> 24) >= 2) { + GST_LOG ("%*s default sample description index: %" G_GUINT32_FORMAT, depth, + "", default_length_or_sample); + } + + if (!gst_byte_reader_get_uint32_be (data, &entry_count)) + return FALSE; + + GST_LOG ("%*s entry count: %" G_GUINT32_FORMAT, depth, "", entry_count); + + return TRUE; +} + gboolean qtdemux_dump_unknown (GstQTDemux * qtdemux, GstByteReader * data, int depth) { diff --git a/gst/isomp4/qtdemux_dump.h b/gst/isomp4/qtdemux_dump.h index 45dcd3f081..db7d1b05e3 100644 --- a/gst/isomp4/qtdemux_dump.h +++ b/gst/isomp4/qtdemux_dump.h @@ -91,6 +91,10 @@ gboolean qtdemux_dump_fLaC (GstQTDemux * qtdemux, GstByteReader * data, int depth); gboolean qtdemux_dump_gmin (GstQTDemux * qtdemux, GstByteReader * data, int depth); +gboolean qtdemux_dump_sbgp (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_sgpd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); gboolean qtdemux_node_dump (GstQTDemux * qtdemux, GNode * node); diff --git a/gst/isomp4/qtdemux_types.c b/gst/isomp4/qtdemux_types.c index 88db8c2ceb..b002f5857c 100644 --- a/gst/isomp4/qtdemux_types.c +++ b/gst/isomp4/qtdemux_types.c @@ -212,7 +212,16 @@ static const QtNodeType qt_node_types[] = { {FOURCC_schi, "scheme information", QT_FLAG_CONTAINER}, {FOURCC_pssh, "protection system specific header", 0}, {FOURCC_tenc, "track encryption", 0}, - {FOURCC_stpp, "XML subtitle sample entry", 0}, + {FOURCC_senc, "sample encryption", 0}, + {FOURCC_sbgp, "Sample to Group", 0, qtdemux_dump_sbgp}, + {FOURCC_sgpd, "Sample Group Description", 0, qtdemux_dump_sgpd}, + {FOURCC_ec_3, "ec-3 sample entry", 0, qtdemux_dump_unknown}, + {FOURCC_av01, "AV1 Sample Entry", 0}, + {FOURCC_av1C, "AV1 Codec Configuration", 0}, + {FOURCC_av1f, "AV1 Forward Key Frame sample group entry", 0}, + {FOURCC_av1m, "AV1 Multi-Frame sample group entry", 0}, + {FOURCC_av1s, "AV1 S-Frame sample group entry", 0}, + {FOURCC_av1M, "AV1 Metadata sample group entry", 0}, {0, "unknown", 0,}, }; diff --git a/gst/isomp4_1_8/LEGAL b/gst/isomp4_1_8/LEGAL new file mode 100644 index 0000000000..5af6e8f92e --- /dev/null +++ b/gst/isomp4_1_8/LEGAL @@ -0,0 +1,10 @@ +This is a demuxer supporting a subset of the Quicktime video container +format developed by Apple. Apple and others have some patents on +some features of the Quicktime container format in regards to technologies +such as QuicktimeVR and RTP hinting. Due to that be aware that if ever +such features are added to this demuxer it would need to be moved to the +-ugly module or those features need to come as add-in functionality stored in +another module. + +As the plugin is as of today's date (19th of June 2007) it does not +violate any software patents we know of. diff --git a/gst/isomp4_1_8/Makefile.am b/gst/isomp4_1_8/Makefile.am new file mode 100644 index 0000000000..97a70fc8b3 --- /dev/null +++ b/gst/isomp4_1_8/Makefile.am @@ -0,0 +1,32 @@ + +plugin_LTLIBRARIES = libgstisomp4_1_8.la + +libgstisomp4_1_8_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + -DGstQTDemux=GstQTDemux_1_8 -DGstQTDemuxClass=GstQTDemux_1_8Class +libgstisomp4_1_8_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstriff-@GST_API_VERSION@ \ + -lgstaudio-@GST_API_VERSION@ \ + -lgstvideo-@GST_API_VERSION@ \ + -lgstsubtitle-@GST_API_VERSION@ \ + -lgstrtp-@GST_API_VERSION@ \ + -lgsttag-@GST_API_VERSION@ \ + -lgstpbutils-@GST_API_VERSION@ \ + $(GST_BASE_LIBS) $(GST_LIBS) $(ZLIB_LIBS) +libgstisomp4_1_8_la_LDFLAGS = ${GST_PLUGIN_LDFLAGS} +libgstisomp4_1_8_la_SOURCES = isomp4-plugin.c \ + qtdemux.c qtdemux_types.c qtdemux_dump.c qtdemux_lang.c \ + descriptors.c properties.c gstisoff.c +libgstisomp4_1_8_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = \ + qtatomparser.h \ + qtdemux.h \ + qtdemux_types.h \ + qtdemux_dump.h \ + qtdemux_lang.h \ + qtpalette.h \ + descriptors.h \ + properties.h \ + fourcc.h \ + gstisoff.h diff --git a/gst/isomp4_1_8/descriptors.c b/gst/isomp4_1_8/descriptors.c new file mode 100644 index 0000000000..713ffdcf5f --- /dev/null +++ b/gst/isomp4_1_8/descriptors.c @@ -0,0 +1,457 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "descriptors.h" + +/* + * Some mp4 structures (descriptors) use a coding scheme for + * representing its size. + * It is grouped in bytes. The 1st bit set to 1 means we need another byte, + * 0 otherwise. The remaining 7 bits are the useful values. + * + * The next set of functions handle those values + */ + +/* + * Gets an unsigned integer and packs it into a 'expandable size' format + * (as used by mp4 descriptors) + * @size: the integer to be parsed + * @ptr: the array to place the result + * @array_size: the size of ptr array + */ +static void +expandable_size_parse (guint64 size, guint8 * ptr, guint32 array_size) +{ + int index = 0; + + memset (ptr, 0, sizeof (array_size)); + while (size > 0 && index < array_size) { + ptr[index++] = (size > 0x7F ? 0x80 : 0x0) | (size & 0x7F); + size = size >> 7; + } +} + +/* + * Gets how many positions in an array holding an 'expandable size' + * are really used + * + * @ptr: the array with the 'expandable size' + * @array_size: the size of ptr array + * + * Returns: the number of really used positions + */ +static guint64 +expandable_size_get_length (guint8 * ptr, guint32 array_size) +{ + gboolean next = TRUE; + guint32 index = 0; + + while (next && index < array_size) { + next = (ptr[index] & 0x80); + index++; + } + return index; +} + +/* + * Initializers below + */ + +static void +desc_base_descriptor_init (BaseDescriptor * bd, guint8 tag, guint32 size) +{ + bd->tag = tag; + expandable_size_parse (size, bd->size, 4); +} + +static void +desc_dec_specific_info_init (DecoderSpecificInfoDescriptor * dsid) +{ + desc_base_descriptor_init (&dsid->base, DECODER_SPECIFIC_INFO_TAG, 0); + dsid->length = 0; + dsid->data = NULL; +} + +DecoderSpecificInfoDescriptor * +desc_dec_specific_info_new (void) +{ + DecoderSpecificInfoDescriptor *desc = + g_new0 (DecoderSpecificInfoDescriptor, 1); + desc_dec_specific_info_init (desc); + return desc; +} + +static void +desc_dec_conf_desc_init (DecoderConfigDescriptor * dcd) +{ + desc_base_descriptor_init (&dcd->base, DECODER_CONFIG_DESC_TAG, 0); + dcd->dec_specific_info = NULL; +} + +static void +desc_sl_conf_desc_init (SLConfigDescriptor * sl) +{ + desc_base_descriptor_init (&sl->base, SL_CONFIG_DESC_TAG, 0); + sl->predefined = 0x2; +} + +void +desc_es_init (ESDescriptor * es) +{ + desc_base_descriptor_init (&es->base, ES_DESCRIPTOR_TAG, 0); + + es->id = 0; + es->flags = 0; + es->depends_on_es_id = 0; + es->ocr_es_id = 0; + es->url_length = 0; + es->url_string = NULL; + + desc_dec_conf_desc_init (&es->dec_conf_desc); + desc_sl_conf_desc_init (&es->sl_conf_desc); +} + +ESDescriptor * +desc_es_descriptor_new (void) +{ + ESDescriptor *es = g_new0 (ESDescriptor, 1); + + desc_es_init (es); + return es; +} + +/* + * Deinitializers/Destructors below + */ + +static void +desc_base_descriptor_clear (BaseDescriptor * base) +{ +} + +void +desc_dec_specific_info_free (DecoderSpecificInfoDescriptor * dsid) +{ + desc_base_descriptor_clear (&dsid->base); + if (dsid->data) { + g_free (dsid->data); + dsid->data = NULL; + } + g_free (dsid); +} + +static void +desc_dec_conf_desc_clear (DecoderConfigDescriptor * dec) +{ + desc_base_descriptor_clear (&dec->base); + if (dec->dec_specific_info) { + desc_dec_specific_info_free (dec->dec_specific_info); + } +} + +static void +desc_sl_config_descriptor_clear (SLConfigDescriptor * sl) +{ + desc_base_descriptor_clear (&sl->base); +} + +void +desc_es_descriptor_clear (ESDescriptor * es) +{ + desc_base_descriptor_clear (&es->base); + if (es->url_string) { + g_free (es->url_string); + es->url_string = NULL; + } + desc_dec_conf_desc_clear (&es->dec_conf_desc); + desc_sl_config_descriptor_clear (&es->sl_conf_desc); +} + +/* + * Size handling functions below + */ + +void +desc_dec_specific_info_alloc_data (DecoderSpecificInfoDescriptor * dsid, + guint32 size) +{ + if (dsid->data) { + g_free (dsid->data); + } + dsid->data = g_new0 (guint8, size); + dsid->length = size; +} + +static void +desc_base_descriptor_set_size (BaseDescriptor * bd, guint32 size) +{ + expandable_size_parse (size, bd->size, 4); +} + +static guint64 +desc_base_descriptor_get_size (BaseDescriptor * bd) +{ + guint64 size = 0; + + size += sizeof (guint8); + size += expandable_size_get_length (bd->size, 4) * sizeof (guint8); + return size; +} + +static guint64 +desc_sl_config_descriptor_get_size (SLConfigDescriptor * sl_desc) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&sl_desc->base); + /* predefined */ + extra_size += sizeof (guint8); + + desc_base_descriptor_set_size (&sl_desc->base, extra_size); + + return size + extra_size; +} + +static guint64 +desc_dec_specific_info_get_size (DecoderSpecificInfoDescriptor * dsid) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&dsid->base); + extra_size += sizeof (guint8) * dsid->length; + desc_base_descriptor_set_size (&dsid->base, extra_size); + return size + extra_size; +} + +static guint64 +desc_dec_config_descriptor_get_size (DecoderConfigDescriptor * dec_desc) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&dec_desc->base); + /* object type */ + extra_size += sizeof (guint8); + /* stream type */ + extra_size += sizeof (guint8); + /* buffer size */ + extra_size += sizeof (guint8) * 3; + /* max bitrate */ + extra_size += sizeof (guint32); + /* avg bitrate */ + extra_size += sizeof (guint32); + if (dec_desc->dec_specific_info) { + extra_size += desc_dec_specific_info_get_size (dec_desc->dec_specific_info); + } + + desc_base_descriptor_set_size (&dec_desc->base, extra_size); + return size + extra_size; +} + +static guint64 +desc_es_descriptor_get_size (ESDescriptor * es) +{ + guint64 size = 0; + guint64 extra_size = 0; + + size += desc_base_descriptor_get_size (&es->base); + /* id */ + extra_size += sizeof (guint16); + /* flags */ + extra_size += sizeof (guint8); + /* depends_on_es_id */ + if (es->flags & 0x80) { + extra_size += sizeof (guint16); + } + if (es->flags & 0x40) { + /* url_length */ + extra_size += sizeof (guint8); + /* url */ + extra_size += sizeof (gchar) * es->url_length; + } + if (es->flags & 0x20) { + /* ocr_es_id */ + extra_size += sizeof (guint16); + } + + extra_size += desc_dec_config_descriptor_get_size (&es->dec_conf_desc); + extra_size += desc_sl_config_descriptor_get_size (&es->sl_conf_desc); + + desc_base_descriptor_set_size (&es->base, extra_size); + + return size + extra_size; +} + +static gboolean +desc_es_descriptor_check_stream_dependency (ESDescriptor * es) +{ + return es->flags & 0x80; +} + +static gboolean +desc_es_descriptor_check_url_flag (ESDescriptor * es) +{ + return es->flags & 0x40; +} + +static gboolean +desc_es_descriptor_check_ocr (ESDescriptor * es) +{ + return es->flags & 0x20; +} + +/* Copy/Serializations Functions below */ + +static guint64 +desc_base_descriptor_copy_data (BaseDescriptor * desc, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + prop_copy_uint8 (desc->tag, buffer, size, offset); + prop_copy_uint8_array (desc->size, expandable_size_get_length (desc->size, 4), + buffer, size, offset); + return original_offset - *offset; +} + +static guint64 +desc_sl_config_descriptor_copy_data (SLConfigDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + /* predefined attribute */ + prop_copy_uint8 (desc->predefined, buffer, size, offset); + + return *offset - original_offset; +} + +static guint64 +desc_dec_specific_info_copy_data (DecoderSpecificInfoDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + prop_copy_uint8_array (desc->data, desc->length, buffer, size, offset); + + return *offset - original_offset; +} + +static guint64 +desc_dec_config_descriptor_copy_data (DecoderConfigDescriptor * desc, + guint8 ** buffer, guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + + prop_copy_uint8 (desc->object_type, buffer, size, offset); + + prop_copy_uint8 (desc->stream_type, buffer, size, offset); + prop_copy_uint8_array (desc->buffer_size_DB, 3, buffer, size, offset); + + prop_copy_uint32 (desc->max_bitrate, buffer, size, offset); + prop_copy_uint32 (desc->avg_bitrate, buffer, size, offset); + + if (desc->dec_specific_info) { + if (!desc_dec_specific_info_copy_data (desc->dec_specific_info, buffer, + size, offset)) { + return 0; + } + } + + return *offset - original_offset; +} + +guint64 +desc_es_descriptor_copy_data (ESDescriptor * desc, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + /* must call this twice to have size fields of all contained descriptors set + * correctly, and to have the size of the size fields taken into account */ + desc_es_descriptor_get_size (desc); + desc_es_descriptor_get_size (desc); + + if (!desc_base_descriptor_copy_data (&desc->base, buffer, size, offset)) { + return 0; + } + /* id and flags */ + prop_copy_uint16 (desc->id, buffer, size, offset); + prop_copy_uint8 (desc->flags, buffer, size, offset); + + if (desc_es_descriptor_check_stream_dependency (desc)) { + prop_copy_uint16 (desc->depends_on_es_id, buffer, size, offset); + } + + if (desc_es_descriptor_check_url_flag (desc)) { + prop_copy_size_string (desc->url_string, desc->url_length, buffer, size, + offset); + } + + if (desc_es_descriptor_check_ocr (desc)) { + prop_copy_uint16 (desc->ocr_es_id, buffer, size, offset); + } + + if (!desc_dec_config_descriptor_copy_data (&desc->dec_conf_desc, buffer, size, + offset)) { + return 0; + } + + if (!desc_sl_config_descriptor_copy_data (&desc->sl_conf_desc, buffer, size, + offset)) { + return 0; + } + + return *offset - original_offset; +} diff --git a/gst/isomp4_1_8/descriptors.h b/gst/isomp4_1_8/descriptors.h new file mode 100644 index 0000000000..b472523196 --- /dev/null +++ b/gst/isomp4_1_8/descriptors.h @@ -0,0 +1,151 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __DESCRIPTORS_H__ +#define __DESCRIPTORS_H__ + +#include +#include +#include "properties.h" + +/* + * Tags for descriptor (each kind is represented by a number, instead of fourcc as in atoms) + */ +#define OBJECT_DESC_TAG 0x01 +#define INIT_OBJECT_DESC_TAG 0x02 +#define ES_DESCRIPTOR_TAG 0x03 +#define DECODER_CONFIG_DESC_TAG 0x04 +#define DECODER_SPECIFIC_INFO_TAG 0x05 +#define SL_CONFIG_DESC_TAG 0x06 +#define ES_ID_INC_TAG 0x0E +#define MP4_INIT_OBJECT_DESC_TAG 0x10 + +#define ESDS_OBJECT_TYPE_MPEG1_P3 0x6B +#define ESDS_OBJECT_TYPE_MPEG2_P7_MAIN 0x66 +#define ESDS_OBJECT_TYPE_MPEG4_P7_LC 0x67 +#define ESDS_OBJECT_TYPE_MPEG4_P7_SSR 0x68 +#define ESDS_OBJECT_TYPE_MPEG4_P2 0x20 +#define ESDS_OBJECT_TYPE_MPEG4_P3 0x40 + +#define ESDS_STREAM_TYPE_VISUAL 0x04 +#define ESDS_STREAM_TYPE_AUDIO 0x05 + + +typedef struct _BaseDescriptor +{ + guint8 tag; + /* the first bit of each byte indicates if the next byte should be used */ + guint8 size[4]; +} BaseDescriptor; + +typedef struct _SLConfigDescriptor +{ + BaseDescriptor base; + + guint8 predefined; /* everything is supposed predefined */ +} SLConfigDescriptor; + +typedef struct _DecoderSpecificInfoDescriptor +{ + BaseDescriptor base; + guint32 length; + guint8 *data; +} DecoderSpecificInfoDescriptor; + +typedef struct _DecoderConfigDescriptor { + BaseDescriptor base; + + guint8 object_type; + + /* following are condensed into streamType: + * bit(6) streamType; + * bit(1) upStream; + * const bit(1) reserved=1; + */ + guint8 stream_type; + + guint8 buffer_size_DB[3]; + guint32 max_bitrate; + guint32 avg_bitrate; + + DecoderSpecificInfoDescriptor *dec_specific_info; +} DecoderConfigDescriptor; + +typedef struct _ESDescriptor +{ + BaseDescriptor base; + + guint16 id; + + /* flags contains the following: + * bit(1) streamDependenceFlag; + * bit(1) URL_Flag; + * bit(1) OCRstreamFlag; + * bit(5) streamPriority; + */ + guint8 flags; + + guint16 depends_on_es_id; + guint8 url_length; /* only if URL_flag is set */ + guint8 *url_string; /* size is url_length */ + + guint16 ocr_es_id; /* only if OCRstreamFlag is set */ + + DecoderConfigDescriptor dec_conf_desc; + SLConfigDescriptor sl_conf_desc; + + /* optional remainder of ESDescriptor is not used */ +} ESDescriptor; + +/* --- FUNCTIONS --- */ +void desc_es_init (ESDescriptor *es); +ESDescriptor *desc_es_descriptor_new (void); +guint64 desc_es_descriptor_copy_data (ESDescriptor *es, guint8 **buffer, + guint64 *size, guint64 *offset); +void desc_es_descriptor_clear (ESDescriptor *es); + +DecoderSpecificInfoDescriptor *desc_dec_specific_info_new(void); +void desc_dec_specific_info_free (DecoderSpecificInfoDescriptor *dsid); +void desc_dec_specific_info_alloc_data (DecoderSpecificInfoDescriptor *dsid, + guint32 size); + +#endif /* __DESCRIPTORS_H__ */ diff --git a/gst/isomp4_1_8/fourcc.h b/gst/isomp4_1_8/fourcc.h new file mode 100644 index 0000000000..80d91a9749 --- /dev/null +++ b/gst/isomp4_1_8/fourcc.h @@ -0,0 +1,396 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + /* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef __FOURCC_H__ +#define __FOURCC_H__ + +#include + +G_BEGIN_DECLS + +#define FOURCC_null 0x0 + +#define FOURCC_2vuy GST_MAKE_FOURCC('2','v','u','y') +#define FOURCC_FMP4 GST_MAKE_FOURCC('F','M','P','4') +#define FOURCC_H264 GST_MAKE_FOURCC('H','2','6','4') +#define FOURCC_H265 GST_MAKE_FOURCC('H','2','6','5') +#define FOURCC_MAC3 GST_MAKE_FOURCC('M','A','C','3') +#define FOURCC_MAC6 GST_MAKE_FOURCC('M','A','C','6') +#define FOURCC_MP4V GST_MAKE_FOURCC('M','P','4','V') +#define FOURCC_PICT GST_MAKE_FOURCC('P','I','C','T') +#define FOURCC_QDM2 GST_MAKE_FOURCC('Q','D','M','2') +#define FOURCC_SVQ1 GST_MAKE_FOURCC('S','V','Q','1') +#define FOURCC_SVQ3 GST_MAKE_FOURCC('S','V','Q','3') +#define FOURCC_VP31 GST_MAKE_FOURCC('V','P','3','1') +#define FOURCC_VP80 GST_MAKE_FOURCC('V','P','8','0') +#define FOURCC_WRLE GST_MAKE_FOURCC('W','R','L','E') +#define FOURCC_XMP_ GST_MAKE_FOURCC('X','M','P','_') +#define FOURCC__ART GST_MAKE_FOURCC(0xa9,'A','R','T') +#define FOURCC_____ GST_MAKE_FOURCC('-','-','-','-') +#define FOURCC___in GST_MAKE_FOURCC(' ',' ','i','n') +#define FOURCC___ty GST_MAKE_FOURCC(' ',' ','t','y') +#define FOURCC__alb GST_MAKE_FOURCC(0xa9,'a','l','b') +#define FOURCC__cpy GST_MAKE_FOURCC(0xa9,'c','p','y') +#define FOURCC__day GST_MAKE_FOURCC(0xa9,'d','a','y') +#define FOURCC__des GST_MAKE_FOURCC(0xa9,'d','e','s') +#define FOURCC__enc GST_MAKE_FOURCC(0xa9,'e','n','c') +#define FOURCC__gen GST_MAKE_FOURCC(0xa9, 'g', 'e', 'n') +#define FOURCC__grp GST_MAKE_FOURCC(0xa9,'g','r','p') +#define FOURCC__inf GST_MAKE_FOURCC(0xa9,'i','n','f') +#define FOURCC__lyr GST_MAKE_FOURCC(0xa9,'l','y','r') +#define FOURCC__mp2 GST_MAKE_FOURCC('.','m','p','2') +#define FOURCC__mp3 GST_MAKE_FOURCC('.','m','p','3') +#define FOURCC__nam GST_MAKE_FOURCC(0xa9,'n','a','m') +#define FOURCC__req GST_MAKE_FOURCC(0xa9,'r','e','q') +#define FOURCC__too GST_MAKE_FOURCC(0xa9,'t','o','o') +#define FOURCC__wrt GST_MAKE_FOURCC(0xa9,'w','r','t') +#define FOURCC_aART GST_MAKE_FOURCC('a','A','R','T') +#define FOURCC_ac_3 GST_MAKE_FOURCC('a','c','-','3') +#define FOURCC_ac_4 GST_MAKE_FOURCC('a','c','-','4') +#define FOURCC_agsm GST_MAKE_FOURCC('a','g','s','m') +#define FOURCC_alac GST_MAKE_FOURCC('a','l','a','c') +#define FOURCC_alaw GST_MAKE_FOURCC('a','l','a','w') +#define FOURCC_alis GST_MAKE_FOURCC('a','l','i','s') +#define FOURCC_appl GST_MAKE_FOURCC('a','p','p','l') +#define FOURCC_avc1 GST_MAKE_FOURCC('a','v','c','1') +#define FOURCC_avc3 GST_MAKE_FOURCC('a','v','c','3') +#define FOURCC_avcC GST_MAKE_FOURCC('a','v','c','C') +#define FOURCC_clip GST_MAKE_FOURCC('c','l','i','p') +#define FOURCC_cmov GST_MAKE_FOURCC('c','m','o','v') +#define FOURCC_cmvd GST_MAKE_FOURCC('c','m','v','d') +#define FOURCC_co64 GST_MAKE_FOURCC('c','o','6','4') +#define FOURCC_covr GST_MAKE_FOURCC('c','o','v','r') +#define FOURCC_cpil GST_MAKE_FOURCC('c','p','i','l') +#define FOURCC_cprt GST_MAKE_FOURCC('c','p','r','t') +#define FOURCC_crgn GST_MAKE_FOURCC('c','r','g','n') +#define FOURCC_ctab GST_MAKE_FOURCC('c','t','a','b') +#define FOURCC_ctts GST_MAKE_FOURCC('c','t','t','s') +#define FOURCC_cslg GST_MAKE_FOURCC('c','s','l','g') +#define FOURCC_d263 GST_MAKE_FOURCC('d','2','6','3') +#define FOURCC_dac3 GST_MAKE_FOURCC('d','a','c','3') +#define FOURCC_damr GST_MAKE_FOURCC('d','a','m','r') +#define FOURCC_data GST_MAKE_FOURCC('d','a','t','a') +#define FOURCC_dcom GST_MAKE_FOURCC('d','c','o','m') +#define FOURCC_desc GST_MAKE_FOURCC('d','e','s','c') +#define FOURCC_dhlr GST_MAKE_FOURCC('d','h','l','r') +#define FOURCC_dinf GST_MAKE_FOURCC('d','i','n','f') +#define FOURCC_disc GST_MAKE_FOURCC('d','i','s','c') +#define FOURCC_disk GST_MAKE_FOURCC('d','i','s','k') +#define FOURCC_drac GST_MAKE_FOURCC('d','r','a','c') +#define FOURCC_dref GST_MAKE_FOURCC('d','r','e','f') +#define FOURCC_drmi GST_MAKE_FOURCC('d','r','m','i') +#define FOURCC_drms GST_MAKE_FOURCC('d','r','m','s') +#define FOURCC_dvcp GST_MAKE_FOURCC('d','v','c','p') +#define FOURCC_dvc_ GST_MAKE_FOURCC('d','v','c',' ') +#define FOURCC_dv5p GST_MAKE_FOURCC('d','v','5','p') +#define FOURCC_dv5n GST_MAKE_FOURCC('d','v','5','n') +#define FOURCC_ec_3 GST_MAKE_FOURCC('e','c','-','3') +#define FOURCC_edts GST_MAKE_FOURCC('e','d','t','s') +#define FOURCC_elst GST_MAKE_FOURCC('e','l','s','t') +#define FOURCC_enda GST_MAKE_FOURCC('e','n','d','a') +#define FOURCC_esds GST_MAKE_FOURCC('e','s','d','s') +#define FOURCC_fmp4 GST_MAKE_FOURCC('f','m','p','4') +#define FOURCC_free GST_MAKE_FOURCC('f','r','e','e') +#define FOURCC_frma GST_MAKE_FOURCC('f','r','m','a') +#define FOURCC_ftyp GST_MAKE_FOURCC('f','t','y','p') +#define FOURCC_ftab GST_MAKE_FOURCC('f','t','a','b') +#define FOURCC_gama GST_MAKE_FOURCC('g','a','m','a') +#define FOURCC_glbl GST_MAKE_FOURCC('g','l','b','l') +#define FOURCC_gmhd GST_MAKE_FOURCC('g','m','h','d') +#define FOURCC_gmin GST_MAKE_FOURCC('g','m','i','n') +#define FOURCC_gnre GST_MAKE_FOURCC('g','n','r','e') +#define FOURCC_h263 GST_MAKE_FOURCC('h','2','6','3') +#define FOURCC_hdlr GST_MAKE_FOURCC('h','d','l','r') +#define FOURCC_hev1 GST_MAKE_FOURCC('h','e','v','1') +#define FOURCC_hint GST_MAKE_FOURCC('h','i','n','t') +#define FOURCC_hmhd GST_MAKE_FOURCC('h','m','h','d') +#define FOURCC_hndl GST_MAKE_FOURCC('h','n','d','l') +#define FOURCC_hnti GST_MAKE_FOURCC('h','n','t','i') +#define FOURCC_hvc1 GST_MAKE_FOURCC('h','v','c','1') +#define FOURCC_hvcC GST_MAKE_FOURCC('h','v','c','C') +#define FOURCC_ilst GST_MAKE_FOURCC('i','l','s','t') +#define FOURCC_ima4 GST_MAKE_FOURCC('i','m','a','4') +#define FOURCC_imap GST_MAKE_FOURCC('i','m','a','p') +#define FOURCC_in24 GST_MAKE_FOURCC('i','n','2','4') +#define FOURCC_jp2c GST_MAKE_FOURCC('j','p','2','c') +#define FOURCC_jpeg GST_MAKE_FOURCC('j','p','e','g') +#define FOURCC_keyw GST_MAKE_FOURCC('k','e','y','w') +#define FOURCC_kmat GST_MAKE_FOURCC('k','m','a','t') +#define FOURCC_kywd GST_MAKE_FOURCC('k','y','w','d') +#define FOURCC_load GST_MAKE_FOURCC('l','o','a','d') +#define FOURCC_matt GST_MAKE_FOURCC('m','a','t','t') +#define FOURCC_mdat GST_MAKE_FOURCC('m','d','a','t') +#define FOURCC_mdhd GST_MAKE_FOURCC('m','d','h','d') +#define FOURCC_mdia GST_MAKE_FOURCC('m','d','i','a') +#define FOURCC_mdir GST_MAKE_FOURCC('m','d','i','r') +#define FOURCC_mean GST_MAKE_FOURCC('m','e','a','n') +#define FOURCC_meta GST_MAKE_FOURCC('m','e','t','a') +#define FOURCC_mhlr GST_MAKE_FOURCC('m','h','l','r') +#define FOURCC_minf GST_MAKE_FOURCC('m','i','n','f') +#define FOURCC_moov GST_MAKE_FOURCC('m','o','o','v') +#define FOURCC_mp4a GST_MAKE_FOURCC('m','p','4','a') +#define FOURCC_mp4s GST_MAKE_FOURCC('m','p','4','s') +#define FOURCC_mp4v GST_MAKE_FOURCC('m','p','4','v') +#define FOURCC_name GST_MAKE_FOURCC('n','a','m','e') +#define FOURCC_opus GST_MAKE_FOURCC('O','p','u','s') +#define FOURCC_dops GST_MAKE_FOURCC('d','O','p','s') +#define FOURCC_pasp GST_MAKE_FOURCC('p','a','s','p') +#define FOURCC_pcst GST_MAKE_FOURCC('p','c','s','t') +#define FOURCC_pgap GST_MAKE_FOURCC('p','g','a','p') +#define FOURCC_pnot GST_MAKE_FOURCC('p','n','o','t') +#define FOURCC_qt__ GST_MAKE_FOURCC('q','t',' ',' ') +#define FOURCC_qtim GST_MAKE_FOURCC('q','t','i','m') +#define FOURCC_raw_ GST_MAKE_FOURCC('r','a','w',' ') +#define FOURCC_rdrf GST_MAKE_FOURCC('r','d','r','f') +#define FOURCC_rle_ GST_MAKE_FOURCC('r','l','e',' ') +#define FOURCC_rmda GST_MAKE_FOURCC('r','m','d','a') +#define FOURCC_rmdr GST_MAKE_FOURCC('r','m','d','r') +#define FOURCC_rmra GST_MAKE_FOURCC('r','m','r','a') +#define FOURCC_rmvc GST_MAKE_FOURCC('r','m','v','c') +#define FOURCC_rtp_ GST_MAKE_FOURCC('r','t','p',' ') +#define FOURCC_rtsp GST_MAKE_FOURCC('r','t','s','p') +#define FOURCC_s263 GST_MAKE_FOURCC('s','2','6','3') +#define FOURCC_samr GST_MAKE_FOURCC('s','a','m','r') +#define FOURCC_sawb GST_MAKE_FOURCC('s','a','w','b') +#define FOURCC_sbtl GST_MAKE_FOURCC('s','b','t','l') +#define FOURCC_sdp_ GST_MAKE_FOURCC('s','d','p',' ') +#define FOURCC_sidx GST_MAKE_FOURCC('s','i','d','x') +#define FOURCC_smhd GST_MAKE_FOURCC('s','m','h','d') +#define FOURCC_soaa GST_MAKE_FOURCC('s','o','a','a') +#define FOURCC_soal GST_MAKE_FOURCC('s','o','a','l') +#define FOURCC_soar GST_MAKE_FOURCC('s','o','a','r') +#define FOURCC_soco GST_MAKE_FOURCC('s','o','c','o') +#define FOURCC_sonm GST_MAKE_FOURCC('s','o','n','m') +#define FOURCC_sosn GST_MAKE_FOURCC('s','o','s','n') +#define FOURCC_soun GST_MAKE_FOURCC('s','o','u','n') +#define FOURCC_sowt GST_MAKE_FOURCC('s','o','w','t') +#define FOURCC_stbl GST_MAKE_FOURCC('s','t','b','l') +#define FOURCC_stco GST_MAKE_FOURCC('s','t','c','o') +#define FOURCC_stps GST_MAKE_FOURCC('s','t','p','s') +#define FOURCC_strf GST_MAKE_FOURCC('s','t','r','f') +#define FOURCC_strm GST_MAKE_FOURCC('s','t','r','m') +#define FOURCC_stsc GST_MAKE_FOURCC('s','t','s','c') +#define FOURCC_stsd GST_MAKE_FOURCC('s','t','s','d') +#define FOURCC_stss GST_MAKE_FOURCC('s','t','s','s') +#define FOURCC_stsz GST_MAKE_FOURCC('s','t','s','z') +#define FOURCC_stts GST_MAKE_FOURCC('s','t','t','s') +#define FOURCC_subp GST_MAKE_FOURCC('s','u','b','p') +#define FOURCC_subt GST_MAKE_FOURCC('s','u','b','t') +#define FOURCC_text GST_MAKE_FOURCC('t','e','x','t') +#define FOURCC_tkhd GST_MAKE_FOURCC('t','k','h','d') +#define FOURCC_tmpo GST_MAKE_FOURCC('t','m','p','o') +#define FOURCC_trak GST_MAKE_FOURCC('t','r','a','k') +#define FOURCC_tref GST_MAKE_FOURCC('t','r','e','f') +#define FOURCC_trkn GST_MAKE_FOURCC('t','r','k','n') +#define FOURCC_tven GST_MAKE_FOURCC('t','v','e','n') +#define FOURCC_tves GST_MAKE_FOURCC('t','v','e','s') +#define FOURCC_tvsh GST_MAKE_FOURCC('t','v','s','h') +#define FOURCC_tvsn GST_MAKE_FOURCC('t','v','s','n') +#define FOURCC_twos GST_MAKE_FOURCC('t','w','o','s') +#define FOURCC_tx3g GST_MAKE_FOURCC('t','x','3','g') +#define FOURCC_udta GST_MAKE_FOURCC('u','d','t','a') +#define FOURCC_ulaw GST_MAKE_FOURCC('u','l','a','w') +#define FOURCC_url_ GST_MAKE_FOURCC('u','r','l',' ') +#define FOURCC_uuid GST_MAKE_FOURCC('u','u','i','d') +#define FOURCC_v210 GST_MAKE_FOURCC('v','2','1','0') +#define FOURCC_vc_1 GST_MAKE_FOURCC('v','c','-','1') +#define FOURCC_vide GST_MAKE_FOURCC('v','i','d','e') +#define FOURCC_vmhd GST_MAKE_FOURCC('v','m','h','d') +#define FOURCC_vp09 GST_MAKE_FOURCC('v','p','0','9') +#define FOURCC_wave GST_MAKE_FOURCC('w','a','v','e') +#define FOURCC_wide GST_MAKE_FOURCC('w','i','d','e') +#define FOURCC_zlib GST_MAKE_FOURCC('z','l','i','b') + +#define FOURCC_ap4h GST_MAKE_FOURCC('a','p','4','h') +#define FOURCC_apch GST_MAKE_FOURCC('a','p','c','h') +#define FOURCC_apcn GST_MAKE_FOURCC('a','p','c','n') +#define FOURCC_apco GST_MAKE_FOURCC('a','p','c','o') +#define FOURCC_apcs GST_MAKE_FOURCC('a','p','c','s') +#define FOURCC_m1v GST_MAKE_FOURCC('m','1','v',' ') +#define FOURCC_vivo GST_MAKE_FOURCC('v','i','v','o') +#define FOURCC_saiz GST_MAKE_FOURCC('s','a','i','z') +#define FOURCC_saio GST_MAKE_FOURCC('s','a','i','o') +#define FOURCC_rak3 GST_MAKE_FOURCC('r','a','k','3') + +#define FOURCC_3gg6 GST_MAKE_FOURCC('3','g','g','6') +#define FOURCC_3gg7 GST_MAKE_FOURCC('3','g','g','7') +#define FOURCC_3gp4 GST_MAKE_FOURCC('3','g','p','4') +#define FOURCC_3gp6 GST_MAKE_FOURCC('3','g','p','6') +#define FOURCC_3gr6 GST_MAKE_FOURCC('3','g','r','6') +#define FOURCC_3g__ GST_MAKE_FOURCC('3','g',0,0) +#define FOURCC_isml GST_MAKE_FOURCC('i','s','m','l') +#define FOURCC_iso2 GST_MAKE_FOURCC('i','s','o','2') +#define FOURCC_isom GST_MAKE_FOURCC('i','s','o','m') +#define FOURCC_mp41 GST_MAKE_FOURCC('m','p','4','1') +#define FOURCC_mp42 GST_MAKE_FOURCC('m','p','4','2') +#define FOURCC_piff GST_MAKE_FOURCC('p','i','f','f') +#define FOURCC_titl GST_MAKE_FOURCC('t','i','t','l') + +/* SVQ3 fourcc */ +#define FOURCC_SEQH GST_MAKE_FOURCC('S','E','Q','H') +#define FOURCC_SMI_ GST_MAKE_FOURCC('S','M','I',' ') + +/* 3gpp asset meta data fourcc */ +#define FOURCC_albm GST_MAKE_FOURCC('a','l','b','m') +#define FOURCC_auth GST_MAKE_FOURCC('a','u','t','h') +#define FOURCC_clsf GST_MAKE_FOURCC('c','l','s','f') +#define FOURCC_dscp GST_MAKE_FOURCC('d','s','c','p') +#define FOURCC_loci GST_MAKE_FOURCC('l','o','c','i') +#define FOURCC_perf GST_MAKE_FOURCC('p','e','r','f') +#define FOURCC_rtng GST_MAKE_FOURCC('r','t','n','g') +#define FOURCC_yrrc GST_MAKE_FOURCC('y','r','r','c') + +/* misc tag stuff */ +#define FOURCC_ID32 GST_MAKE_FOURCC('I', 'D','3','2') + +/* ISO Motion JPEG 2000 fourcc */ +#define FOURCC_cdef GST_MAKE_FOURCC('c','d','e','f') +#define FOURCC_cmap GST_MAKE_FOURCC('c','m','a','p') +#define FOURCC_colr GST_MAKE_FOURCC('c','o','l','r') +#define FOURCC_fiel GST_MAKE_FOURCC('f','i','e','l') +#define FOURCC_ihdr GST_MAKE_FOURCC('i','h','d','r') +#define FOURCC_jp2h GST_MAKE_FOURCC('j','p','2','h') +#define FOURCC_jp2x GST_MAKE_FOURCC('j','p','2','x') +#define FOURCC_mjp2 GST_MAKE_FOURCC('m','j','p','2') + +/* some buggy hardware's notion of mdhd */ +#define FOURCC_mhdr GST_MAKE_FOURCC('m','h','d','r') + +/* Fragmented MP4 */ +#define FOURCC_btrt GST_MAKE_FOURCC('b','t','r','t') +#define FOURCC_mehd GST_MAKE_FOURCC('m','e','h','d') +#define FOURCC_mfhd GST_MAKE_FOURCC('m','f','h','d') +#define FOURCC_mfra GST_MAKE_FOURCC('m','f','r','a') +#define FOURCC_mfro GST_MAKE_FOURCC('m','f','r','o') +#define FOURCC_moof GST_MAKE_FOURCC('m','o','o','f') +#define FOURCC_mvex GST_MAKE_FOURCC('m','v','e','x') +#define FOURCC_mvhd GST_MAKE_FOURCC('m','v','h','d') +#define FOURCC_ovc1 GST_MAKE_FOURCC('o','v','c','1') +#define FOURCC_owma GST_MAKE_FOURCC('o','w','m','a') +#define FOURCC_sdtp GST_MAKE_FOURCC('s','d','t','p') +#define FOURCC_tfhd GST_MAKE_FOURCC('t','f','h','d') +#define FOURCC_tfra GST_MAKE_FOURCC('t','f','r','a') +#define FOURCC_traf GST_MAKE_FOURCC('t','r','a','f') +#define FOURCC_trex GST_MAKE_FOURCC('t','r','e','x') +#define FOURCC_trun GST_MAKE_FOURCC('t','r','u','n') +#define FOURCC_wma_ GST_MAKE_FOURCC('w','m','a',' ') +#define FOURCC_sgpd GST_MAKE_FOURCC('s','g','p','d') +#define FOURCC_sbgp GST_MAKE_FOURCC('s','b','g','p') + +/* MPEG DASH */ +#define FOURCC_tfdt GST_MAKE_FOURCC('t','f','d','t') +#define FOURCC_styp GST_MAKE_FOURCC('s','t','y','p') +#define FOURCC_dash GST_MAKE_FOURCC('d','a','s','h') +#define FOURCC_msdh GST_MAKE_FOURCC('m','s','d','h') + +/* Xiph fourcc */ +#define FOURCC_XdxT GST_MAKE_FOURCC('X','d','x','T') +#define FOURCC_XiTh GST_MAKE_FOURCC('X','i','T','h') +#define FOURCC_tCtC GST_MAKE_FOURCC('t','C','t','C') +#define FOURCC_tCtH GST_MAKE_FOURCC('t','C','t','H') +#define FOURCC_tCt_ GST_MAKE_FOURCC('t','C','t','#') + +/* ilst metatags */ +#define FOURCC__cmt GST_MAKE_FOURCC(0xa9, 'c','m','t') + +/* apple tags */ +#define FOURCC__mak GST_MAKE_FOURCC(0xa9, 'm','a','k') +#define FOURCC__mod GST_MAKE_FOURCC(0xa9, 'm','o','d') +#define FOURCC__swr GST_MAKE_FOURCC(0xa9, 's','w','r') + +/* Chapters reference */ +#define FOURCC_chap GST_MAKE_FOURCC('c','h','a','p') + +/* Dolby HDR */ +#define FOURCC_dby1 GST_MAKE_FOURCC('d','b','y','1') +#define FOURCC_dvcC GST_MAKE_FOURCC('d','v','c','C') +#define FOURCC_dvav GST_MAKE_FOURCC('d','v','a','v') +#define FOURCC_dvhe GST_MAKE_FOURCC('d','v','h','e') + +/* For Microsoft Wave formats embedded in quicktime, the FOURCC is + 'm', 's', then the 16 bit wave codec id */ +#define MS_WAVE_FOURCC(codecid) GST_MAKE_FOURCC( \ + 'm', 's', ((codecid)>>8)&0xff, ((codecid)&0xff)) + +/* MPEG Application Format , Stereo Video */ +#define FOURCC_ss01 GST_MAKE_FOURCC('s','s','0','1') +#define FOURCC_ss02 GST_MAKE_FOURCC('s','s','0','2') +#define FOURCC_svmi GST_MAKE_FOURCC('s','v','m','i') +#define FOURCC_scdi GST_MAKE_FOURCC('s','c','d','i') + +/* Protected streams */ +#define FOURCC_encv GST_MAKE_FOURCC('e','n','c','v') +#define FOURCC_enca GST_MAKE_FOURCC('e','n','c','a') +#define FOURCC_enct GST_MAKE_FOURCC('e','n','c','t') +#define FOURCC_encs GST_MAKE_FOURCC('e','n','c','s') +#define FOURCC_sinf GST_MAKE_FOURCC('s','i','n','f') +#define FOURCC_frma GST_MAKE_FOURCC('f','r','m','a') +#define FOURCC_schm GST_MAKE_FOURCC('s','c','h','m') +#define FOURCC_schi GST_MAKE_FOURCC('s','c','h','i') + +/* Common Encryption */ +#define FOURCC_pssh GST_MAKE_FOURCC('p','s','s','h') +#define FOURCC_tenc GST_MAKE_FOURCC('t','e','n','c') +#define FOURCC_cenc GST_MAKE_FOURCC('c','e','n','c') +#define FOURCC_seig GST_MAKE_FOURCC('s','e','i','g') +#define FOURCC_cbc1 GST_MAKE_FOURCC('c','b','c','1') +#define FOURCC_cens GST_MAKE_FOURCC('c','e','n','s') +#define FOURCC_cbcs GST_MAKE_FOURCC('c','b','c','s') + +/* Common File Format */ +#define FOURCC_senc GST_MAKE_FOURCC('s','e','n','c') + +/* MPEG-H 3D Audio stream(MHAS) */ +#define FOURCC_mhm1 GST_MAKE_FOURCC('m','h','m','1') + +/* MPEG Media Transport */ +#define FOURCC_mpuf GST_MAKE_FOURCC('m','p','u','f') +#define FOURCC_mmpu GST_MAKE_FOURCC('m','m','p','u') +#define FOURCC_mmth GST_MAKE_FOURCC('m','m','t','h') +#define FOURCC_muli GST_MAKE_FOURCC('m','u','l','i') + +#define FOURCC_stpp GST_MAKE_FOURCC('s','t','p','p') + +G_END_DECLS + +#endif /* __FOURCC_H__ */ diff --git a/gst/isomp4_1_8/gstisoff.c b/gst/isomp4_1_8/gstisoff.c new file mode 100644 index 0000000000..5c9f1ed669 --- /dev/null +++ b/gst/isomp4_1_8/gstisoff.c @@ -0,0 +1,200 @@ +/* + * ISO File Format parsing library + * + * gstisoff.h + * + * Copyright (C) 2015 Samsung Electronics. All rights reserved. + * Author: Thiago Santos + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gstisoff.h" +#include + +void +gst_isoff_qt_sidx_parser_init (GstSidxParser * parser) +{ + parser->status = GST_ISOFF_QT_SIDX_PARSER_INIT; + parser->cumulative_entry_size = 0; + parser->sidx.entries = NULL; + parser->sidx.entries_count = 0; +} + +void +gst_isoff_qt_sidx_parser_clear (GstSidxParser * parser) +{ + g_free (parser->sidx.entries); + parser->sidx.entries = NULL; +} + +static void +gst_isoff_qt_parse_sidx_entry (GstSidxBoxEntry * entry, GstByteReader * reader) +{ + guint32 aux; + + aux = gst_byte_reader_get_uint32_be_unchecked (reader); + entry->ref_type = aux >> 31; + entry->size = aux & 0x7FFFFFFF; + entry->duration = gst_byte_reader_get_uint32_be_unchecked (reader); + aux = gst_byte_reader_get_uint32_be_unchecked (reader); + entry->starts_with_sap = aux >> 31; + entry->sap_type = ((aux >> 28) & 0x7); + entry->sap_delta_time = aux & 0xFFFFFFF; +} + +GstIsoffParserResult +gst_isoff_qt_sidx_parser_add_data (GstSidxParser * parser, + const guint8 * buffer, gint length, guint * consumed) +{ + GstIsoffParserResult res = GST_ISOFF_QT_PARSER_OK; + GstByteReader reader; + gsize remaining; + guint32 fourcc; + + gst_byte_reader_init (&reader, buffer, length); + + switch (parser->status) { + case GST_ISOFF_QT_SIDX_PARSER_INIT: + if (gst_byte_reader_get_remaining (&reader) < GST_ISOFF_QT_FULL_BOX_SIZE) { + break; + } + + parser->size = gst_byte_reader_get_uint32_be_unchecked (&reader); + fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader); + if (fourcc != GST_ISOFF_QT_FOURCC_SIDX) { + res = GST_ISOFF_QT_PARSER_UNEXPECTED; + gst_byte_reader_set_pos (&reader, 0); + break; + } + if (parser->size == 1) { + if (gst_byte_reader_get_remaining (&reader) < 12) { + gst_byte_reader_set_pos (&reader, 0); + break; + } + + parser->size = gst_byte_reader_get_uint64_be_unchecked (&reader); + } + if (parser->size == 0) { + res = GST_ISOFF_QT_PARSER_ERROR; + gst_byte_reader_set_pos (&reader, 0); + break; + } + parser->sidx.version = gst_byte_reader_get_uint8_unchecked (&reader); + parser->sidx.flags = gst_byte_reader_get_uint24_le_unchecked (&reader); + + parser->status = GST_ISOFF_QT_SIDX_PARSER_HEADER; + + case GST_ISOFF_QT_SIDX_PARSER_HEADER: + remaining = gst_byte_reader_get_remaining (&reader); + if (remaining < 12 + (parser->sidx.version == 0 ? 8 : 16)) { + break; + } + + parser->sidx.ref_id = gst_byte_reader_get_uint32_be_unchecked (&reader); + parser->sidx.timescale = + gst_byte_reader_get_uint32_be_unchecked (&reader); + if (parser->sidx.version == 0) { + parser->sidx.earliest_pts = + gst_byte_reader_get_uint32_be_unchecked (&reader); + parser->sidx.first_offset = parser->sidx.earliest_pts = + gst_byte_reader_get_uint32_be_unchecked (&reader); + } else { + parser->sidx.earliest_pts = + gst_byte_reader_get_uint64_be_unchecked (&reader); + parser->sidx.first_offset = + gst_byte_reader_get_uint64_be_unchecked (&reader); + } + /* skip 2 reserved bytes */ + gst_byte_reader_skip_unchecked (&reader, 2); + parser->sidx.entries_count = + gst_byte_reader_get_uint16_be_unchecked (&reader); + + GST_LOG ("Timescale: %" G_GUINT32_FORMAT, parser->sidx.timescale); + GST_LOG ("Earliest pts: %" G_GUINT64_FORMAT, parser->sidx.earliest_pts); + GST_LOG ("First offset: %" G_GUINT64_FORMAT, parser->sidx.first_offset); + + parser->cumulative_pts = + gst_util_uint64_scale_int_round (parser->sidx.earliest_pts, + GST_SECOND, parser->sidx.timescale); + + if (parser->sidx.entries_count) { + parser->sidx.entries = + g_malloc (sizeof (GstSidxBoxEntry) * parser->sidx.entries_count); + } + parser->sidx.entry_index = 0; + + parser->status = GST_ISOFF_QT_SIDX_PARSER_DATA; + + case GST_ISOFF_QT_SIDX_PARSER_DATA: + while (parser->sidx.entry_index < parser->sidx.entries_count) { + GstSidxBoxEntry *entry = + &parser->sidx.entries[parser->sidx.entry_index]; + + remaining = gst_byte_reader_get_remaining (&reader); + if (remaining < 12) + break; + + entry->offset = parser->cumulative_entry_size; + entry->pts = parser->cumulative_pts; + gst_isoff_qt_parse_sidx_entry (entry, &reader); + entry->duration = gst_util_uint64_scale_int_round (entry->duration, + GST_SECOND, parser->sidx.timescale); + parser->cumulative_entry_size += entry->size; + parser->cumulative_pts += entry->duration; + + GST_LOG ("Sidx entry %d) offset: %" G_GUINT64_FORMAT ", pts: %" + GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT " - size %" + G_GUINT32_FORMAT, parser->sidx.entry_index, entry->offset, + GST_TIME_ARGS (entry->pts), GST_TIME_ARGS (entry->duration), + entry->size); + + parser->sidx.entry_index++; + } + + if (parser->sidx.entry_index == parser->sidx.entries_count) + parser->status = GST_ISOFF_QT_SIDX_PARSER_FINISHED; + else + break; + case GST_ISOFF_QT_SIDX_PARSER_FINISHED: + parser->sidx.entry_index = 0; + res = GST_ISOFF_QT_PARSER_DONE; + break; + } + + *consumed = gst_byte_reader_get_pos (&reader); + return res; +} + +GstIsoffParserResult +gst_isoff_qt_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buffer, + guint * consumed) +{ + GstIsoffParserResult res = GST_ISOFF_QT_PARSER_OK; + GstMapInfo info; + + if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { + *consumed = 0; + return GST_ISOFF_QT_PARSER_ERROR; + } + + res = + gst_isoff_qt_sidx_parser_add_data (parser, info.data, info.size, + consumed); + + gst_buffer_unmap (buffer, &info); + return res; +} diff --git a/gst/isomp4_1_8/gstisoff.h b/gst/isomp4_1_8/gstisoff.h new file mode 100644 index 0000000000..c6fbf33828 --- /dev/null +++ b/gst/isomp4_1_8/gstisoff.h @@ -0,0 +1,100 @@ +/* + * ISO File Format parsing library + * + * gstisoff.h + * + * Copyright (C) 2015 Samsung Electronics. All rights reserved. + * Author: Thiago Santos + * + * 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.1 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 (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_ISOFF_QT_H__ +#define __GST_ISOFF_QT_H__ + +#include + +G_BEGIN_DECLS + +typedef enum { + GST_ISOFF_QT_PARSER_OK, + GST_ISOFF_QT_PARSER_DONE, + GST_ISOFF_QT_PARSER_UNEXPECTED, + GST_ISOFF_QT_PARSER_ERROR +} GstIsoffParserResult; + +/* this is the minimum size, it can be larger if it + * uses extended size or type */ +#define GST_ISOFF_QT_FULL_BOX_SIZE 12 + +#define GST_ISOFF_QT_FOURCC_SIDX GST_MAKE_FOURCC('s','i','d','x') +typedef struct _GstSidxBoxEntry +{ + gboolean ref_type; + guint32 size; + GstClockTime duration; + gboolean starts_with_sap; + guint8 sap_type; + guint32 sap_delta_time; + + guint64 offset; + GstClockTime pts; +} GstSidxBoxEntry; + +typedef struct _GstSidxBox +{ + guint8 version; + guint32 flags; + + guint32 ref_id; + guint32 timescale; + guint64 earliest_pts; + guint64 first_offset; + + gint entry_index; + gint entries_count; + + GstSidxBoxEntry *entries; +} GstSidxBox; + +typedef enum _GstSidxParserStatus +{ + GST_ISOFF_QT_SIDX_PARSER_INIT, + GST_ISOFF_QT_SIDX_PARSER_HEADER, + GST_ISOFF_QT_SIDX_PARSER_DATA, + GST_ISOFF_QT_SIDX_PARSER_FINISHED +} GstSidxParserStatus; + +typedef struct _GstSidxParser +{ + GstSidxParserStatus status; + + guint64 size; + guint64 cumulative_entry_size; + guint64 cumulative_pts; + + GstSidxBox sidx; +} GstSidxParser; + +void gst_isoff_qt_sidx_parser_init (GstSidxParser * parser); +void gst_isoff_qt_sidx_parser_clear (GstSidxParser * parser); +GstIsoffParserResult gst_isoff_qt_sidx_parser_add_data (GstSidxParser * parser, const guint8 * buffer, gint length, guint * consumed); +GstIsoffParserResult gst_isoff_qt_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buf, guint * consumed); + +G_END_DECLS + +#endif /* __GST_ISOFF_QT_H__ */ + diff --git a/gst/isomp4_1_8/isomp4-plugin.c b/gst/isomp4_1_8/isomp4-plugin.c new file mode 100644 index 0000000000..fb101160d6 --- /dev/null +++ b/gst/isomp4_1_8/isomp4-plugin.c @@ -0,0 +1,52 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David A. Schleef + * Copyright (C) <2006> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gst/gst-i18n-plugin.h" + +#include "qtdemux.h" + +#include + +static gboolean +plugin_init (GstPlugin * plugin) +{ +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + + gst_pb_utils_init (); + + if (!gst_element_register (plugin, "qtdemux_1_8", + GST_RANK_NONE, GST_TYPE_QTDEMUX)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + isomp4_1_8, + "ISO base media file format support (mp4, 3gpp, qt, mj2)", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/isomp4_1_8/meson.build b/gst/isomp4_1_8/meson.build new file mode 100644 index 0000000000..ffd5bdf1c0 --- /dev/null +++ b/gst/isomp4_1_8/meson.build @@ -0,0 +1,29 @@ +mp4_sources = [ + 'isomp4-plugin.c', + 'qtdemux.c', + 'qtdemux_types.c', + 'qtdemux_dump.c', + 'qtdemux_lang.c', + 'gstisoff.c', + 'descriptors.c', + 'properties.c', +] + +mp4_args = [ + '-DGstQTDemux=GstQTDemux_1_8', + '-DGstQTDemuxClass=GstQTDemux_1_8Class' +] + +gstsubtitle_dep = dependency('gstreamer-subtitle-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'subtitle_dep']) + +gstisomp4_1_8 = library('gstisomp4_1_8', + mp4_sources, + c_args : gst_plugins_good_args + mp4_args, + link_args : noseh_link_args, + include_directories : [configinc, libsinc], + dependencies : [gst_dep, gstriff_dep, gstaudio_dep, gstvideo_dep, + gstsubtitle_dep, gstpbutils_dep, zlib_dep], + install : true, + install_dir : plugins_install_dir, +) diff --git a/gst/isomp4_1_8/properties.c b/gst/isomp4_1_8/properties.c new file mode 100644 index 0000000000..cb43e295e5 --- /dev/null +++ b/gst/isomp4_1_8/properties.c @@ -0,0 +1,210 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "properties.h" + +/* if needed, re-allocate buffer to ensure size bytes can be written into it + * at offset */ +void +prop_copy_ensure_buffer (guint8 ** buffer, guint64 * bsize, guint64 * offset, + guint64 size) +{ + if (buffer && *bsize - *offset < size) { + *bsize += size + 10 * 1024; + *buffer = g_realloc (*buffer, *bsize); + } +} + +static guint64 +copy_func (void *prop, guint size, guint8 ** buffer, guint64 * bsize, + guint64 * offset) +{ + if (buffer) { + prop_copy_ensure_buffer (buffer, bsize, offset, size); + memcpy (*buffer + *offset, prop, size); + } + *offset += size; + return size; +} + +#define INT_ARRAY_COPY_FUNC_FAST(name, datatype) \ +guint64 prop_copy_ ## name ## _array (datatype *prop, guint size, \ + guint8 ** buffer, guint64 * bsize, guint64 * offset) { \ + return copy_func (prop, sizeof (datatype) * size, buffer, bsize, offset);\ +} + +#define INT_ARRAY_COPY_FUNC(name, datatype) \ +guint64 prop_copy_ ## name ## _array (datatype *prop, guint size, \ + guint8 ** buffer, guint64 * bsize, guint64 * offset) { \ + guint i; \ + \ + for (i = 0; i < size; i++) { \ + prop_copy_ ## name (prop[i], buffer, bsize, offset); \ + } \ + return sizeof (datatype) * size; \ +} + +/* INTEGERS */ +guint64 +prop_copy_uint8 (guint8 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + return copy_func (&prop, sizeof (guint8), buffer, size, offset); +} + +guint64 +prop_copy_uint16 (guint16 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT16_TO_BE (prop); + return copy_func (&prop, sizeof (guint16), buffer, size, offset); +} + +guint64 +prop_copy_uint32 (guint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT32_TO_BE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +guint64 +prop_copy_uint64 (guint64 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GUINT64_TO_BE (prop); + return copy_func (&prop, sizeof (guint64), buffer, size, offset); +} + +guint64 +prop_copy_int32 (gint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GINT32_TO_BE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +/* uint8 can use direct copy in any case, and may be used for large quantity */ +INT_ARRAY_COPY_FUNC_FAST (uint8, guint8); +/* not used in large quantity anyway */ +INT_ARRAY_COPY_FUNC (uint16, guint16); +INT_ARRAY_COPY_FUNC (uint32, guint32); +INT_ARRAY_COPY_FUNC (uint64, guint64); + +/* FOURCC */ +guint64 +prop_copy_fourcc (guint32 prop, guint8 ** buffer, guint64 * size, + guint64 * offset) +{ + prop = GINT32_TO_LE (prop); + return copy_func (&prop, sizeof (guint32), buffer, size, offset); +} + +INT_ARRAY_COPY_FUNC (fourcc, guint32); + +/** + * prop_copy_fixed_size_string: + * @string: the string to be copied + * @str_size: size of the string + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Copies a string of bytes without placing its size at the beginning. + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_fixed_size_string (guint8 * string, guint str_size, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + return copy_func (string, str_size * sizeof (guint8), buffer, size, offset); +} + +/** + * prop_copy_size_string: + * + * @string: the string to be copied + * @str_size: size of the string + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Copies a string and its size to an array. Example: + * string = 'abc\0' + * result in the array: [3][a][b][c] (each [x] represents a position) + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_size_string (guint8 * string, guint str_size, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + + prop_copy_uint8 (str_size, buffer, size, offset); + prop_copy_fixed_size_string (string, str_size, buffer, size, offset); + return *offset - original_offset; +} + +/** + * prop_copy_null_terminated_string: + * @string: the string to be copied + * @buffer: the array to copy the string to + * @offset: the position in the buffer array. + * This value is updated to the point right after the copied string. + * + * Copies a string including its null terminating char to an array. + * + * Returns: the number of bytes copied + */ +guint64 +prop_copy_null_terminated_string (gchar * string, guint8 ** buffer, + guint64 * size, guint64 * offset) +{ + guint64 original_offset = *offset; + guint len = strlen (string); + + prop_copy_fixed_size_string ((guint8 *) string, len, buffer, size, offset); + prop_copy_uint8 ('\0', buffer, size, offset); + return *offset - original_offset; +} diff --git a/gst/isomp4_1_8/properties.h b/gst/isomp4_1_8/properties.h new file mode 100644 index 0000000000..c36fe48769 --- /dev/null +++ b/gst/isomp4_1_8/properties.h @@ -0,0 +1,87 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008 Thiago Sousa Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/* + * Unless otherwise indicated, Source Code is licensed under MIT license. + * See further explanation attached in License Statement (distributed in the file + * LICENSE). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __PROPERTIES_H__ +#define __PROPERTIES_H__ + +#include +#include + +/* + * Functions for copying atoms properties. + * + * All of them receive, as the input, the property to be copied, the destination + * buffer, and a pointer to an offset in the destination buffer to copy to the right place. + * This offset will be updated to the new value (offset + copied_size) + * The functions return the size of the property that has been copied or 0 + * if it couldn't copy. + */ + +void prop_copy_ensure_buffer (guint8 ** buffer, guint64 * bsize, guint64 * offset, guint64 size); + +guint64 prop_copy_uint8 (guint8 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint16 (guint16 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint32 (guint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_uint64 (guint64 prop, guint8 **buffer, guint64 *size, guint64 *offset); + +guint64 prop_copy_int32 (gint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); + +guint64 prop_copy_uint8_array (guint8 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint16_array (guint16 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint32_array (guint32 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_uint64_array (guint64 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); + +guint64 prop_copy_fourcc (guint32 prop, guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_fourcc_array (guint32 *prop, guint size, + guint8 **buffer, guint64 *bsize, guint64 *offset); +guint64 prop_copy_fixed_size_string (guint8 *string, guint str_size, + guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_size_string (guint8 *string, guint str_size, + guint8 **buffer, guint64 *size, guint64 *offset); +guint64 prop_copy_null_terminated_string (gchar *string, + guint8 **buffer, guint64 *size, guint64 *offset); + +#endif /* __PROPERTIES_H__ */ diff --git a/gst/isomp4_1_8/qtatomparser.h b/gst/isomp4_1_8/qtatomparser.h new file mode 100644 index 0000000000..89bab50d8e --- /dev/null +++ b/gst/isomp4_1_8/qtatomparser.h @@ -0,0 +1,139 @@ +/* GStreamer QuickTime atom parser + * Copyright (C) 2009 Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef QT_ATOM_PARSER_H +#define QT_ATOM_PARSER_H + +#include + +/* our inlined version of GstByteReader */ + +static inline gboolean +qt_atom_parser_has_remaining (GstByteReader * parser, guint64 bytes_needed) +{ + return G_LIKELY (parser->size >= bytes_needed) && + G_LIKELY ((parser->size - bytes_needed) >= parser->byte); +} + +static inline gboolean +qt_atom_parser_has_chunks (GstByteReader * parser, guint32 n_chunks, + guint32 chunk_size) +{ + /* assumption: n_chunks and chunk_size are 32-bit, we cast to 64-bit here + * to avoid overflows, to handle e.g. (guint32)-1 * size correctly */ + return qt_atom_parser_has_remaining (parser, (guint64) n_chunks * chunk_size); +} + +static inline gboolean +qt_atom_parser_peek_sub (GstByteReader * parser, guint offset, guint size, + GstByteReader * sub) +{ + *sub = *parser; + + if (G_UNLIKELY (!gst_byte_reader_skip (sub, offset))) + return FALSE; + + return (gst_byte_reader_get_remaining (sub) >= size); +} + +static inline gboolean +qt_atom_parser_skipn_and_get_uint32 (GstByteReader * parser, + guint bytes_to_skip, guint32 * val) +{ + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < (bytes_to_skip + 4))) + return FALSE; + + gst_byte_reader_skip_unchecked (parser, bytes_to_skip); + *val = gst_byte_reader_get_uint32_be_unchecked (parser); + return TRUE; +} + +/* off_size must be either 4 or 8 */ +static inline gboolean +qt_atom_parser_get_offset (GstByteReader * parser, guint off_size, + guint64 * val) +{ + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < off_size)) + return FALSE; + + if (off_size == sizeof (guint64)) { + *val = gst_byte_reader_get_uint64_be_unchecked (parser); + } else { + *val = gst_byte_reader_get_uint32_be_unchecked (parser); + } + return TRUE; +} + +/* off_size must be either 4 or 8 */ +static inline guint64 +qt_atom_parser_get_offset_unchecked (GstByteReader * parser, guint off_size) +{ + if (off_size == sizeof (guint64)) { + return gst_byte_reader_get_uint64_be_unchecked (parser); + } else { + return gst_byte_reader_get_uint32_be_unchecked (parser); + } +} + +/* size must be from 1 to 4 */ +static inline guint32 +qt_atom_parser_get_uint_with_size_unchecked (GstByteReader * parser, + guint size) +{ + switch (size) { + case 1: + return gst_byte_reader_get_uint8_unchecked (parser); + case 2: + return gst_byte_reader_get_uint16_be_unchecked (parser); + case 3: + return gst_byte_reader_get_uint24_be_unchecked (parser); + case 4: + return gst_byte_reader_get_uint32_be_unchecked (parser); + default: + g_assert_not_reached (); + gst_byte_reader_skip_unchecked (parser, size); + break; + } + return 0; +} + +static inline gboolean +qt_atom_parser_get_fourcc (GstByteReader * parser, guint32 * fourcc) +{ + guint32 f_be; + + if (G_UNLIKELY (gst_byte_reader_get_remaining (parser) < 4)) + return FALSE; + + f_be = gst_byte_reader_get_uint32_be_unchecked (parser); + *fourcc = GUINT32_SWAP_LE_BE (f_be); + return TRUE; +} + +static inline guint32 +qt_atom_parser_get_fourcc_unchecked (GstByteReader * parser) +{ + guint32 fourcc; + + fourcc = gst_byte_reader_get_uint32_be_unchecked (parser); + return GUINT32_SWAP_LE_BE (fourcc); +} + +#endif /* QT_ATOM_PARSER_H */ diff --git a/gst/isomp4_1_8/qtdemux.c b/gst/isomp4_1_8/qtdemux.c new file mode 100644 index 0000000000..e9b1790db6 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux.c @@ -0,0 +1,16458 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David A. Schleef + * Copyright (C) <2006> Wim Taymans + * Copyright (C) <2007> Julien Moutte + * Copyright (C) <2009> Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * Copyright (C) <2013> Sreerenj Balachandran + * Copyright (C) <2013> Intel Corporation + * Copyright (C) <2014> Centricular Ltd + * Copyright (C) <2015> YouView TV Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-qtdemux + * + * Demuxes a .mov file into raw or compressed audio and/or video streams. + * + * This element supports both push and pull-based scheduling, depending on the + * capabilities of the upstream elements. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=test.mov ! qtdemux name=demux demux.audio_0 ! queue ! decodebin ! audioconvert ! audioresample ! autoaudiosink demux.video_0 ! queue ! decodebin ! videoconvert ! videoscale ! autovideosink + * ]| Play (parse and decode) a .mov file and try to output it to + * an automatically detected soundcard and videosink. If the MOV file contains + * compressed audio or video data, this will only work if you have the + * right decoder elements/plugins installed. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst/gst-i18n-plugin.h" + +#include +#include +#include +#include +#include + +#include "qtatomparser.h" +#include "qtdemux_types.h" +#include "qtdemux_dump.h" +#include "fourcc.h" +#include "descriptors.h" +#include "qtdemux_lang.h" +#include "qtdemux.h" +#include "qtpalette.h" + +#include "gst/riff/riff-media.h" +#include "gst/riff/riff-read.h" + +#include + +#include +#include +#include + +#include +#include + +#ifdef HAVE_ZLIB +#include +#endif + +/* max. size considered 'sane' for non-mdat atoms */ +#define QTDEMUX_MAX_ATOM_SIZE (32*1024*1024) + +/* maximum allowed non-mdat top-level box size for ATSC3.0 */ +#define QTDEMUX_ATSC3_MAX_ATOM_SIZE (5*1024) + +/* if the sample index is larger than this, something is likely wrong */ +#define QTDEMUX_MAX_SAMPLE_INDEX_SIZE (50*1024*1024) + +/* For converting qt creation times to unix epoch times */ +#define QTDEMUX_SECONDS_PER_DAY (60 * 60 * 24) +#define QTDEMUX_LEAP_YEARS_FROM_1904_TO_1970 17 +#define QTDEMUX_SECONDS_FROM_1904_TO_1970 (((1970 - 1904) * (guint64) 365 + \ + QTDEMUX_LEAP_YEARS_FROM_1904_TO_1970) * QTDEMUX_SECONDS_PER_DAY) + +#define QTDEMUX_TREE_NODE_FOURCC(n) ((n) ? \ + QT_FOURCC(((guint8 *) (n)->data) + 4) : FOURCC_null) + +#define STREAM_IS_EOS(s) (s->time_position == GST_CLOCK_TIME_NONE) + +GST_DEBUG_CATEGORY (qtdemux_debug); + +/*typedef struct _QtNode QtNode; */ +typedef struct _QtDemuxSegment QtDemuxSegment; +typedef struct _QtDemuxSample QtDemuxSample; + +typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo; +typedef struct _QtDemuxSampleToGroup QtDemuxSampleToGroup; + +typedef struct _QtDemuxSampleToGroupEntry QtDemuxSampleToGroupEntry; + +/*struct _QtNode +{ + guint32 type; + guint8 *data; + gint len; +};*/ + +struct _QtDemuxSample +{ + guint32 size; + gint32 pts_offset; /* Add this value to timestamp to get the pts */ + guint64 offset; + guint64 timestamp; /* DTS In mov time */ + guint32 duration; /* In mov time */ + gboolean keyframe; /* TRUE when this packet is a keyframe */ +}; + +/* Macros for converting to/from timescale */ +#define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale)) +#define GSTTIME_TO_QTSTREAMTIME(stream, value) (gst_util_uint64_scale((value), (stream)->timescale, GST_SECOND)) + +#define QTTIME_TO_GSTTIME(qtdemux, value) (gst_util_uint64_scale((value), GST_SECOND, (qtdemux)->timescale)) +#define GSTTIME_TO_QTTIME(qtdemux, value) (gst_util_uint64_scale((value), (qtdemux)->timescale, GST_SECOND)) + +/* timestamp is the DTS */ +#define QTSAMPLE_DTS(stream,sample) (QTSTREAMTIME_TO_GSTTIME((stream), (sample)->timestamp)) +/* timestamp + offset is the PTS */ +#define QTSAMPLE_PTS(stream,sample) (QTSTREAMTIME_TO_GSTTIME((stream), (sample)->timestamp + (stream)->cslg_shift + (sample)->pts_offset)) +/* timestamp + duration - dts is the duration */ +#define QTSAMPLE_DUR_DTS(stream, sample, dts) (QTSTREAMTIME_TO_GSTTIME ((stream), (sample)->timestamp + (sample)->duration) - (dts)) + +#define QTSAMPLE_KEYFRAME(stream,sample) ((stream)->all_keyframe || (sample)->keyframe) + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) +#define QTDEMUX_IS_CUSTOM_MODE(qtdemux) ((qtdemux)->dash_mode || (qtdemux)->atsc3_mode) +#endif + +#define QTDEMUX_EXPOSE_GET_LOCK(demux) (&((demux)->expose_lock)) +#define QTDEMUX_EXPOSE_LOCK(demux) G_STMT_START { \ + GST_DEBUG_OBJECT (demux, "Locking from thread %p", g_thread_self()); \ + g_mutex_lock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \ + GST_DEBUG_OBJECT (demux, "Locked from thread %p", g_thread_self()); \ +} G_STMT_END + +#define QTDEMUX_EXPOSE_UNLOCK(demux) G_STMT_START { \ + GST_DEBUG_OBJECT (demux, "Unlocking from thread %p", g_thread_self()); \ + g_mutex_unlock (QTDEMUX_EXPOSE_GET_LOCK (demux)); \ +} G_STMT_END + +/* + * Quicktime has tracks and segments. A track is a continuous piece of + * multimedia content. The track is not always played from start to finish but + * instead, pieces of the track are 'cut out' and played in sequence. This is + * what the segments do. + * + * Inside the track we have keyframes (K) and delta frames. The track has its + * own timing, which starts from 0 and extends to end. The position in the track + * is called the media_time. + * + * The segments now describe the pieces that should be played from this track + * and are basically tuples of media_time/duration/rate entries. We can have + * multiple segments and they are all played after one another. An example: + * + * segment 1: media_time: 1 second, duration: 1 second, rate 1 + * segment 2: media_time: 3 second, duration: 2 second, rate 2 + * + * To correctly play back this track, one must play: 1 second of media starting + * from media_time 1 followed by 2 seconds of media starting from media_time 3 + * at a rate of 2. + * + * Each of the segments will be played at a specific time, the first segment at + * time 0, the second one after the duration of the first one, etc.. Note that + * the time in resulting playback is not identical to the media_time of the + * track anymore. + * + * Visually, assuming the track has 4 second of media_time: + * + * (a) (b) (c) (d) + * .-----------------------------------------------------------. + * track: | K.....K.........K........K.......K.......K...........K... | + * '-----------------------------------------------------------' + * 0 1 2 3 4 + * .------------^ ^ .----------^ ^ + * / .-------------' / .------------------' + * / / .-----' / + * .--------------. .--------------. + * | segment 1 | | segment 2 | + * '--------------' '--------------' + * + * The challenge here is to cut out the right pieces of the track for each of + * the playback segments. This fortunately can easily be done with the SEGMENT + * events of GStreamer. + * + * For playback of segment 1, we need to provide the decoder with the keyframe + * (a), in the above figure, but we must instruct it only to output the decoded + * data between second 1 and 2. We do this with a SEGMENT event for 1 to 2, time + * position set to the time of the segment: 0. + * + * We then proceed to push data from keyframe (a) to frame (b). The decoder + * decodes but clips all before media_time 1. + * + * After finishing a segment, we push out a new SEGMENT event with the clipping + * boundaries of the new data. + * + * This is a good usecase for the GStreamer accumulated SEGMENT events. + */ + +struct _QtDemuxSegment +{ + /* global time and duration, all gst time */ + GstClockTime time; + GstClockTime stop_time; + GstClockTime duration; + /* media time of trak, all gst time */ + GstClockTime media_start; + GstClockTime media_stop; + gdouble rate; + /* Media start time in trak timescale units */ + guint32 trak_media_start; +}; + +#define QTSEGMENT_IS_EMPTY(s) ((s)->media_start == GST_CLOCK_TIME_NONE) + +/* Used with fragmented MP4 files (mfra atom) + * Due to the similarity between this and sidx entry, + * sidx box also use this structure for the purpose of unification + */ +typedef struct +{ + GstClockTime ts; + guint64 moof_offset; +} QtDemuxRandomAccessEntry; + +typedef struct _QtDemuxStreamStsdEntry +{ + GstCaps *caps; + guint32 fourcc; + + /* if we use chunks or samples */ + gboolean sampled; + guint padding; + + /* video info */ + gint width; + gint height; + /* aspect ratio */ + gint display_width; + gint display_height; + gint par_w; + gint par_h; + + guint16 bits_per_sample; + guint16 color_table_id; + GstMemory *rgb8_palette; + + /* audio info */ + gdouble rate; + gint n_channels; + guint samples_per_packet; + guint samples_per_frame; + guint bytes_per_packet; + guint bytes_per_sample; + guint bytes_per_frame; + guint compression; + +} QtDemuxStreamStsdEntry; + +struct _QtDemuxStream +{ + GstPad *pad; + + QtDemuxStreamStsdEntry *stsd_entries; + guint stsd_entries_length; + guint cur_stsd_entry_index; + + /* stream type */ + guint32 subtype; + GstCaps *caps; + guint32 fourcc; + gboolean sparse; + + gboolean new_caps; + gboolean new_stream; /* signals that a stream_start is required */ + gboolean on_keyframe; /* if this stream last pushed buffer was a + * keyframe. This is important to identify + * where to stop pushing buffers after a + * segment stop time */ + + /* if the stream has a redirect URI in its headers, we store it here */ + gchar *redirect_uri; + + /* track id */ + guint track_id; + + /* duration/scale */ + guint64 duration; /* in timescale */ + guint32 timescale; + + /* language */ + gchar lang_id[4]; /* ISO 639-2T language code */ + + /* our samples */ + guint32 n_samples; + QtDemuxSample *samples; + gboolean all_keyframe; /* TRUE when all samples are keyframes (no stss) */ + guint32 first_duration; /* duration in timescale of first sample, used for figuring out + the framerate, in timescale units */ + guint32 n_samples_moof; /* sample count in a moof */ + guint64 duration_moof; /* duration in timescale of a moof, used for figure out + * the framerate of fragmented format stream */ + guint32 offset_in_sample; + guint32 max_buffer_size; + + /* if we use chunks or samples */ + gboolean sampled; + guint padding; + + /* video info */ + gint width; + gint height; + /* aspect ratio */ + gint display_width; + gint display_height; + gint par_w; + gint par_h; + /* Numerator/denominator framerate */ + gint fps_n; + gint fps_d; + guint16 bits_per_sample; + guint16 color_table_id; + GstMemory *rgb8_palette; + + /* audio info */ + gdouble rate; + gint n_channels; + guint samples_per_packet; + guint samples_per_frame; + guint bytes_per_packet; + guint bytes_per_sample; + guint bytes_per_frame; + guint compression; + + /* allocation */ + gboolean use_allocator; + GstAllocator *allocator; + GstAllocationParams params; + + /* when a discontinuity is pending */ + gboolean discont; + + /* list of buffers to push first */ + GSList *buffers; + + /* if we need to clip this buffer. This is only needed for uncompressed + * data */ + gboolean need_clip; + + /* buffer needs some custom processing, e.g. subtitles */ + gboolean need_process; + + /* current position */ + guint32 segment_index; + guint32 sample_index; + GstClockTime time_position; /* in gst time */ + guint64 accumulated_base; + + /* the Gst segment we are processing out, used for clipping */ + GstSegment segment; + + /* quicktime segments */ + guint32 n_segments; + QtDemuxSegment *segments; + gboolean dummy_segment; + guint32 from_sample; + guint32 to_sample; + + gboolean sent_eos; + GstTagList *pending_tags; + gboolean send_global_tags; + + GstEvent *pending_event; + + GstByteReader stco; + GstByteReader stsz; + GstByteReader stsc; + GstByteReader stts; + GstByteReader stss; + GstByteReader stps; + GstByteReader ctts; + + gboolean chunks_are_samples; /* TRUE means treat chunks as samples */ + gint64 stbl_index; + /* stco */ + guint co_size; + GstByteReader co_chunk; + guint32 first_chunk; + guint32 current_chunk; + guint32 last_chunk; + guint32 samples_per_chunk; + guint32 stsd_sample_description_id; + guint32 stco_sample_index; + /* stsz */ + guint32 sample_size; /* 0 means variable sizes are stored in stsz */ + /* stsc */ + guint32 stsc_index; + guint32 n_samples_per_chunk; + guint32 stsc_chunk_index; + guint32 stsc_sample_index; + guint64 chunk_offset; + /* stts */ + guint32 stts_index; + guint32 stts_samples; + guint32 n_sample_times; + guint32 stts_sample_index; + guint64 stts_time; + guint32 stts_duration; + guint64 stts_total_duration; /* To ensure stream duration */ + /* stss */ + gboolean stss_present; + guint32 n_sample_syncs; + guint32 stss_index; + /* stps */ + gboolean stps_present; + guint32 n_sample_partial_syncs; + guint32 stps_index; + QtDemuxRandomAccessEntry *ra_entries; + guint n_ra_entries; + + const QtDemuxRandomAccessEntry *pending_seek; + + /* ctts */ + gboolean ctts_present; + guint32 n_composition_times; + guint32 ctts_index; + guint32 ctts_sample_index; + guint32 ctts_count; + gint32 ctts_soffset; + + /* cslg */ + guint32 cslg_shift; + + /* fragmented */ + gboolean parsed_trex; + guint32 def_sample_duration; + guint32 def_sample_size; + guint32 def_sample_flags; + + gboolean disabled; + + /* sidx */ + QtDemuxRandomAccessEntry *seg_idx_entries; + guint n_seg_idx_entries; + + /* stereoscopic video streams */ + GstVideoMultiviewMode multiview_mode; + GstVideoMultiviewFlags multiview_flags; + + /* protected streams */ + gboolean protected; + guint32 protection_scheme_type; + guint32 protection_scheme_version; + gpointer protection_scheme_info; /* specific to the protection scheme */ + GQueue protection_scheme_event_queue; + + guint64 first_chunk_offset; + guint64 last_chunk_offset; + guint8 length_size_avcC; + + /* SampleToGroup information structure */ + GList *sample_to_group; +}; + +/* Contains properties and cryptographic info for a set of samples from a + * track protected using Common Encryption (cenc) */ +struct _QtDemuxCencSampleSetInfo +{ + GstStructure *default_properties; + + /* overriding default_properties if any */ + GPtrArray *sample_properties; + + /* @crypto_info holds one GstStructure per sample */ + GPtrArray *crypto_info; +}; + +struct _QtDemuxSampleToGroupEntry +{ + guint32 sample_count; + guint32 group_description_idx; + + /* grouping_type specific data */ + gpointer data; +}; + +struct _QtDemuxSampleToGroup +{ + guint32 grouping_type; + guint32 grouping_type_parameter; /* version 1 only */ + + QtDemuxSampleToGroupEntry *sbgp_entries; + guint32 sbgp_entries_length; + + GDestroyNotify entry_free_func; +}; + + +enum QtDemuxState +{ + QTDEMUX_STATE_INITIAL, /* Initial state (haven't got the header yet) */ + QTDEMUX_STATE_HEADER, /* Parsing the header */ + QTDEMUX_STATE_MOVIE, /* Parsing/Playing the media data */ + QTDEMUX_STATE_BUFFER_MDAT, /* Buffering the mdat atom */ + QTDEMUX_STATE_LOST_SYNC /* Lost sync in push mode, drop data until ftyp box */ +}; + +static GNode *qtdemux_tree_get_child_by_type (GNode * node, guint32 fourcc); +static GNode *qtdemux_tree_get_child_by_type_full (GNode * node, + guint32 fourcc, GstByteReader * parser); +static GNode *qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc); +static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node, + guint32 fourcc, GstByteReader * parser); + +static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux); + +static GstStaticPadTemplate gst_qtdemux_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/quicktime; video/mj2; audio/x-m4a; " + "application/x-3gp") + ); + +static GstStaticPadTemplate gst_qtdemux_videosrc_template = +GST_STATIC_PAD_TEMPLATE ("video_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_qtdemux_audiosrc_template = +GST_STATIC_PAD_TEMPLATE ("audio_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_qtdemux_subsrc_template = +GST_STATIC_PAD_TEMPLATE ("subtitle_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) +enum +{ + START_TIME, + LAST_SIGNAL +}; + +static guint qtdemux_signals[LAST_SIGNAL] = { 0 }; +#endif + +#define gst_qtdemux_parent_class parent_class +G_DEFINE_TYPE (GstQTDemux, gst_qtdemux, GST_TYPE_ELEMENT); + +static void gst_qtdemux_dispose (GObject * object); + +static guint32 +gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str, + GstClockTime media_time); +static guint32 +gst_qtdemux_find_index_for_given_media_offset_linear (GstQTDemux * qtdemux, + QtDemuxStream * str, gint64 media_offset); + +#if 0 +static void gst_qtdemux_set_index (GstElement * element, GstIndex * index); +static GstIndex *gst_qtdemux_get_index (GstElement * element); +#endif +static GstStateChangeReturn gst_qtdemux_change_state (GstElement * element, + GstStateChange transition); +static gboolean qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent); +static gboolean qtdemux_sink_activate_mode (GstPad * sinkpad, + GstObject * parent, GstPadMode mode, gboolean active); + +static void gst_qtdemux_loop (GstPad * pad); +static GstFlowReturn gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, + GstBuffer * inbuf); +static gboolean gst_qtdemux_handle_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event); +static gboolean gst_qtdemux_setcaps (GstQTDemux * qtdemux, GstCaps * caps); +static gboolean gst_qtdemux_configure_stream (GstQTDemux * qtdemux, + QtDemuxStream * stream); +static void gst_qtdemux_stream_check_and_change_stsd_index (GstQTDemux * demux, + QtDemuxStream * stream); +static GstFlowReturn gst_qtdemux_process_adapter (GstQTDemux * demux, + gboolean force); + +static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux, + const guint8 * buffer, guint length); +static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, + const guint8 * buffer, guint length); +static gboolean qtdemux_parse_tree (GstQTDemux * qtdemux); +static void qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, + GNode * udta); + +static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux, + QtDemuxStream * stream, GNode * esds, GstTagList * list); +static GstCaps *qtdemux_video_caps (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 fourcc, const guint8 * stsd_entry_data, + gchar ** codec_name); +static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 fourcc, const guint8 * data, int len, + gchar ** codec_name); +static GstCaps *qtdemux_sub_caps (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 fourcc, const guint8 * data, + gchar ** codec_name); +static GstCaps *qtdemux_generic_caps (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 fourcc, const guint8 * stsd_entry_data, + gchar ** codec_name); + +static inline QtDemuxStream *qtdemux_find_stream (GstQTDemux * qtdemux, + guint32 id); +static gboolean qtdemux_parse_samples (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 n); +static GstFlowReturn qtdemux_expose_streams (GstQTDemux * qtdemux); +static void gst_qtdemux_stream_free (GstQTDemux * qtdemux, + QtDemuxStream * stream); +static void gst_qtdemux_stream_clear (GstQTDemux * qtdemux, + QtDemuxStream * stream); +static void gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int index); +static GstFlowReturn qtdemux_prepare_streams (GstQTDemux * qtdemux); +static void qtdemux_do_allocation (GstQTDemux * qtdemux, + QtDemuxStream * stream); +static gboolean gst_qtdemux_activate_segment (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 seg_idx, GstClockTime offset); +static gboolean gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, + QtDemuxStream * stream, gint seg_idx, GstClockTime offset, + GstClockTime * _start, GstClockTime * _stop); +static void gst_qtdemux_send_gap_for_segment (GstQTDemux * demux, + QtDemuxStream * stream, gint segment_index, GstClockTime pos); + +static gboolean qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux); +static void check_update_duration (GstQTDemux * qtdemux, GstClockTime duration); + +static gboolean qtdemux_is_keyframe_trick_condition (GstQTDemux * qtdemux); + +static gchar *qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes); + +static GstStructure *qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint sample_index); + +static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux, + const gchar * id); +static void qtdemux_gst_structure_free (GstStructure * gststructure); + +static gboolean qtdemux_sample_description_copy_into_stream (GstQTDemux * + qtdemux, QtDemuxStreamStsdEntry * stsd_entry, QtDemuxStream * stream); + +static gboolean qtdemux_stream_copy_into_sample_description (GstQTDemux * + qtdemux, QtDemuxStream * stream, gint stsd_index); + +#ifdef MP4_PUSHMODE_TRICK +static gboolean gst_qtdemux_seek_push (GstQTDemux * demux, + guint64 start, guint64 stop); + +static gboolean gst_qtdemux_handle_trick (GstQTDemux * demux); + +#endif + +static gboolean is_common_enc_scheme_type (guint32 scheme_type); + +#ifdef DOLBYHDR_SUPPORT +static gboolean qtdemux_prepare_dolby_track (GstQTDemux * qtdemux, + QtDemuxStream * stream); +#endif + +static void +gst_qtdemux_class_init (GstQTDemuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_qtdemux_dispose; + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + qtdemux_signals[START_TIME] = + g_signal_new ("start-time", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstQTDemuxClass, start_time), + NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 3, + G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER); +#endif + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qtdemux_change_state); +#if 0 + gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_qtdemux_set_index); + gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_qtdemux_get_index); +#endif + + gst_tag_register_musicbrainz_tags (); + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_qtdemux_sink_template); + gst_element_class_add_static_pad_template (gstelement_class, + &gst_qtdemux_videosrc_template); + gst_element_class_add_static_pad_template (gstelement_class, + &gst_qtdemux_audiosrc_template); + gst_element_class_add_static_pad_template (gstelement_class, + &gst_qtdemux_subsrc_template); + gst_element_class_set_static_metadata (gstelement_class, "QuickTime demuxer", + "Codec/Demuxer", + "Demultiplex a QuickTime file into audio and video streams", + "David Schleef , Wim Taymans "); + + GST_DEBUG_CATEGORY_INIT (qtdemux_debug, "qtdemux", 0, "qtdemux plugin"); + +} + +static void +get_property_cb (GstPad * pad, GstPad * peer, gpointer usr_data) +{ + GstSmartPropertiesReturn res; + GstQTDemux *qtdemux = GST_QTDEMUX (usr_data); + + res = + gst_element_get_smart_properties (GST_ELEMENT_CAST (qtdemux), + "thumbnail-mode", &qtdemux->thumbnail_mode, +#ifdef MPEGDASH_MODE + "dash-mode", &qtdemux->dash_mode, "use-svp", &qtdemux->use_svp, +#endif +#ifdef ATSC3_MODE + "atsc3-mode", &qtdemux->atsc3_mode, +#endif +#ifdef DOLBYHDR_SUPPORT + "dolby-vision-support", &qtdemux->dolby_vision_support, +#endif + "dlna-opval", &qtdemux->dlna_opval, NULL); + + if (res != GST_SMART_PROPERTIES_OK) { + GST_WARNING_OBJECT (qtdemux, "failed to get properties"); + } + + GST_INFO_OBJECT (qtdemux, "thumbnail-mode = %d", qtdemux->thumbnail_mode); +#ifdef MPEGDASH_MODE + GST_INFO_OBJECT (qtdemux, "dash-mode = %d", qtdemux->dash_mode); + GST_INFO_OBJECT (qtdemux, "use-svp = %d", qtdemux->use_svp); +#endif +#ifdef ATSC3_MODE + GST_INFO_OBJECT (qtdemux, "atsc3-mode = %d", qtdemux->atsc3_mode); +#endif +#ifdef DOLBYHDR_SUPPORT + GST_INFO_OBJECT (qtdemux, "dolby-vision-support = %d", + qtdemux->dolby_vision_support); +#endif + GST_INFO_OBJECT (qtdemux, "dlna-opval = %X", qtdemux->dlna_opval); +} + +static void +gst_qtdemux_init (GstQTDemux * qtdemux) +{ + qtdemux->sinkpad = + gst_pad_new_from_static_template (&gst_qtdemux_sink_template, "sink"); + gst_pad_set_activate_function (qtdemux->sinkpad, qtdemux_sink_activate); + gst_pad_set_activatemode_function (qtdemux->sinkpad, + qtdemux_sink_activate_mode); + gst_pad_set_chain_function (qtdemux->sinkpad, gst_qtdemux_chain); + gst_pad_set_event_function (qtdemux->sinkpad, gst_qtdemux_handle_sink_event); + gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), qtdemux->sinkpad); + + qtdemux->collection = NULL; + qtdemux->state = QTDEMUX_STATE_INITIAL; + qtdemux->pullbased = FALSE; + qtdemux->posted_redirect = FALSE; + qtdemux->upstream_basetime = GST_CLOCK_TIME_NONE; + qtdemux->new_collection = FALSE; + qtdemux->neededbytes = 16; + qtdemux->todrop = 0; + qtdemux->adapter = gst_adapter_new (); + qtdemux->offset = 0; + qtdemux->first_mdat = -1; + qtdemux->got_moov = FALSE; + qtdemux->mdatoffset = -1; + qtdemux->mdatbuffer = NULL; + qtdemux->restoredata_buffer = NULL; + qtdemux->restoredata_offset = -1; + qtdemux->fragment_start = -1; + qtdemux->fragment_start_offset = -1; + qtdemux->media_caps = NULL; + qtdemux->exposed = FALSE; + qtdemux->mss_mode = FALSE; + qtdemux->pending_newsegment = NULL; + qtdemux->upstream_format_is_time = FALSE; + qtdemux->have_group_id = FALSE; + qtdemux->group_id = G_MAXUINT; + qtdemux->cenc_aux_info_offset = 0; + qtdemux->cenc_aux_info_sizes = NULL; + qtdemux->cenc_aux_sample_count = 0; + qtdemux->protection_system_ids = NULL; + g_queue_init (&qtdemux->protection_event_queue); + qtdemux->seek_to_key_frame = FALSE; + gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); + qtdemux->flowcombiner = gst_flow_combiner_new (); + qtdemux->mpu_seq_num = G_MAXUINT32; + qtdemux->asset_id = NULL; + qtdemux->has_mmth = FALSE; + qtdemux->is_mmth_timed = TRUE; + qtdemux->ignore_hintsample = FALSE; + g_mutex_init (&qtdemux->expose_lock); + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + qtdemux->dash_mode = FALSE; + qtdemux->dash_pts_offset = GST_CLOCK_STIME_NONE; + qtdemux->dash_fragment_start = -1; + qtdemux->dash_segment_start = GST_CLOCK_TIME_NONE; + qtdemux->dash_period_start = 0; + qtdemux->dash_subtitle_offset = 0; + qtdemux->dash_subtitle_index = 0; +#endif +#ifdef ATSC3_MODE + qtdemux->atsc3_mode = FALSE; + qtdemux->prev_decode_time = GST_CLOCK_TIME_NONE; + qtdemux->configure_dvr = FALSE; +#endif + + GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE); + + qtdemux->thumbnail_mode = FALSE; + qtdemux->isBigData = FALSE; + qtdemux->isInterleaved = TRUE; + qtdemux->dlna_opval = 0x111; + qtdemux->prev_segment_position = 0; + +#ifdef DOLBYHDR_SUPPORT + /* Dolby HDR */ + qtdemux->dolby_vision_support = FALSE; + qtdemux->is_dolby_hdr = FALSE; + qtdemux->has_dolby_bl_cand = FALSE; + qtdemux->has_dolby_el_cand = FALSE; + + qtdemux->dv_profile = -1; + qtdemux->rpu_present_flag = FALSE; + qtdemux->el_present_flag = FALSE; + qtdemux->bl_present_flag = FALSE; +#endif + + qtdemux->highest_temporal_id = -1; + + g_signal_connect (G_OBJECT (qtdemux->sinkpad), "linked", + (GCallback) get_property_cb, qtdemux); +} + +static void +gst_qtdemux_dispose (GObject * object) +{ + GstQTDemux *qtdemux = GST_QTDEMUX (object); + + if (qtdemux->adapter) { + g_object_unref (G_OBJECT (qtdemux->adapter)); + qtdemux->adapter = NULL; + } + gst_flow_combiner_free (qtdemux->flowcombiner); + g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, + NULL); + g_queue_clear (&qtdemux->protection_event_queue); + + g_free (qtdemux->cenc_aux_info_sizes); + qtdemux->cenc_aux_info_sizes = NULL; + g_mutex_clear (&qtdemux->expose_lock); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_qtdemux_post_no_playable_stream_error (GstQTDemux * qtdemux) +{ + if (qtdemux->posted_redirect) { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file contains no playable streams.")), + ("no known streams found, a redirect message has been posted")); + } else { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file contains no playable streams.")), + ("no known streams found")); + } +} + +static GstBuffer * +_gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func) +{ + return gst_buffer_new_wrapped_full (free_func ? 0 : GST_MEMORY_FLAG_READONLY, + mem, size, 0, size, mem, free_func); +} + +static GstFlowReturn +gst_qtdemux_pull_atom (GstQTDemux * qtdemux, guint64 offset, guint64 size, + GstBuffer ** buf) +{ + GstFlowReturn flow; + GstMapInfo map; + gsize bsize; + + if (G_UNLIKELY (size == 0)) { + GstFlowReturn ret; + GstBuffer *tmp = NULL; + + ret = gst_qtdemux_pull_atom (qtdemux, offset, sizeof (guint32), &tmp); + if (ret != GST_FLOW_OK) + return ret; + + gst_buffer_map (tmp, &map, GST_MAP_READ); + size = QT_UINT32 (map.data); + GST_DEBUG_OBJECT (qtdemux, "size 0x%08" G_GINT64_MODIFIER "x", size); + + gst_buffer_unmap (tmp, &map); + gst_buffer_unref (tmp); + } + + /* Sanity check: catch bogus sizes (fuzzed/broken files) */ + if (G_UNLIKELY (size > QTDEMUX_MAX_ATOM_SIZE)) { + if (qtdemux->state != QTDEMUX_STATE_MOVIE && qtdemux->got_moov) { + /* we're pulling header but already got most interesting bits, + * so never mind the rest (e.g. tags) (that much) */ + GST_WARNING_OBJECT (qtdemux, "atom has bogus size %" G_GUINT64_FORMAT, + size); + return GST_FLOW_EOS; + } else { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is invalid and cannot be played.")), + ("atom has bogus size %" G_GUINT64_FORMAT, size)); + return GST_FLOW_ERROR; + } + } + + flow = gst_pad_pull_range (qtdemux->sinkpad, offset, size, buf); + + if (G_UNLIKELY (flow != GST_FLOW_OK)) + return flow; + + bsize = gst_buffer_get_size (*buf); + /* Catch short reads - we don't want any partial atoms */ + if (G_UNLIKELY (bsize < size)) { + GST_WARNING_OBJECT (qtdemux, + "short read: %" G_GSIZE_FORMAT " < %" G_GUINT64_FORMAT, bsize, size); + gst_buffer_unref (*buf); + *buf = NULL; + return GST_FLOW_EOS; + } + + return flow; +} + +#if 1 +static gboolean +gst_qtdemux_src_convert (GstQTDemux * qtdemux, GstPad * pad, + GstFormat src_format, gint64 src_value, GstFormat dest_format, + gint64 * dest_value) +{ + gboolean res = TRUE; + QtDemuxStream *stream = gst_pad_get_element_private (pad); + gint32 index; + + if (stream->subtype != FOURCC_vide) { + res = FALSE; + goto done; + } + + switch (src_format) { + case GST_FORMAT_TIME: + switch (dest_format) { + case GST_FORMAT_BYTES:{ + index = gst_qtdemux_find_index_linear (qtdemux, stream, src_value); + if (-1 == index) { + res = FALSE; + goto done; + } + + *dest_value = stream->samples[index].offset; + + GST_DEBUG_OBJECT (qtdemux, "Format Conversion Time->Offset :%" + GST_TIME_FORMAT "->%" G_GUINT64_FORMAT, + GST_TIME_ARGS (src_value), *dest_value); + break; + } + default: + res = FALSE; + break; + } + break; + case GST_FORMAT_BYTES: + switch (dest_format) { + case GST_FORMAT_TIME:{ + index = + gst_qtdemux_find_index_for_given_media_offset_linear (qtdemux, + stream, src_value); + + if (-1 == index) { + res = FALSE; + goto done; + } + + *dest_value = + QTSTREAMTIME_TO_GSTTIME (stream, + stream->samples[index].timestamp); + GST_DEBUG_OBJECT (qtdemux, + "Format Conversion Offset->Time :%" G_GUINT64_FORMAT "->%" + GST_TIME_FORMAT, src_value, GST_TIME_ARGS (*dest_value)); + break; + } + default: + res = FALSE; + break; + } + break; + default: + res = FALSE; + break; + } + +done: + return res; +} +#endif + +static gboolean +gst_qtdemux_get_duration (GstQTDemux * qtdemux, GstClockTime * duration) +{ + gboolean res = FALSE; + + *duration = GST_CLOCK_TIME_NONE; + + if (qtdemux->duration != 0 && + qtdemux->duration != G_MAXINT64 && qtdemux->timescale != 0) { + *duration = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration); + res = TRUE; + } else { + *duration = GST_CLOCK_TIME_NONE; + } + + return res; +} + +static gboolean +gst_qtdemux_handle_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean res = FALSE; + GstQTDemux *qtdemux = GST_QTDEMUX (parent); + + GST_LOG_OBJECT (pad, "%s query", GST_QUERY_TYPE_NAME (query)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION:{ + GstFormat fmt; + + gst_query_parse_position (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME + && GST_CLOCK_TIME_IS_VALID (qtdemux->segment.position)) { + gst_query_set_position (query, GST_FORMAT_TIME, + qtdemux->segment.position); + res = TRUE; + } + } + break; + case GST_QUERY_DURATION:{ + GstFormat fmt; + + gst_query_parse_duration (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME) { + /* First try to query upstream */ + res = gst_pad_query_default (pad, parent, query); + if (!res) { + GstClockTime duration; + if (gst_qtdemux_get_duration (qtdemux, &duration) && duration > 0) { + gst_query_set_duration (query, GST_FORMAT_TIME, duration); + res = TRUE; + } + } + } + break; + } + case GST_QUERY_CONVERT:{ + GstFormat src_fmt, dest_fmt; + gint64 src_value, dest_value = 0; + + gst_query_parse_convert (query, &src_fmt, &src_value, &dest_fmt, NULL); + + res = gst_qtdemux_src_convert (qtdemux, pad, + src_fmt, src_value, dest_fmt, &dest_value); + if (res) { + gst_query_set_convert (query, src_fmt, src_value, dest_fmt, dest_value); + res = TRUE; + } + break; + } + case GST_QUERY_FORMATS: + gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); + res = TRUE; + break; + case GST_QUERY_CUSTOM:{ + gboolean trickable = TRUE; + GstStructure *s; + + s = (GstStructure *) gst_query_get_structure (query); + + if (gst_structure_has_name (s, "custom-trickable")) { + if (!qtdemux->pullbased && qtdemux->isBigData) { + trickable = FALSE; + } else if (qtdemux->has_dolby_bl_cand && qtdemux->has_dolby_el_cand + && qtdemux->fragmented) { + trickable = FALSE; + } + + gst_structure_set (s, "trickable", G_TYPE_BOOLEAN, trickable, NULL); + res = TRUE; + break; + } else { + res = gst_pad_query_default (pad, parent, query); + break; + } +#if 0 //CID 89385 (#1 of 1): Structurally dead code (UNREACHABLE) + gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES); + res = TRUE; + break; +#endif + } + case GST_QUERY_SEEKING:{ + GstFormat fmt; + gboolean seekable; + + /* try upstream first */ + res = gst_pad_query_default (pad, parent, query); + + if (!res) { + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + if (fmt == GST_FORMAT_TIME) { + GstClockTime duration; + + gst_qtdemux_get_duration (qtdemux, &duration); + seekable = TRUE; + if (!qtdemux->pullbased) { + GstQuery *q; + + /* we might be able with help from upstream */ + seekable = FALSE; + q = gst_query_new_seeking (GST_FORMAT_BYTES); + if (gst_pad_peer_query (qtdemux->sinkpad, q)) { + gst_query_parse_seeking (q, &fmt, &seekable, NULL, NULL); + GST_LOG_OBJECT (qtdemux, "upstream BYTE seekable %d", seekable); + } + gst_query_unref (q); + } + gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, duration); + res = TRUE; + } + } + break; + } + case GST_QUERY_SEGMENT: + { + GstFormat format; + gint64 start, stop; + + format = qtdemux->segment.format; + + start = + gst_segment_to_stream_time (&qtdemux->segment, format, + qtdemux->segment.start); + if ((stop = qtdemux->segment.stop) == -1) + stop = qtdemux->segment.duration; + else + stop = gst_segment_to_stream_time (&qtdemux->segment, format, stop); + + gst_query_set_segment (query, qtdemux->segment.rate, format, start, stop); + res = TRUE; + break; + } + default: + res = gst_pad_query_default (pad, parent, query); + break; + } + + return res; +} + +static void +gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + if (G_LIKELY (stream->pad)) { + GST_DEBUG_OBJECT (qtdemux, "Checking pad %s:%s for tags", + GST_DEBUG_PAD_NAME (stream->pad)); + + if (G_UNLIKELY (stream->pending_tags)) { + GST_DEBUG_OBJECT (qtdemux, "Sending tags %" GST_PTR_FORMAT, + stream->pending_tags); + gst_pad_push_event (stream->pad, + gst_event_new_tag (stream->pending_tags)); + stream->pending_tags = NULL; + } + + if (G_UNLIKELY (stream->send_global_tags && qtdemux->tag_list)) { + GST_DEBUG_OBJECT (qtdemux, "Sending global tags %" GST_PTR_FORMAT, + qtdemux->tag_list); + gst_pad_push_event (stream->pad, + gst_event_new_tag (gst_tag_list_ref (qtdemux->tag_list))); + stream->send_global_tags = FALSE; + } + } +} + +/* push event on all source pads; takes ownership of the event */ +static void +gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event) +{ + guint n; + gboolean has_valid_stream = FALSE; + GstEventType etype = GST_EVENT_TYPE (event); + + GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads", + GST_EVENT_TYPE_NAME (event)); + + for (n = 0; n < qtdemux->n_streams; n++) { + GstPad *pad; + QtDemuxStream *stream = qtdemux->streams[n]; + GST_DEBUG_OBJECT (qtdemux, "pushing on pad %i", n); + + if ((pad = stream->pad)) { + has_valid_stream = TRUE; + + if (etype == GST_EVENT_EOS) { + /* let's not send twice */ + if (stream->sent_eos) + continue; + stream->sent_eos = TRUE; + } + + gst_pad_push_event (pad, gst_event_ref (event)); + } + } + + gst_event_unref (event); + + /* if it is EOS and there are no pads, post an error */ + if (!has_valid_stream && etype == GST_EVENT_EOS) { + gst_qtdemux_post_no_playable_stream_error (qtdemux); + } +} + +/* push a pending newsegment event, if any from the streaming thread */ +static void +gst_qtdemux_push_pending_newsegment (GstQTDemux * qtdemux) +{ + if (qtdemux->pending_newsegment) { + gst_qtdemux_push_event (qtdemux, qtdemux->pending_newsegment); + qtdemux->pending_newsegment = NULL; + } +} + +typedef struct +{ + guint64 media_time; +} FindData; + +static gint +find_func (QtDemuxSample * s1, gint64 * media_time, gpointer user_data) +{ + if ((gint64) s1->timestamp + s1->pts_offset > *media_time) + return 1; + if ((gint64) s1->timestamp + s1->pts_offset == *media_time) + return 0; + + return -1; +} + +/* find the index of the sample that includes the data for @media_time using a + * binary search. Only to be called in optimized cases of linear search below. + * + * Returns the index of the sample. + */ +static guint32 +gst_qtdemux_find_index (GstQTDemux * qtdemux, QtDemuxStream * str, + guint64 media_time) +{ + QtDemuxSample *result; + guint32 index; + + /* convert media_time to mov format */ + media_time = + gst_util_uint64_scale_ceil (media_time, str->timescale, GST_SECOND); + + result = gst_util_array_binary_search (str->samples, str->stbl_index + 1, + sizeof (QtDemuxSample), (GCompareDataFunc) find_func, + GST_SEARCH_MODE_BEFORE, &media_time, NULL); + + if (G_LIKELY (result)) + index = result - str->samples; + else + index = 0; + + return index; +} + + + +/* find the index of the sample that includes the data for @media_offset using a + * linear search + * + * Returns the index of the sample. + */ +static guint32 +gst_qtdemux_find_index_for_given_media_offset_linear (GstQTDemux * qtdemux, + QtDemuxStream * str, gint64 media_offset) +{ + QtDemuxSample *result = str->samples; + guint32 index = 0; + + if (result == NULL || str->n_samples == 0) + return -1; + + if (media_offset == result->offset) + return index; + + result++; + while (index < str->n_samples - 1) { + if (!qtdemux_parse_samples (qtdemux, str, index + 1)) + goto parse_failed; + + if (media_offset < result->offset) + break; + + index++; + result++; + } + return index; + + /* ERRORS */ +parse_failed: + { + GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", index + 1); + return -1; + } +} + +/* find the index of the sample that includes the data for @media_time using a + * linear search, and keeping in mind that not all samples may have been parsed + * yet. If possible, it will delegate to binary search. + * + * Returns the index of the sample. + */ +static guint32 +gst_qtdemux_find_index_linear (GstQTDemux * qtdemux, QtDemuxStream * str, + GstClockTime media_time) +{ + guint32 index = 0; + guint64 mov_time; + QtDemuxSample *sample; + + /* convert media_time to mov format */ + mov_time = + gst_util_uint64_scale_ceil (media_time, str->timescale, GST_SECOND); + + sample = str->samples; + if (mov_time == sample->timestamp + sample->pts_offset) + return index; + + /* use faster search if requested time in already parsed range */ + sample = str->samples + str->stbl_index; + if (str->stbl_index >= 0 && + mov_time <= (sample->timestamp + sample->pts_offset)) + return gst_qtdemux_find_index (qtdemux, str, media_time); + + while (index < str->n_samples - 1) { + if (!qtdemux_parse_samples (qtdemux, str, index + 1)) + goto parse_failed; + + sample = str->samples + index + 1; + if (mov_time < (sample->timestamp + sample->pts_offset)) + break; + + index++; + } + return index; + + /* ERRORS */ +parse_failed: + { + GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", index + 1); + return -1; + } +} + +/* find the index of the keyframe needed to decode the sample at @index + * of stream @str. + * + * Returns the index of the keyframe. + */ +static guint32 +gst_qtdemux_find_keyframe (GstQTDemux * qtdemux, QtDemuxStream * str, + guint32 index) +{ + guint32 new_index = index; + + if (index >= str->n_samples) { + new_index = str->n_samples; + goto beach; + } + + /* only one video key frame, return zero index */ + if (str->subtype == FOURCC_vide && str->n_sample_syncs < 2) { + new_index = 0; + goto beach; + } + + /* all keyframes, return index */ + if (str->all_keyframe) { + new_index = index; + goto beach; + } + + /* else go back until we have a keyframe */ + while (TRUE) { + if (str->samples[new_index].keyframe) + break; + + if (new_index == 0) + break; + + if (qtdemux->segment.rate > 2.0) + ++new_index; + else + new_index--; + } + +beach: + GST_DEBUG_OBJECT (qtdemux, "searching for keyframe index before index %u " + "gave %u", index, new_index); + + return new_index; +} + +/* find the segment for @time_position for @stream + * + * Returns the index of the segment containing @time_position. + * Returns the last segment and sets the @eos variable to TRUE + * if the time is beyond the end. @eos may be NULL + */ +static guint32 +gst_qtdemux_find_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstClockTime time_position) +{ + gint i; + guint32 seg_idx; + + GST_LOG_OBJECT (stream->pad, "finding segment for %" GST_TIME_FORMAT, + GST_TIME_ARGS (time_position)); + + seg_idx = -1; + for (i = 0; i < stream->n_segments; i++) { + QtDemuxSegment *segment = &stream->segments[i]; + + GST_LOG_OBJECT (stream->pad, + "looking at segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->time), GST_TIME_ARGS (segment->stop_time)); + + /* For the last segment we include stop_time in the last segment */ + if (i < stream->n_segments - 1) { + if (segment->time <= time_position && time_position < segment->stop_time) { + GST_LOG_OBJECT (stream->pad, "segment %d matches", i); + seg_idx = i; + break; + } + } else { + /* Last segment always matches */ + seg_idx = i; + break; + } + } + return seg_idx; +} + +/* move the stream @str to the sample position @index. + * + * Updates @str->sample_index and marks discontinuity if needed. + */ +static void +gst_qtdemux_move_stream (GstQTDemux * qtdemux, QtDemuxStream * str, + guint32 index) +{ + /* no change needed */ + if (index == str->sample_index) + return; + + GST_DEBUG_OBJECT (qtdemux, "moving to sample %u of %u", index, + str->n_samples); + + /* position changed, we have a discont */ + str->sample_index = index; + str->offset_in_sample = 0; + /* Each time we move in the stream we store the position where we are + * starting from */ + str->from_sample = index; + str->discont = TRUE; +} + +static void +gst_qtdemux_adjust_seek (GstQTDemux * qtdemux, gint64 desired_time, + gboolean use_sparse, gint64 * key_time, gint64 * key_offset) +{ + guint64 min_offset; + gint64 min_byte_offset = -1; + gint n; + + min_offset = desired_time; + + /* for each stream, find the index of the sample in the segment + * and move back to the previous keyframe. */ + for (n = 0; n < qtdemux->n_streams; n++) { + QtDemuxStream *str; + guint32 index, kindex = 0; + guint32 seg_idx; + GstClockTime media_start; + GstClockTime media_time; + GstClockTime seg_time; + QtDemuxSegment *seg; + gboolean empty_segment = FALSE; + + str = qtdemux->streams[n]; + + if (str->sparse && !use_sparse) + continue; + + if ((qtdemux->demux_rate > 2.0 || qtdemux->demux_rate == 0.5) + && strstr (GST_PAD_NAME (str->pad), "audio")) + continue; + + seg_idx = gst_qtdemux_find_segment (qtdemux, str, desired_time); + GST_DEBUG_OBJECT (qtdemux, "align segment %d", seg_idx); + + /* get segment and time in the segment */ + seg = &str->segments[seg_idx]; + seg_time = desired_time - seg->time; + + while (QTSEGMENT_IS_EMPTY (seg)) { + seg_time = 0; + empty_segment = TRUE; + GST_DEBUG_OBJECT (str->pad, "Segment %d is empty, moving to next one", + seg_idx); + seg_idx++; + if (seg_idx == str->n_segments) + break; + seg = &str->segments[seg_idx]; + } + + if (seg_idx == str->n_segments) { + /* FIXME track shouldn't have the last segment as empty, but if it + * happens we better handle it */ + continue; + } + + /* get the media time in the segment */ + media_start = seg->media_start + seg_time; + + /* get the index of the sample with media time */ + index = gst_qtdemux_find_index_linear (qtdemux, str, media_start); + GST_DEBUG_OBJECT (qtdemux, "sample for %" GST_TIME_FORMAT " at %u" + " at offset %" G_GUINT64_FORMAT " (empty segment: %d)", + GST_TIME_ARGS (media_start), index, str->samples[index].offset, + empty_segment); + + if (!empty_segment) { + /* find previous keyframe */ + kindex = gst_qtdemux_find_keyframe (qtdemux, str, index); + + /* if the keyframe is at a different position, we need to update the + * requested seek time */ + if (index != kindex) { + index = kindex; + + /* get timestamp of keyframe */ + media_time = QTSAMPLE_PTS (str, &str->samples[kindex]); + GST_DEBUG_OBJECT (qtdemux, + "keyframe at %u with time %" GST_TIME_FORMAT " at offset %" + G_GUINT64_FORMAT, kindex, GST_TIME_ARGS (media_time), + str->samples[kindex].offset); + + /* keyframes in the segment get a chance to change the + * desired_offset. keyframes out of the segment are + * ignored. */ + if (media_time >= seg->media_start) { + GstClockTime seg_time; + + /* this keyframe is inside the segment, convert back to + * segment time */ + seg_time = (media_time - seg->media_start) + seg->time; + if (seg_time < min_offset) + min_offset = seg_time; + } + } + } + + if (min_byte_offset < 0 || str->samples[index].offset < min_byte_offset) + min_byte_offset = str->samples[index].offset; + + /* if keyframe is at a start position, we need to change segment position */ + if (!kindex && !qtdemux->fragmented) + qtdemux->isStartKeyFrame = TRUE; + } + + if (key_time) + *key_time = min_offset; + if (key_offset) + *key_offset = min_byte_offset; +} + +static gboolean +gst_qtdemux_convert_seek (GstPad * pad, GstFormat * format, + GstSeekType cur_type, gint64 * cur, GstSeekType stop_type, gint64 * stop) +{ + gboolean res; + + g_return_val_if_fail (format != NULL, FALSE); + g_return_val_if_fail (cur != NULL, FALSE); + g_return_val_if_fail (stop != NULL, FALSE); + + if (*format == GST_FORMAT_TIME) + return TRUE; + + res = TRUE; + if (cur_type != GST_SEEK_TYPE_NONE) + res = gst_pad_query_convert (pad, *format, *cur, GST_FORMAT_TIME, cur); + if (res && stop_type != GST_SEEK_TYPE_NONE) + res = gst_pad_query_convert (pad, *format, *stop, GST_FORMAT_TIME, stop); + + if (res) + *format = GST_FORMAT_TIME; + + return res; +} + +/* perform seek in push based mode: + find BYTE position to move to based on time and delegate to upstream +*/ +static gboolean +gst_qtdemux_do_push_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop, key_cur; + gboolean res; + gint64 byte_cur; + gint64 original_stop; + guint32 seqnum; + GstSegment seeksegment; + + GST_DEBUG_OBJECT (qtdemux, "doing push-based seek"); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + if (qtdemux->dlna_opval == 0x00 && rate == 0.5) { + qtdemux->segment.rate = rate; + qtdemux->segment.start = qtdemux->segment.time = qtdemux->segment.position; + qtdemux->pending_newsegment = gst_event_new_segment (&(qtdemux->segment)); + gst_event_set_seqnum (qtdemux->pending_newsegment, + gst_event_get_seqnum (event)); + return TRUE; + } +#ifdef MP4_PUSHMODE_TRICK + qtdemux->demux_rate = rate; + qtdemux->rate_changed = TRUE; + qtdemux->segment.rate = rate; + /* Handles trick condition */ + if (rate == 0.5 || qtdemux_is_keyframe_trick_condition (qtdemux)) { + memcpy (&seeksegment, &qtdemux->segment, sizeof (GstSegment)); + gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, + stop_type, stop, FALSE); + memcpy (&qtdemux->segment, &seeksegment, sizeof (GstSegment)); + qtdemux->next_seek_offset = qtdemux->prev_seek_offset = qtdemux->offset; + } +#endif + + cur = qtdemux->segment.position = (qtdemux->segment.rate < 0.0) ? stop : cur; + /* convert to TIME if needed and possible */ + if (!gst_qtdemux_convert_seek (pad, &format, cur_type, &cur, + stop_type, &stop)) + goto no_format; + + /* Upstream seek in bytes will have undefined stop, but qtdemux stores + * the original stop position to use when upstream pushes the new segment + * for this seek */ + original_stop = stop; + stop = -1; + + /* find reasonable corresponding BYTE position, + * also try to mind about keyframes, since we can not go back a bit for them + * later on */ + gst_qtdemux_adjust_seek (qtdemux, cur, FALSE, &key_cur, &byte_cur); + qtdemux->trick_offset = (qtdemux->dlna_opval == 0x10) ? key_cur : byte_cur; + + if (byte_cur == -1) + goto abort_seek; + + GST_DEBUG_OBJECT (qtdemux, "Pushing BYTE seek rate %g, " + "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT, rate, byte_cur, + stop); + + format = (qtdemux->dlna_opval == 0x10) ? GST_FORMAT_TIME : GST_FORMAT_BYTES; + + GST_OBJECT_LOCK (qtdemux); + qtdemux->seek_offset = byte_cur; + if (!(flags & GST_SEEK_FLAG_KEY_UNIT)) { + qtdemux->push_seek_start = cur; + } else { + qtdemux->push_seek_start = key_cur; + } + + if (stop_type == GST_SEEK_TYPE_NONE) { + qtdemux->push_seek_stop = qtdemux->segment.stop; + } else { + qtdemux->push_seek_stop = original_stop; + } + GST_OBJECT_UNLOCK (qtdemux); + + event = + gst_event_new_seek (rate, format, flags, cur_type, qtdemux->trick_offset, + stop_type, stop); + gst_event_set_seqnum (event, seqnum); + res = gst_pad_push_event (qtdemux->sinkpad, event); + + return res; + + /* ERRORS */ +abort_seek: + { + GST_DEBUG_OBJECT (qtdemux, "could not determine byte position to seek to, " + "seek aborted."); + return FALSE; + } +no_format: + { + GST_DEBUG_OBJECT (qtdemux, "unsupported format given, seek aborted."); + return FALSE; + } +} + +#ifdef MP4_PUSHMODE_TRICK +/* This function based on Byte or Time Format creates seek event +for respective start and stop values */ +static gboolean +gst_qtdemux_seek_push (GstQTDemux * demux, guint64 start, guint64 stop) +{ + GstEvent *seek_event; + gboolean ret = FALSE; + + GstFormat format; + format = (demux->dlna_opval == 0x10) ? GST_FORMAT_TIME : GST_FORMAT_BYTES; + seek_event = + gst_event_new_seek (1.0, format, + GST_SEEK_FLAG_SKIP | GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, start, + GST_SEEK_TYPE_NONE, stop); + ret = gst_pad_push_event (demux->sinkpad, seek_event); + return ret; +} + +/* This function is called from chain function of qtdemux to generate seek events +based on conditions of trick play */ +static gboolean +gst_qtdemux_handle_trick (GstQTDemux * demux) +{ + gint64 offset = 0; + gint64 time_offset = 0; + guint64 start, stop; + gboolean ret = TRUE; + demux->rate_changed = FALSE; + if (demux->segment.rate < 0.0) { + demux->prev_seek_offset = demux->trick_offset; + /*Handling cases where prev_seek_offset & rewind_offset becomes + equal before reaching start of file */ + if (demux->prev_segment_position == demux->segment.position) + demux->segment.position = demux->segment.position - TIME_ADJUST; + + if (demux->prev_seek_offset != G_MAXUINT64 && + demux->segment.position > (guint64) TIME_ADJUST) { + /*Time adjust using next seek offset in Trick forward time */ + if (demux->dlna_opval == 0x10) { + demux->prev_seek_offset = demux->next_seek_offset; + gst_qtdemux_adjust_seek (demux, demux->segment.position - TIME_ADJUST, + FALSE, &time_offset, &offset); + demux->next_seek_offset = offset; + } else + gst_qtdemux_adjust_seek (demux, demux->segment.position - TIME_ADJUST, + FALSE, &time_offset, &offset); + demux->trick_offset = offset; + } else + demux->trick_offset = G_MAXUINT64; + demux->prev_segment_position = demux->segment.position; + if (demux->trick_offset > 0 && demux->trick_offset != G_MAXUINT64) { + start = (demux->dlna_opval == 0x10) ? time_offset : offset; + stop = -1; + ret = gst_qtdemux_seek_push (demux, start, stop); + } else { + gst_qtdemux_push_event (demux, gst_event_new_eos ()); + return TRUE; + } + } else { + demux->next_seek_offset = demux->trick_offset; + if (demux->next_seek_offset != G_MAXUINT64) { + /*Time adjust using next seek offset in Trick forward time */ + if (demux->dlna_opval == 0x10) { + demux->next_seek_offset = demux->prev_seek_offset; + gst_qtdemux_adjust_seek (demux, demux->segment.position + TIME_ADJUST, + FALSE, &time_offset, &offset); + demux->prev_seek_offset = offset; + } else + gst_qtdemux_adjust_seek (demux, demux->segment.position + TIME_ADJUST, + FALSE, &time_offset, &offset); + } else + demux->trick_offset = G_MAXUINT64; + demux->trick_offset = (demux->dlna_opval == 0x10) ? time_offset : offset; + if (demux->trick_offset != demux->next_seek_offset + && demux->trick_offset != G_MAXUINT64) { + start = demux->trick_offset; + stop = -1; + ret = gst_qtdemux_seek_push (demux, start, stop); + } + } + return ret; +} +#endif + +/* + perform the seek. + * + * We set all segment_indexes in the streams to unknown and + * adjust the time_position to the desired position. this is enough + * to trigger a segment switch in the streaming thread to start + * streaming from the desired position. + * + * Keyframe seeking is a little more complicated when dealing with + * segments. Ideally we want to move to the previous keyframe in + * the segment but there might not be a keyframe in the segment. In + * fact, none of the segments could contain a keyframe. We take a + * practical approach: seek to the previous keyframe in the segment, + * if there is none, seek to the beginning of the segment. + * + * Called with STREAM_LOCK + */ +static gboolean +gst_qtdemux_perform_seek (GstQTDemux * qtdemux, GstSegment * segment, + guint32 seqnum, GstSeekFlags flags) +{ + gint64 desired_offset; + gint n; + + desired_offset = segment->position; + + GST_DEBUG_OBJECT (qtdemux, "seeking to %" GST_TIME_FORMAT, + GST_TIME_ARGS (desired_offset)); + + /* may not have enough fragmented info to do this adjustment, + * and we can't scan (and probably should not) at this time with + * possibly flushing upstream */ + if (((flags & GST_SEEK_FLAG_KEY_UNIT) && !qtdemux->fragmented) + || qtdemux->is_dolby_hdr) { + gint64 min_offset; + + gst_qtdemux_adjust_seek (qtdemux, desired_offset, TRUE, &min_offset, NULL); + GST_DEBUG_OBJECT (qtdemux, "keyframe seek, align to %" + GST_TIME_FORMAT, GST_TIME_ARGS (min_offset)); + desired_offset = min_offset; + } + + if (qtdemux->isStartKeyFrame) { + GST_DEBUG_OBJECT (qtdemux, + "keyframe is at a start position, so we need to update segment information."); + desired_offset = segment->position = segment->start = segment->time = 0; + qtdemux->isStartKeyFrame = FALSE; + } + + /* and set all streams to the final position */ + gst_flow_combiner_reset (qtdemux->flowcombiner); + qtdemux->segment_seqnum = seqnum; + for (n = 0; n < qtdemux->n_streams; n++) { + QtDemuxStream *stream = qtdemux->streams[n]; + + stream->time_position = desired_offset; + stream->accumulated_base = 0; + stream->sample_index = -1; + stream->offset_in_sample = 0; + stream->segment_index = -1; + stream->sent_eos = FALSE; + + if (segment->flags & GST_SEEK_FLAG_FLUSH) + gst_segment_init (&stream->segment, GST_FORMAT_TIME); + } + segment->position = desired_offset; + segment->time = desired_offset; + if (segment->rate >= 0) { + segment->start = desired_offset; + + /* we stop at the end */ + if (segment->stop == -1) + segment->stop = segment->duration; + } else { + segment->stop = desired_offset; + } + + if (qtdemux->fragmented) + qtdemux->fragmented_seek_pending = TRUE; + + return TRUE; +} + +/* do a seek in pull based mode */ +static gboolean +gst_qtdemux_do_seek (GstQTDemux * qtdemux, GstPad * pad, GstEvent * event) +{ + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + gboolean flush; + gboolean update; + GstSegment seeksegment; + guint32 seqnum = 0; + GstEvent *flush_event; + + if (event) { + GST_DEBUG_OBJECT (qtdemux, "doing seek with event"); + + gst_event_parse_seek (event, &rate, &format, &flags, + &cur_type, &cur, &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + /* we have to have a format as the segment format. Try to convert + * if not. */ + if (!gst_qtdemux_convert_seek (pad, &format, cur_type, &cur, + stop_type, &stop)) + goto no_format; + + GST_DEBUG_OBJECT (qtdemux, "seek format %s", gst_format_get_name (format)); + } else { + GST_DEBUG_OBJECT (qtdemux, "doing seek without event"); + flags = 0; + } + + flush = flags & GST_SEEK_FLAG_FLUSH; + + /* stop streaming, either by flushing or by pausing the task */ + if (flush) { + flush_event = gst_event_new_flush_start (); + if (seqnum) + gst_event_set_seqnum (flush_event, seqnum); + /* unlock upstream pull_range */ + gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event)); + /* make sure out loop function exits */ + gst_qtdemux_push_event (qtdemux, flush_event); + } else { + /* non flushing seek, pause the task */ + gst_pad_pause_task (qtdemux->sinkpad); + } + + /* wait for streaming to finish */ + GST_PAD_STREAM_LOCK (qtdemux->sinkpad); + + /* copy segment, we need this because we still need the old + * segment when we close the current segment. */ + memcpy (&seeksegment, &qtdemux->segment, sizeof (GstSegment)); + + if (event) { + /* configure the segment with the seek variables */ + GST_DEBUG_OBJECT (qtdemux, "configuring seek"); + gst_segment_do_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); + } + + /* now do the seek, this actually never returns FALSE */ + gst_qtdemux_perform_seek (qtdemux, &seeksegment, seqnum, flags); + + /* prepare for streaming again */ + if (flush) { + flush_event = gst_event_new_flush_stop (TRUE); + if (seqnum) + gst_event_set_seqnum (flush_event, seqnum); + + gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (flush_event)); + gst_qtdemux_push_event (qtdemux, flush_event); + } + + /* commit the new segment */ + memcpy (&qtdemux->segment, &seeksegment, sizeof (GstSegment)); + + if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) { + GstMessage *msg = gst_message_new_segment_start (GST_OBJECT_CAST (qtdemux), + qtdemux->segment.format, qtdemux->segment.position); + if (seqnum) + gst_message_set_seqnum (msg, seqnum); + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), msg); + } + + /* restart streaming, NEWSEGMENT will be sent from the streaming thread. */ + gst_pad_start_task (qtdemux->sinkpad, (GstTaskFunction) gst_qtdemux_loop, + qtdemux->sinkpad, NULL); + + GST_PAD_STREAM_UNLOCK (qtdemux->sinkpad); + + return TRUE; + + /* ERRORS */ +no_format: + { + GST_DEBUG_OBJECT (qtdemux, "unsupported format given, seek aborted."); + return FALSE; + } +} + +static gboolean +qtdemux_ensure_index (GstQTDemux * qtdemux) +{ + guint i; + + GST_DEBUG_OBJECT (qtdemux, "collecting all metadata for all streams"); + + /* Build complete index */ + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream = qtdemux->streams[i]; + + if (!qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) + goto parse_error; + } + return TRUE; + + /* ERRORS */ +parse_error: + { + GST_LOG_OBJECT (qtdemux, + "Building complete index of stream %u for seeking failed!", i); + return FALSE; + } +} + +static gboolean +gst_qtdemux_handle_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res = TRUE; + GstQTDemux *qtdemux = GST_QTDEMUX (parent); + gdouble rate = 0.0; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { +#ifndef GST_DISABLE_GST_DEBUG + GstClockTime ts = gst_util_get_timestamp (); +#endif + + if (qtdemux->upstream_format_is_time && qtdemux->fragmented) { + /* seek should be handled by upstream, we might need to re-download fragments */ + GST_DEBUG_OBJECT (qtdemux, + "let upstream handle seek for fragmented playback"); + goto upstream; + } + + /* Build complete index for seeking; + * if not a fragmented file at least */ + if (!qtdemux->fragmented) + if (!qtdemux_ensure_index (qtdemux)) + goto index_failed; +#ifndef GST_DISABLE_GST_DEBUG + ts = gst_util_get_timestamp () - ts; + GST_INFO_OBJECT (qtdemux, + "Time taken to parse index %" GST_TIME_FORMAT, GST_TIME_ARGS (ts)); +#endif + } + gst_event_parse_seek (event, &rate, NULL, NULL, NULL, NULL, NULL, NULL); + + if (qtdemux->fragmented && rate < 0.0) { + guint i; + gboolean has_ra_entries = FALSE; + QtDemuxStream *stream; + for (i = 0; i < qtdemux->n_streams; i++) { + stream = qtdemux->streams[i]; + if (stream->ra_entries) { + has_ra_entries = TRUE; + break; + } + } + /* If there is no tfra, we cannot do REW */ + if (!has_ra_entries) { + res = FALSE; + gst_event_unref (event); + GST_DEBUG_OBJECT (qtdemux, + "Cannot do rewind for fragmented file without tfra"); + goto done; + } + } + + if (qtdemux->pullbased) { + res = gst_qtdemux_do_seek (qtdemux, pad, event); + } else if (qtdemux->state == QTDEMUX_STATE_MOVIE && qtdemux->n_streams + && !qtdemux->fragmented) { + res = gst_qtdemux_do_push_seek (qtdemux, pad, event); + } else if ((rate == 1) && + gst_pad_push_event (qtdemux->sinkpad, gst_event_ref (event))) { + GST_DEBUG_OBJECT (qtdemux, "Upstream successfully seeked"); + res = TRUE; + } else { + GST_DEBUG_OBJECT (qtdemux, + "ignoring seek in push mode in current state"); + res = FALSE; + } + gst_event_unref (event); + break; + case GST_EVENT_QOS: + case GST_EVENT_NAVIGATION: + res = FALSE; + gst_event_unref (event); + break; + default: + upstream: + res = gst_pad_event_default (pad, parent, event); + break; + } + +done: + return res; + + /* ERRORS */ +index_failed: + { + GST_ERROR_OBJECT (qtdemux, "Index failed"); + gst_event_unref (event); + res = FALSE; + goto done; + } +} + +/* stream/index return sample that is min/max w.r.t. byte position, + * time is min/max w.r.t. time of samples, + * the latter need not be time of the former sample */ +static void +gst_qtdemux_find_sample (GstQTDemux * qtdemux, gint64 byte_pos, gboolean fw, + gboolean set, QtDemuxStream ** _stream, gint * _index, gint64 * _time) +{ + gint i, n, index; + gint64 time, min_time; + QtDemuxStream *stream; + + min_time = -1; + stream = NULL; + index = -1; + + for (n = 0; n < qtdemux->n_streams; ++n) { + QtDemuxStream *str; + gint inc; + gboolean set_sample; + + str = qtdemux->streams[n]; + set_sample = !set; + + if (fw) { + i = 0; + inc = 1; + } else { + i = str->n_samples - 1; + inc = -1; + } + + for (; (i >= 0) && (i < str->n_samples); i += inc) { + if (str->samples[i].size == 0) + continue; + + if (fw && (str->samples[i].offset < byte_pos)) + continue; + + if (!fw && (str->samples[i].offset + str->samples[i].size > byte_pos)) + continue; + + /* move stream to first available sample */ + if (set) { + gst_qtdemux_move_stream (qtdemux, str, i); + set_sample = TRUE; + } + + /* avoid index from sparse streams since they might be far away */ + if (!str->sparse) { + /* determine min/max time */ + time = QTSAMPLE_PTS (str, &str->samples[i]); + if ((min_time == -1 || (!fw && time > min_time) || + (fw && time < min_time)) && str->samples[i].keyframe) { + min_time = time; + } + + /* determine stream with leading sample, to get its position */ + if (!stream || + (fw && (str->samples[i].offset < stream->samples[index].offset)) || + (!fw && (str->samples[i].offset > stream->samples[index].offset))) { + stream = str; + index = i; + } + + /* add a condition to fix keyframe problem in sample of stream + * for some sample which has not keyframe */ + if (min_time < 0) { + GST_DEBUG_OBJECT (qtdemux, "Setting min time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (time)); + min_time = time; + } + } + break; + } + + /* no sample for this stream, mark eos */ + if (!set_sample) + gst_qtdemux_move_stream (qtdemux, str, str->n_samples); + } + + if (_time) + *_time = min_time; + if (_stream) + *_stream = stream; + if (_index) + *_index = index; +} + +static QtDemuxStream * +_create_stream (void) +{ + QtDemuxStream *stream; + + stream = g_new0 (QtDemuxStream, 1); + /* new streams always need a discont */ + stream->discont = TRUE; + /* we enable clipping for raw audio/video streams */ + stream->need_clip = FALSE; + stream->need_process = FALSE; + stream->segment_index = -1; + stream->time_position = 0; + stream->sample_index = -1; + stream->offset_in_sample = 0; + stream->new_stream = TRUE; + stream->multiview_mode = GST_VIDEO_MULTIVIEW_MODE_NONE; + stream->multiview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE; + stream->protected = FALSE; + stream->protection_scheme_type = 0; + stream->protection_scheme_version = 0; + stream->protection_scheme_info = NULL; + stream->n_samples_moof = 0; + stream->duration_moof = 0; + g_queue_init (&stream->protection_scheme_event_queue); + return stream; +} + +static gboolean +gst_qtdemux_setcaps (GstQTDemux * demux, GstCaps * caps) +{ + GstStructure *structure; + const gchar *variant; + const GstCaps *mediacaps = NULL; + gint highest_temporal_id = -1; + + GST_DEBUG_OBJECT (demux, "Sink set caps: %" GST_PTR_FORMAT, caps); + + structure = gst_caps_get_structure (caps, 0); + variant = gst_structure_get_string (structure, "variant"); + gst_structure_get_int (structure, "highest-temporal-id", + &highest_temporal_id); + + if (highest_temporal_id != -1) { + GST_DEBUG_OBJECT (demux, "Seeing highest-temporal-id: %d", + highest_temporal_id); + demux->highest_temporal_id = highest_temporal_id; + } + + if (variant && strcmp (variant, "mss-fragmented") == 0) { + QtDemuxStream *stream; + const GValue *value; + + demux->fragmented = TRUE; + demux->mss_mode = TRUE; + + if (demux->n_streams > 1) { + /* can't do this, we can only renegotiate for another mss format */ + return FALSE; + } + + value = gst_structure_get_value (structure, "media-caps"); + /* create stream */ + if (value) { + const GValue *timescale_v; + + /* TODO update when stream changes during playback */ + + if (demux->n_streams == 0) { + stream = _create_stream (); + demux->streams[demux->n_streams] = stream; + demux->n_streams = 1; + } else { + stream = demux->streams[0]; + } + + timescale_v = gst_structure_get_value (structure, "timescale"); + if (timescale_v) { + stream->timescale = g_value_get_uint64 (timescale_v); + } else { + /* default mss timescale */ + stream->timescale = 10000000; + } + demux->timescale = stream->timescale; + + mediacaps = gst_value_get_caps (value); + if (!stream->caps || !gst_caps_is_equal_fixed (mediacaps, stream->caps)) { + GST_DEBUG_OBJECT (demux, "We have a new caps %" GST_PTR_FORMAT, + mediacaps); + stream->new_caps = TRUE; + } + gst_caps_replace (&stream->caps, (GstCaps *) mediacaps); + structure = gst_caps_get_structure (mediacaps, 0); + if (g_str_has_prefix (gst_structure_get_name (structure), "video")) { + stream->subtype = FOURCC_vide; + + gst_structure_get_int (structure, "width", &stream->width); + gst_structure_get_int (structure, "height", &stream->height); + gst_structure_get_fraction (structure, "framerate", &stream->fps_n, + &stream->fps_d); + } else if (g_str_has_prefix (gst_structure_get_name (structure), "audio")) { + gint rate = 0; + stream->subtype = FOURCC_soun; + gst_structure_get_int (structure, "channels", &stream->n_channels); + gst_structure_get_int (structure, "rate", &rate); + stream->rate = rate; + } + } + gst_caps_replace (&demux->media_caps, (GstCaps *) mediacaps); + } else { + demux->mss_mode = FALSE; + } + + return TRUE; +} + +static void +gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard) +{ + gint n; + + GST_DEBUG_OBJECT (qtdemux, "Resetting demux"); + gst_pad_stop_task (qtdemux->sinkpad); + +#ifdef MP4_PUSHMODE_TRICK + qtdemux->pushed_Iframe = FALSE; + qtdemux->pushed_Audio = FALSE; + qtdemux->all_audio_pushed = FALSE; + qtdemux->prev_seek_offset = 0; +#endif + + if (hard || qtdemux->upstream_format_is_time) { + qtdemux->state = QTDEMUX_STATE_INITIAL; + qtdemux->neededbytes = 16; + qtdemux->todrop = 0; + qtdemux->pullbased = FALSE; + qtdemux->posted_redirect = FALSE; + qtdemux->first_mdat = -1; + qtdemux->header_size = 0; + qtdemux->mdatoffset = -1; + qtdemux->restoredata_offset = -1; + if (qtdemux->mdatbuffer) + gst_buffer_unref (qtdemux->mdatbuffer); + if (qtdemux->restoredata_buffer) + gst_buffer_unref (qtdemux->restoredata_buffer); + qtdemux->mdatbuffer = NULL; + qtdemux->restoredata_buffer = NULL; + qtdemux->mdatleft = 0; + if (qtdemux->comp_brands) + gst_buffer_unref (qtdemux->comp_brands); + qtdemux->comp_brands = NULL; + qtdemux->last_moov_offset = -1; + if (qtdemux->moov_node) + g_node_destroy (qtdemux->moov_node); + qtdemux->moov_node = NULL; + qtdemux->moov_node_compressed = NULL; + if (qtdemux->tag_list) + gst_mini_object_unref (GST_MINI_OBJECT_CAST (qtdemux->tag_list)); + qtdemux->tag_list = NULL; +#if 0 + if (qtdemux->element_index) + gst_object_unref (qtdemux->element_index); + qtdemux->element_index = NULL; +#endif + qtdemux->major_brand = 0; + if (qtdemux->pending_newsegment) + gst_event_unref (qtdemux->pending_newsegment); + qtdemux->pending_newsegment = NULL; + qtdemux->upstream_format_is_time = FALSE; + qtdemux->upstream_seekable = FALSE; + qtdemux->upstream_size = 0; + + qtdemux->fragment_start = -1; + qtdemux->fragment_start_offset = -1; + qtdemux->duration = 0; + qtdemux->moof_offset = 0; + qtdemux->chapters_track_id = 0; + qtdemux->have_group_id = FALSE; + qtdemux->group_id = G_MAXUINT; + + g_queue_foreach (&qtdemux->protection_event_queue, (GFunc) gst_event_unref, + NULL); + g_queue_clear (&qtdemux->protection_event_queue); +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + qtdemux->dash_pts_offset = GST_CLOCK_STIME_NONE; + qtdemux->dash_fragment_start = -1; + qtdemux->dash_segment_start = GST_CLOCK_TIME_NONE; + qtdemux->dash_period_start = 0; +#endif +#ifdef ATSC3_MODE + qtdemux->prev_decode_time = GST_CLOCK_TIME_NONE; +#endif + } + qtdemux->offset = 0; + qtdemux->mpu_offset = 0; + gst_adapter_clear (qtdemux->adapter); + gst_segment_init (&qtdemux->segment, GST_FORMAT_TIME); + qtdemux->segment_seqnum = 0; + + qtdemux->highest_temporal_id = -1; + + if (hard) { + for (n = 0; n < qtdemux->n_streams; n++) { + gst_qtdemux_stream_free (qtdemux, qtdemux->streams[n]); + qtdemux->streams[n] = NULL; + } + if (qtdemux->collection) { + gst_object_unref (qtdemux->collection); + qtdemux->collection = NULL; + } + qtdemux->n_streams = 0; + qtdemux->n_video_streams = 0; + qtdemux->n_audio_streams = 0; + qtdemux->n_sub_streams = 0; + qtdemux->exposed = FALSE; + qtdemux->fragmented = FALSE; + qtdemux->mss_mode = FALSE; + gst_caps_replace (&qtdemux->media_caps, NULL); + qtdemux->timescale = 0; + qtdemux->got_moov = FALSE; + qtdemux->new_collection = FALSE; + if (qtdemux->protection_system_ids) { + g_ptr_array_free (qtdemux->protection_system_ids, TRUE); + qtdemux->protection_system_ids = NULL; + } + qtdemux->upstream_basetime = GST_CLOCK_TIME_NONE; + qtdemux->upstream_basetime_offset = GST_CLOCK_TIME_NONE; + qtdemux->mpu_seq_num = G_MAXUINT32; + g_free (qtdemux->asset_id); + qtdemux->has_mmth = FALSE; + qtdemux->is_mmth_timed = TRUE; + qtdemux->ignore_hintsample = FALSE; + qtdemux->configure_dvr = FALSE; + } else if (qtdemux->mss_mode) { + gst_flow_combiner_reset (qtdemux->flowcombiner); + for (n = 0; n < qtdemux->n_streams; n++) + gst_qtdemux_stream_clear (qtdemux, qtdemux->streams[n]); + } else { + gst_flow_combiner_reset (qtdemux->flowcombiner); + for (n = 0; n < qtdemux->n_streams; n++) { + qtdemux->streams[n]->sent_eos = FALSE; + qtdemux->streams[n]->time_position = 0; + qtdemux->streams[n]->accumulated_base = 0; + } + if (!qtdemux->pending_newsegment) { + qtdemux->pending_newsegment = gst_event_new_segment (&qtdemux->segment); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (qtdemux->pending_newsegment, + qtdemux->segment_seqnum); + } + } +} + + +/* Maps the @segment to the qt edts internal segments and pushes + * the correspnding segment event. + * + * If it ends up being at a empty segment, a gap will be pushed and the next + * edts segment will be activated in sequence. + * + * To be used in push-mode only */ +static void +gst_qtdemux_map_and_push_segments (GstQTDemux * qtdemux, GstSegment * segment) +{ + gint n, i; + + for (n = 0; n < qtdemux->n_streams; n++) { + QtDemuxStream *stream = qtdemux->streams[n]; + + stream->time_position = segment->start; + if (segment->rate < 0) + stream->time_position = segment->stop; + + /* in push mode we should be guaranteed that we will have empty segments + * at the beginning and then one segment after, other scenarios are not + * supported and are discarded when parsing the edts */ + for (i = 0; i < stream->n_segments; i++) { + if (stream->segments[i].stop_time > segment->start) { + gst_qtdemux_activate_segment (qtdemux, stream, i, + stream->time_position); + if (QTSEGMENT_IS_EMPTY (&stream->segments[i])) { + /* push the empty segment and move to the next one */ + if (stream->segment.rate > 0.0) + gst_qtdemux_send_gap_for_segment (qtdemux, stream, i, + stream->time_position); + + /* accumulate previous segments */ + if (GST_CLOCK_TIME_IS_VALID (stream->segment.stop)) + stream->accumulated_base += + (stream->segment.stop - + stream->segment.start) / ABS (stream->segment.rate); + + continue; + } + + g_assert (i == stream->n_segments - 1); + } + } + } +} + +static gboolean +gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + GstEvent * event) +{ + GstQTDemux *demux = GST_QTDEMUX (parent); + gboolean res = TRUE; + + GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + { + gint64 offset = 0; + QtDemuxStream *stream; + gint idx; + GstSegment segment; + +#ifdef MP4_PUSHMODE_TRICK + demux->segment_event_recvd = TRUE; +#endif + + /* some debug output */ + gst_event_copy_segment (event, &segment); + GST_DEBUG_OBJECT (demux, "received newsegment %" GST_SEGMENT_FORMAT, + &segment); + + /* erase any previously set segment */ + gst_event_replace (&demux->pending_newsegment, NULL); + + if (segment.format == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (demux, "new pending_newsegment"); + gst_event_replace (&demux->pending_newsegment, event); + demux->upstream_format_is_time = TRUE; + } else { + GST_DEBUG_OBJECT (demux, "Not storing upstream newsegment, " + "not in time format"); + + /* chain will send initial newsegment after pads have been added */ + if (demux->state != QTDEMUX_STATE_MOVIE || !demux->n_streams) { + GST_DEBUG_OBJECT (demux, "still starting, eating event"); + goto exit; + } + } + + /* check if this matches a time seek we received previously + * FIXME for backwards compatibility reasons we use the + * seek_offset here to compare. In the future we might want to + * change this to use the seqnum as it uniquely should identify + * the segment that corresponds to the seek. */ + GST_DEBUG_OBJECT (demux, "Stored seek offset: %" G_GINT64_FORMAT + ", received segment offset %" G_GINT64_FORMAT, + demux->seek_offset, segment.start); + if (segment.format == GST_FORMAT_BYTES + && demux->seek_offset == segment.start) { + GST_OBJECT_LOCK (demux); + offset = segment.start; + + segment.start = demux->push_seek_start; + + GST_DEBUG_OBJECT (demux, "Replaced segment with stored seek " + "segment %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment.start), GST_TIME_ARGS (segment.stop)); + GST_OBJECT_UNLOCK (demux); + } + + /* we only expect a BYTE segment, e.g. following a seek */ + if (segment.format == GST_FORMAT_BYTES) { + if (GST_CLOCK_TIME_IS_VALID (segment.start)) { + offset = segment.start; + + gst_qtdemux_find_sample (demux, segment.start, TRUE, FALSE, NULL, + NULL, (gint64 *) & segment.start); + if ((gint64) segment.start < 0) + segment.start = 0; + } + if (GST_CLOCK_TIME_IS_VALID (segment.stop)) { + gst_qtdemux_find_sample (demux, segment.stop, FALSE, FALSE, NULL, + NULL, (gint64 *) & segment.stop); + /* keyframe seeking should already arrange for start >= stop, + * but make sure in other rare cases */ + segment.stop = MAX (segment.stop, segment.start); + } + } else if (segment.format == GST_FORMAT_TIME) { + /* push all data on the adapter before starting this + * new segment */ + gst_qtdemux_process_adapter (demux, TRUE); + + if (demux->dlna_opval == 0x10) { + GST_DEBUG_OBJECT (demux, "Server in Time seek mode"); + offset = segment.base; + } + } else { + GST_DEBUG_OBJECT (demux, "unsupported segment format, ignoring"); + goto exit; + } + + /* We shouldn't modify upstream driven TIME FORMAT segment */ + if (!demux->upstream_format_is_time) { + /* accept upstream's notion of segment and distribute along */ + segment.format = GST_FORMAT_TIME; + segment.position = segment.time = segment.start; + segment.duration = demux->segment.duration; + segment.base = gst_segment_to_running_time (&demux->segment, + GST_FORMAT_TIME, demux->segment.position); + } +#ifdef MP4_PUSHMODE_TRICK + if (demux->demux_rate < 0.0) { + segment.stop = segment.start; + segment.start = segment.time = 0; + segment.position = segment.stop; + } + segment.rate = demux->demux_rate; +#endif + gst_segment_copy_into (&segment, &demux->segment); + GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); + + /* map segment to internal qt segments and push on each stream */ + if (demux->rate_changed && demux->n_streams) { + if (demux->fragmented) { + GstEvent *segment_event = gst_event_new_segment (&segment); + + gst_event_replace (&demux->pending_newsegment, NULL); + gst_event_set_seqnum (segment_event, demux->segment_seqnum); + gst_qtdemux_push_event (demux, segment_event); + } else { + gst_event_replace (&demux->pending_newsegment, NULL); + gst_qtdemux_map_and_push_segments (demux, &segment); + } + } + /* clear leftover in current segment, if any */ + gst_adapter_clear (demux->adapter); + + /* set up streaming thread */ + demux->offset = offset; + if (demux->upstream_format_is_time) { + GST_DEBUG_OBJECT (demux, "Upstream is driving in time format, " + "set values to restart reading from a new atom"); + demux->neededbytes = 16; + demux->todrop = 0; + } else { + gst_qtdemux_find_sample (demux, offset, TRUE, TRUE, &stream, &idx, + NULL); + if (stream) { + demux->todrop = stream->samples[idx].offset - offset; + demux->neededbytes = demux->todrop + stream->samples[idx].size; + } else { + /* set up for EOS */ + demux->neededbytes = -1; + demux->todrop = 0; + } + } + exit: + gst_event_unref (event); + res = TRUE; + goto drop; + } + case GST_EVENT_FLUSH_START: + { + if (gst_event_get_seqnum (event) == demux->offset_seek_seqnum) { + gst_event_unref (event); + goto drop; + } +#ifdef MP4_PUSHMODE_TRICK + demux->demux_rate = demux->segment.rate; + demux->segment_event_recvd = FALSE; + + if ((demux->segment.rate < 0.0 || demux->segment.rate > 2.0 + || demux->segment.rate == 0.5) && !demux->rate_changed + && !demux->pullbased) { + res = TRUE; + goto drop; + } else { + QTDEMUX_EXPOSE_LOCK (demux); + gst_qtdemux_push_event (demux, event); + QTDEMUX_EXPOSE_UNLOCK (demux); + res = TRUE; + goto drop; + } +#endif + break; + } + case GST_EVENT_FLUSH_STOP: + { +#ifdef ATSC3_MODE + demux->prev_decode_time = GST_CLOCK_TIME_NONE; +#endif + +#ifdef MP4_PUSHMODE_TRICK + if ((demux->segment.rate < 0.0 || demux->segment.rate > 2.0 + || demux->segment.rate == 0.5) && (!demux->rate_changed) + && (!demux->pullbased)) { + gst_adapter_clear (demux->adapter); + goto drop; + } else { +#endif + guint64 dur; + + dur = demux->segment.duration; + gst_qtdemux_reset (demux, FALSE); + + demux->segment.duration = dur; + + if (gst_event_get_seqnum (event) == demux->offset_seek_seqnum) { + gst_event_unref (event); + goto drop; + } +#ifdef MP4_PUSHMODE_TRICK + } +#endif + break; + } + case GST_EVENT_EOS: + /* If we are in push mode, and get an EOS before we've seen any streams, + * then error out - we have nowhere to send the EOS */ + if (!demux->pullbased) { + gint i; + gboolean has_valid_stream = FALSE; + for (i = 0; i < demux->n_streams; i++) { + if (demux->streams[i]->pad != NULL) { + has_valid_stream = TRUE; + break; + } + } + if (!has_valid_stream) + gst_qtdemux_post_no_playable_stream_error (demux); + else { + GST_DEBUG_OBJECT (demux, "Data still available after EOS: %u", + (guint) gst_adapter_available (demux->adapter)); + if (gst_qtdemux_process_adapter (demux, TRUE) != GST_FLOW_OK) { + res = FALSE; + } + } + } + break; + case GST_EVENT_CAPS:{ + GstCaps *caps = NULL; + + gst_event_parse_caps (event, &caps); + gst_qtdemux_setcaps (demux, caps); + res = TRUE; + gst_event_unref (event); + goto drop; + } + case GST_EVENT_PROTECTION: + { + const gchar *system_id = NULL; + + gst_event_parse_protection (event, &system_id, NULL, NULL); + GST_DEBUG_OBJECT (demux, "Received protection event for system ID %s", + system_id); + gst_qtdemux_append_protection_system_id (demux, system_id); + /* save the event for later, for source pads that have not been created */ + g_queue_push_tail (&demux->protection_event_queue, gst_event_ref (event)); + /* send it to all pads that already exist */ + gst_qtdemux_push_event (demux, event); + res = TRUE; + goto drop; + } + case GST_EVENT_STREAM_START: + { + const gchar *upstream_id; + gst_event_parse_stream_start (event, &upstream_id); + + if (!demux->collection) { + demux->collection = gst_stream_collection_new (upstream_id); + } else if (!demux->pullbased && g_strcmp0 (upstream_id, + gst_stream_collection_get_upstream_id (demux->collection))) { + /* drain all data if stream-id changed */ + gint i; + gboolean has_valid_stream = FALSE; + GST_DEBUG_OBJECT (demux, "stream-id changed"); + for (i = 0; i < demux->n_streams; i++) { + if (demux->streams[i]->pad != NULL) { + has_valid_stream = TRUE; + break; + } + } + if (has_valid_stream) { + /* if we are in here, there was valid stream, so drain all buffers */ + GST_DEBUG_OBJECT (demux, "drain buffers, if available"); + gst_qtdemux_process_adapter (demux, TRUE); + } + + gst_qtdemux_reset (demux, FALSE); + + /* setup new collection */ + gst_object_unref (demux->collection); + demux->collection = gst_stream_collection_new (upstream_id); + + /* FIXME: in case dolby-vision dual track, stream-collection may cause + * problem. To prevent regression, let's block this. + * Please let me fix when stream-collection for dolby-vision dual track is verified */ + if (!demux->is_dolby_hdr) + demux->new_collection = TRUE; + + } + gst_event_unref (event); + goto drop; + } +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + case GST_EVENT_CUSTOM_DOWNSTREAM: + { + const GstStructure *structure = gst_event_get_structure (event); + + if (structure) { + if (gst_structure_has_name (structure, "dash-period") + && demux->dash_mode) { + gint64 dash_pts_offset; + guint64 dash_period_start; + gint64 dash_subtitle_offset; + guint dash_subtitle_index; + + if (gst_structure_get_int64 (structure, "pts-offset", + &dash_pts_offset)) { + GST_INFO_OBJECT (demux, + "received dash pts offset : %" GST_STIME_FORMAT, + GST_STIME_ARGS (dash_pts_offset)); + demux->dash_pts_offset = dash_pts_offset; + } else { + GST_INFO_OBJECT (demux, "fail to parse dash pts-offset event"); + } + + if (gst_structure_get_uint64 (structure, "start", &dash_period_start)) { + GST_INFO_OBJECT (demux, + "received dash period start : %" G_GUINT64_FORMAT, + dash_period_start); + demux->dash_period_start = dash_period_start; + } else { + GST_INFO_OBJECT (demux, "fail to parse dash start event"); + } + + if (gst_structure_get_int64 (structure, "subtitle-offset", + &dash_subtitle_offset)) { + GST_INFO_OBJECT (demux, + "received dash subtitle offset : %" G_GINT64_FORMAT, + dash_subtitle_offset); + demux->dash_subtitle_offset = dash_subtitle_offset; + } else { + GST_INFO_OBJECT (demux, "fail to parse dash subtitle-offset event"); + } + + if (gst_structure_get_uint (structure, "subtitle-index", + &dash_subtitle_index)) { + GST_INFO_OBJECT (demux, "received dash subtitle index : %d", + dash_subtitle_index); + demux->dash_subtitle_index = dash_subtitle_index; + } else { + GST_INFO_OBJECT (demux, "fail to parse dash subtitle-index event"); + } + gst_event_unref (event); + res = TRUE; + goto drop; + } else if (gst_structure_has_name (structure, "utc-offset")) { + GstClockTime upstream_basetime; + + if (gst_structure_get_clock_time (structure, "offset", + &upstream_basetime)) { + GST_DEBUG_OBJECT (demux, "received utc-offset: %" GST_TIME_FORMAT + " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (upstream_basetime), + upstream_basetime); + + demux->upstream_basetime = upstream_basetime; + } else { + GST_ERROR_OBJECT (demux, "fail to get utc-offset"); + } + + gst_event_unref (event); + res = TRUE; + goto drop; + } + } + break; + } +#endif + default: + break; + } + + res = gst_pad_event_default (demux->sinkpad, parent, event) & res; + +drop: + return res; +} + +#if 0 +static void +gst_qtdemux_set_index (GstElement * element, GstIndex * index) +{ + GstQTDemux *demux = GST_QTDEMUX (element); + + GST_OBJECT_LOCK (demux); + if (demux->element_index) + gst_object_unref (demux->element_index); + if (index) { + demux->element_index = gst_object_ref (index); + } else { + demux->element_index = NULL; + } + GST_OBJECT_UNLOCK (demux); + /* object lock might be taken again */ + if (index) + gst_index_get_writer_id (index, GST_OBJECT (element), &demux->index_id); + GST_DEBUG_OBJECT (demux, "Set index %" GST_PTR_FORMAT "for writer id %d", + demux->element_index, demux->index_id); +} + +static GstIndex * +gst_qtdemux_get_index (GstElement * element) +{ + GstIndex *result = NULL; + GstQTDemux *demux = GST_QTDEMUX (element); + + GST_OBJECT_LOCK (demux); + if (demux->element_index) + result = gst_object_ref (demux->element_index); + GST_OBJECT_UNLOCK (demux); + + GST_DEBUG_OBJECT (demux, "Returning index %" GST_PTR_FORMAT, result); + + return result; +} +#endif + +static void +gst_qtdemux_stbl_free (QtDemuxStream * stream) +{ + g_free ((gpointer) stream->stco.data); + stream->stco.data = NULL; + g_free ((gpointer) stream->stsz.data); + stream->stsz.data = NULL; + g_free ((gpointer) stream->stsc.data); + stream->stsc.data = NULL; + g_free ((gpointer) stream->stts.data); + stream->stts.data = NULL; + g_free ((gpointer) stream->stss.data); + stream->stss.data = NULL; + g_free ((gpointer) stream->stps.data); + stream->stps.data = NULL; + g_free ((gpointer) stream->ctts.data); + stream->ctts.data = NULL; +} + +static void +gst_qtdemux_sample_to_group_free_internal (QtDemuxSampleToGroup * + sample_to_group) +{ + if (sample_to_group->entry_free_func) { + guint i; + for (i = 0; i < sample_to_group->sbgp_entries_length; i++) { + QtDemuxSampleToGroupEntry *entry = &sample_to_group->sbgp_entries[i]; + sample_to_group->entry_free_func (entry->data); + } + } + + g_free (sample_to_group->sbgp_entries); + g_free (sample_to_group); +} + +static void +gst_qtdemux_sample_to_group_free (QtDemuxStream * stream) +{ + if (stream->sample_to_group) + g_list_free_full (stream->sample_to_group, + (GDestroyNotify) gst_qtdemux_sample_to_group_free_internal); + + stream->sample_to_group = NULL; +} + +static void +gst_qtdemux_stream_flush_segments_data (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + g_free (stream->segments); + stream->segments = NULL; + stream->segment_index = -1; + stream->accumulated_base = 0; + stream->dummy_segment = FALSE; + + g_free (stream->seg_idx_entries); + stream->seg_idx_entries = NULL; + stream->n_seg_idx_entries = 0; +} + +static void +gst_qtdemux_stream_flush_samples_data (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + g_free (stream->samples); + stream->samples = NULL; + gst_qtdemux_stbl_free (stream); + + /* fragments */ + g_free (stream->ra_entries); + stream->ra_entries = NULL; + stream->n_ra_entries = 0; + + stream->sample_index = -1; + stream->stbl_index = -1; + stream->n_samples = 0; + stream->time_position = 0; + + stream->n_samples_moof = 0; + stream->duration_moof = 0; + + gst_qtdemux_sample_to_group_free (stream); +} + +static void +gst_qtdemux_stream_flush_sample_descriptions (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + gint i; + + if (stream->stsd_entries == NULL || stream->stsd_entries_length < 1) + return; + + for (i = 0; i < stream->stsd_entries_length; i++) { + QtDemuxStreamStsdEntry *stsd_entry = &stream->stsd_entries[i]; + if (stsd_entry->rgb8_palette) { + gst_memory_unref (stsd_entry->rgb8_palette); + stsd_entry->rgb8_palette = NULL; + } + if (stsd_entry->caps) { + gst_caps_unref (stsd_entry->caps); + stsd_entry->caps = NULL; + } + } + g_free (stream->stsd_entries); + stream->stsd_entries = NULL; +} + +static void +gst_qtdemux_stream_clear (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + if (stream->allocator) + gst_object_unref (stream->allocator); + while (stream->buffers) { + gst_buffer_unref (GST_BUFFER_CAST (stream->buffers->data)); + stream->buffers = g_slist_delete_link (stream->buffers, stream->buffers); + } + if (stream->rgb8_palette) { + gst_memory_unref (stream->rgb8_palette); + stream->rgb8_palette = NULL; + } + if (stream->pending_tags) + gst_tag_list_unref (stream->pending_tags); + stream->pending_tags = NULL; + g_free (stream->redirect_uri); + stream->redirect_uri = NULL; + stream->sent_eos = FALSE; + stream->sparse = FALSE; + stream->protected = FALSE; + if (stream->protection_scheme_info) { + if (is_common_enc_scheme_type (stream->protection_scheme_type)) { + QtDemuxCencSampleSetInfo *info = + (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + if (info->default_properties) + gst_structure_free (info->default_properties); + if (info->crypto_info) + g_ptr_array_free (info->crypto_info, TRUE); + } + g_free (stream->protection_scheme_info); + stream->protection_scheme_info = NULL; + } + stream->protection_scheme_type = 0; + stream->protection_scheme_version = 0; + g_queue_foreach (&stream->protection_scheme_event_queue, + (GFunc) gst_event_unref, NULL); + g_queue_clear (&stream->protection_scheme_event_queue); + gst_qtdemux_stream_flush_segments_data (qtdemux, stream); + gst_qtdemux_stream_flush_samples_data (qtdemux, stream); + gst_qtdemux_stream_flush_sample_descriptions (qtdemux, stream); +} + +static void +gst_qtdemux_stream_free (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + gst_qtdemux_stream_clear (qtdemux, stream); + if (stream->caps) + gst_caps_unref (stream->caps); + stream->caps = NULL; + if (stream->pad) { + gst_element_remove_pad (GST_ELEMENT_CAST (qtdemux), stream->pad); + gst_flow_combiner_remove_pad (qtdemux->flowcombiner, stream->pad); + } + g_free (stream); +} + +static void +gst_qtdemux_remove_stream (GstQTDemux * qtdemux, int i) +{ + g_assert (i >= 0 && i < qtdemux->n_streams && qtdemux->streams[i] != NULL); + + gst_qtdemux_stream_free (qtdemux, qtdemux->streams[i]); + qtdemux->streams[i] = qtdemux->streams[qtdemux->n_streams - 1]; + qtdemux->streams[qtdemux->n_streams - 1] = NULL; + qtdemux->n_streams--; +} + +static GstStateChangeReturn +gst_qtdemux_change_state (GstElement * element, GstStateChange transition) +{ + GstQTDemux *qtdemux = GST_QTDEMUX (element); + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + default: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY:{ + gst_qtdemux_reset (qtdemux, TRUE); + break; + } + default: + break; + } + + return result; +} + +static void +qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) +{ + /* counts as header data */ + qtdemux->header_size += length; + + /* only consider at least a sufficiently complete ftyp atom */ + if (length >= 20) { + GstBuffer *buf; + + qtdemux->major_brand = QT_FOURCC (buffer + 8); + GST_DEBUG_OBJECT (qtdemux, "major brand: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (qtdemux->major_brand)); + if (qtdemux->comp_brands) + gst_buffer_unref (qtdemux->comp_brands); + buf = qtdemux->comp_brands = gst_buffer_new_and_alloc (length - 16); + gst_buffer_fill (buf, 0, buffer + 16, length - 16); + } +} + +static void +qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist, + GstTagList * xmptaglist) +{ + /* Strip out bogus fields */ + if (xmptaglist) { + if (gst_tag_list_get_scope (taglist) == GST_TAG_SCOPE_GLOBAL) { + gst_tag_list_remove_tag (xmptaglist, GST_TAG_VIDEO_CODEC); + gst_tag_list_remove_tag (xmptaglist, GST_TAG_AUDIO_CODEC); + } else { + gst_tag_list_remove_tag (xmptaglist, GST_TAG_CONTAINER_FORMAT); + } + + GST_DEBUG_OBJECT (qtdemux, "Found XMP tags %" GST_PTR_FORMAT, xmptaglist); + + /* prioritize native tags using _KEEP mode */ + gst_tag_list_insert (taglist, xmptaglist, GST_TAG_MERGE_KEEP); + gst_tag_list_unref (xmptaglist); + } +} + +static void +qtdemux_parse_piff (GstQTDemux * qtdemux, const guint8 * buffer, gint length, + guint offset) +{ + GstByteReader br; + guint8 version; + guint32 flags = 0; + guint i; + guint8 iv_size = 8; + QtDemuxStream *stream; + GstStructure *structure; + QtDemuxCencSampleSetInfo *ss_info = NULL; + const gchar *system_id; + gboolean uses_sub_sample_encryption = FALSE; + + if (qtdemux->n_streams == 0) + return; + + stream = qtdemux->streams[0]; + + structure = gst_caps_get_structure (stream->caps, 0); + if (!gst_structure_has_name (structure, "application/x-cenc")) { + GST_WARNING_OBJECT (qtdemux, + "Attempting PIFF box parsing on an unencrypted stream."); + return; + } + + if (qtdemux->protection_system_ids && qtdemux->protection_system_ids->len > 0) { + GST_DEBUG_OBJECT (qtdemux, "we've got protection_system id's already"); + } else { + gst_structure_get (structure, GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, + G_TYPE_STRING, &system_id, NULL); + gst_qtdemux_append_protection_system_id (qtdemux, system_id); + } + + stream->protected = TRUE; + stream->protection_scheme_type = FOURCC_cenc; + + if (!stream->protection_scheme_info) + stream->protection_scheme_info = g_new0 (QtDemuxCencSampleSetInfo, 1); + + ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (ss_info->default_properties) + gst_structure_free (ss_info->default_properties); + + ss_info->default_properties = + gst_structure_new ("application/x-cenc", + "iv_size", G_TYPE_UINT, iv_size, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL); + + if (ss_info->crypto_info) { + GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info"); + g_ptr_array_free (ss_info->crypto_info, TRUE); + ss_info->crypto_info = NULL; + } + + /* skip UUID */ + gst_byte_reader_init (&br, buffer + offset + 16, length - offset - 16); + + if (!gst_byte_reader_get_uint8 (&br, &version)) { + GST_ERROR_OBJECT (qtdemux, "Error getting box's version field"); + return; + } + + if (!gst_byte_reader_get_uint24_be (&br, &flags)) { + GST_ERROR_OBJECT (qtdemux, "Error getting box's flags field"); + return; + } + + if ((flags & 0x000001)) { + guint32 algorithm_id = 0; + const guint8 *kid; + GstBuffer *kid_buf; + gboolean is_encrypted = TRUE; + + if (!gst_byte_reader_get_uint24_le (&br, &algorithm_id)) { + GST_ERROR_OBJECT (qtdemux, "Error getting box's algorithm ID field"); + return; + } + + algorithm_id >>= 8; + if (algorithm_id == 0) { + is_encrypted = FALSE; + } else if (algorithm_id == 1) { + /* FIXME: maybe store this in properties? */ + GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CTR encrypted stream"); + } else if (algorithm_id == 2) { + /* FIXME: maybe store this in properties? */ + GST_DEBUG_OBJECT (qtdemux, "AES 128-bits CBC encrypted stream"); + } + + if (!gst_byte_reader_get_uint8 (&br, &iv_size)) + return; + + if (!gst_byte_reader_get_data (&br, 16, &kid)) + return; + + kid_buf = gst_buffer_new_allocate (NULL, 16, NULL); + gst_buffer_fill (kid_buf, 0, kid, 16); + if (ss_info->default_properties) + gst_structure_free (ss_info->default_properties); + ss_info->default_properties = + gst_structure_new ("application/x-cenc", + "iv_size", G_TYPE_UINT, iv_size, + "encrypted", G_TYPE_BOOLEAN, is_encrypted, + "kid", GST_TYPE_BUFFER, kid_buf, NULL); + GST_DEBUG_OBJECT (qtdemux, "default sample properties: " + "is_encrypted=%u, iv_size=%u", is_encrypted, iv_size); + gst_buffer_unref (kid_buf); + } else if ((flags & 0x000002)) { + uses_sub_sample_encryption = TRUE; + } + + if (!gst_byte_reader_get_uint32_be (&br, &qtdemux->cenc_aux_sample_count)) { + GST_ERROR_OBJECT (qtdemux, "Error getting box's sample count field"); + return; + } + + ss_info->crypto_info = + g_ptr_array_new_full (qtdemux->cenc_aux_sample_count, + (GDestroyNotify) qtdemux_gst_structure_free); + + for (i = 0; i < qtdemux->cenc_aux_sample_count; ++i) { + GstStructure *properties; + guint8 *data; + GstBuffer *buf; + + properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i); + if (properties == NULL) { + GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i); + return; + } + + if (!gst_byte_reader_dup_data (&br, iv_size, &data)) { + GST_ERROR_OBJECT (qtdemux, "IV data not present for sample %u", i); + gst_structure_free (properties); + return; + } + buf = gst_buffer_new_wrapped (data, iv_size); + gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + + if (uses_sub_sample_encryption) { + guint16 n_subsamples; + + if (!gst_byte_reader_get_uint16_be (&br, &n_subsamples) + || n_subsamples == 0) { + GST_ERROR_OBJECT (qtdemux, + "failed to get subsample count for sample %u", i); + gst_structure_free (properties); + return; + } + GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples); + if (!gst_byte_reader_dup_data (&br, n_subsamples * 6, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", + i); + gst_structure_free (properties); + return; + } + buf = gst_buffer_new_wrapped (data, n_subsamples * 6); + gst_structure_set (properties, + "subsample_count", G_TYPE_UINT, n_subsamples, + "subsamples", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } else { + gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); + } + + g_ptr_array_add (ss_info->crypto_info, properties); + } +} + +static void +qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length) +{ + static const guint8 xmp_uuid[] = { 0xBE, 0x7A, 0xCF, 0xCB, + 0x97, 0xA9, 0x42, 0xE8, + 0x9C, 0x71, 0x99, 0x94, + 0x91, 0xE3, 0xAF, 0xAC + }; + static const guint8 playready_uuid[] = { + 0xd0, 0x8a, 0x4f, 0x18, 0x10, 0xf3, 0x4a, 0x82, + 0xb6, 0xc8, 0x32, 0xd8, 0xab, 0xa1, 0x83, 0xd3 + }; + + static const guint8 piff_sample_encryption_uuid[] = { + 0xa2, 0x39, 0x4f, 0x52, 0x5a, 0x9b, 0x4f, 0x14, + 0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4 + }; + + /* 4747982f-82a3-44a8-bb1d-a8c2ba5dcf9d + * NOTE: it's big-endian */ + static const guint8 dvr_uuid[] = { + 0x47, 0x47, 0x98, 0x2f, 0x82, 0xa3, 0x44, 0xa8, + 0xbb, 0x1d, 0xa8, 0xc2, 0xba, 0x5d, 0xcf, 0x9d + }; + + guint offset; + + /* counts as header data */ + qtdemux->header_size += length; + + offset = (QT_UINT32 (buffer) == 0) ? 16 : 8; + + if (length < offset + 16) { + GST_DEBUG_OBJECT (qtdemux, "uuid atom is too short, skipping"); + return; + } + + if (memcmp (buffer + offset, xmp_uuid, 16) == 0) { + GstBuffer *buf; + GstTagList *taglist; + + buf = _gst_buffer_new_wrapped ((guint8 *) buffer + offset + 16, + length - offset - 16, NULL); + taglist = gst_tag_list_from_xmp_buffer (buf); + gst_buffer_unref (buf); + + qtdemux_handle_xmp_taglist (qtdemux, qtdemux->tag_list, taglist); + + } else if (memcmp (buffer + offset, playready_uuid, 16) == 0) { + int len; + const gunichar2 *s_utf16; + char *contents; + + len = GST_READ_UINT16_LE (buffer + offset + 0x30); + s_utf16 = (const gunichar2 *) (buffer + offset + 0x32); + contents = g_utf16_to_utf8 (s_utf16, len / 2, NULL, NULL, NULL); + GST_ERROR_OBJECT (qtdemux, "contents: %s", contents); + + g_free (contents); + + GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, + (_("Cannot play stream because it is encrypted with PlayReady DRM.")), + (NULL)); + } else if ((memcmp (buffer + offset, piff_sample_encryption_uuid, 16) == 0) && + qtdemux->mss_mode) { + qtdemux_parse_piff (qtdemux, buffer, length, offset); + } else if (memcmp (buffer + offset, dvr_uuid, 16) == 0) { + GST_DEBUG_OBJECT (qtdemux, "DVR uuid detected"); + qtdemux->configure_dvr = TRUE; + } else { + GST_DEBUG_OBJECT (qtdemux, "Ignoring unknown uuid: %08x-%08x-%08x-%08x", + GST_READ_UINT32_LE (buffer + offset), + GST_READ_UINT32_LE (buffer + offset + 4), + GST_READ_UINT32_LE (buffer + offset + 8), + GST_READ_UINT32_LE (buffer + offset + 12)); + } +} + +static void +qtdemux_parse_sidx (GstQTDemux * qtdemux, const guint8 * buffer, gint length) +{ + GstSidxParser sidx_parser; + GstIsoffParserResult res; + guint consumed; + + gst_isoff_qt_sidx_parser_init (&sidx_parser); + + res = + gst_isoff_qt_sidx_parser_add_data (&sidx_parser, buffer, length, + &consumed); + GST_DEBUG_OBJECT (qtdemux, "sidx parse result: %d", res); + if (res == GST_ISOFF_QT_PARSER_DONE) { + gint i, n_parsed_entries, n_entries = 0; + QtDemuxStream *stream; + GstSidxBoxEntry *entries; + QtDemuxRandomAccessEntry *seg_entries; + + stream = qtdemux_find_stream (qtdemux, sidx_parser.sidx.ref_id); + n_entries = n_parsed_entries = sidx_parser.sidx.entries_count; + entries = sidx_parser.sidx.entries; + + for (i = 0; i < n_parsed_entries; i++) { + /* FIXME: non-zero ref_type means the corresponding sidx entry is pointing + * to another sidx box. We do not consider recursive sidx structure. + * This might be used in the future. But currently there is no use case + * So, let's ignore such a type of sidx entries. */ + if (entries[i].ref_type) { + n_entries--; + } + } + + GST_LOG_OBJECT (qtdemux, "sidx anchor offset = %" G_GUINT64_FORMAT, + qtdemux->offset); + if (stream && n_entries > 0) { + if (stream->n_seg_idx_entries == 0) { + g_assert (stream->seg_idx_entries == NULL); + stream->seg_idx_entries = g_try_new0 (QtDemuxRandomAccessEntry, + n_entries); + } else { + stream->seg_idx_entries = g_try_renew (QtDemuxRandomAccessEntry, + stream->seg_idx_entries, stream->n_seg_idx_entries + n_entries); + } + + seg_entries = stream->seg_idx_entries + stream->n_seg_idx_entries; + for (i = 0; i < n_parsed_entries; i++) { + if (entries[i].ref_type) + continue; + seg_entries->ts = entries[i].pts; + seg_entries->moof_offset = entries[i].offset + qtdemux->offset; + seg_entries++; + } + stream->n_seg_idx_entries += n_entries; + } + + if (!qtdemux->upstream_format_is_time) + check_update_duration (qtdemux, sidx_parser.cumulative_pts); + } + gst_isoff_qt_sidx_parser_clear (&sidx_parser); +} + +static void +qtdemux_parse_mmpu (GstQTDemux * qtdemux, const guint8 * buffer, gint length) +{ + /* counts as header data */ + qtdemux->header_size += length; + + /* reset hint track related variables */ + qtdemux->has_mmth = FALSE; + qtdemux->is_mmth_timed = TRUE; + qtdemux->ignore_hintsample = FALSE; + + /* only consider at least a sufficiently complete mmpu atom */ + if (length >= 25) { + guint8 is_complete; + guint32 mpu_seq_num; + guint32 asset_id_scheme; + guint32 asset_id_length; + gchar *asset_id_value; + const guint8 *asset_id_ptr; + + is_complete = QT_UINT8 (buffer + 12); + mpu_seq_num = QT_UINT32 (buffer + 12 + 1); + + is_complete = is_complete >> 7; + + GST_DEBUG_OBJECT (qtdemux, "is complete: %d", is_complete); + + /* FIXME: do we need to do something with mpu_sequence_number? */ + GST_DEBUG_OBJECT (qtdemux, "mpu sequence number: %" G_GUINT32_FORMAT, + mpu_seq_num); + + asset_id_scheme = QT_FOURCC (buffer + 17); + asset_id_length = QT_UINT32 (buffer + 17 + 4); + + GST_DEBUG_OBJECT (qtdemux, "asset id scheme: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (asset_id_scheme)); + GST_DEBUG_OBJECT (qtdemux, "asset id length: %" G_GUINT32_FORMAT, + asset_id_length); + if (asset_id_length != length - 25) { + GST_ERROR_OBJECT (qtdemux, + "asset id len %" G_GUINT32_FORMAT + " is different to remaining bytes %d", asset_id_length, length - 25); + return; + } + + asset_id_ptr = buffer + 25; + asset_id_value = g_strndup ((gchar *) asset_id_ptr, asset_id_length); + GST_DEBUG_OBJECT (qtdemux, "asset id value: %s", asset_id_value); + + if (g_strcmp0 (asset_id_value, qtdemux->asset_id)) { + GST_DEBUG_OBJECT (qtdemux, "asset id changed from %s to %s", + GST_STR_NULL (qtdemux->asset_id), GST_STR_NULL (asset_id_value)); + g_free (qtdemux->asset_id); + qtdemux->asset_id = g_strdup (asset_id_value); + } + + if (mpu_seq_num != qtdemux->mpu_seq_num) { + GST_DEBUG_OBJECT (qtdemux, + "reset upstream_basetime_offset, mpu sequence number changed from %" + G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT, qtdemux->mpu_seq_num, + mpu_seq_num); + qtdemux->mpu_seq_num = mpu_seq_num; + qtdemux->upstream_basetime_offset = GST_CLOCK_TIME_NONE; + } + + g_free (asset_id_value); + } +} + +static gboolean +qtdemux_parse_mmth_sample (GstQTDemux * qtdemux, const guint8 * data, + gsize size, guint32 * sample_index) +{ + gboolean ret = FALSE; + guint32 seq_num; /* the sequence num of MFU (not sample's sequence) */ + + if (qtdemux->major_brand != FOURCC_mpuf || !qtdemux->has_mmth) + return FALSE; + + GST_DEBUG_OBJECT (qtdemux, "parsing MMT Hint Sample"); + + /* FIXME: only the minimum size of timed hint sample is considered */ + if ((qtdemux->is_mmth_timed && size < 34) || + (!qtdemux->is_mmth_timed && size < 6)) { + GST_ERROR_OBJECT (qtdemux, "hint sample size is too small"); + ret = FALSE; + goto done; + } + + seq_num = QT_UINT32 (data); + GST_LOG_OBJECT (qtdemux, " seq_num : %" G_GUINT32_FORMAT, seq_num); + + if (qtdemux->is_mmth_timed) { + gint8 trackrefindex; /* MEDIA data's track ID. + * From the spec, this seems not to be track ID of hint track */ + guint32 moof_seq_num; /* the sequence num of moof which this sample belongs */ + guint32 samplenumber; /* sample number accumulated from the start of MPU. + * (not in current moof). + * TODO: need confirm whether this sequence is starting + * from MOOV or MOOF */ + guint8 priority; /* FIXME: how to use this ? */ + guint8 dep_counter; /* FIXME: this also */ + guint32 offset; /* offset of MEDIA data in mdat + * TODO: need confirm whether this offset is before + * interleaving or after it. */ + guint32 length; /* length of MEDIA data */ + guint32 muli_size; + guint32 muli_fourcc; + gboolean multilayer_flag; + + trackrefindex = QT_UINT8 (data + 4); + moof_seq_num = QT_UINT32 (data + 5); + samplenumber = QT_UINT32 (data + 9); + priority = QT_UINT8 (data + 13); + dep_counter = QT_UINT8 (data + 14); + offset = QT_UINT32 (data + 15); + length = QT_UINT32 (data + 19); + + /* multiLayerInfo box */ + muli_size = QT_UINT32 (data + 23); + muli_fourcc = QT_FOURCC (data + 27); + multilayer_flag = (QT_UINT8 (data + 31) >> 7) & 0x1; + + GST_LOG_OBJECT (qtdemux, " trackrefindex : %d", trackrefindex); + GST_LOG_OBJECT (qtdemux, " moof_seq_num : %" + G_GUINT32_FORMAT, moof_seq_num); + GST_LOG_OBJECT (qtdemux, " samplenumber : %" + G_GUINT32_FORMAT, samplenumber); + GST_LOG_OBJECT (qtdemux, " priority : %u", priority); + GST_LOG_OBJECT (qtdemux, " dep_counter : %u", dep_counter); + GST_LOG_OBJECT (qtdemux, " offset : %" G_GUINT32_FORMAT, offset); + GST_LOG_OBJECT (qtdemux, " length : %" G_GUINT32_FORMAT, length); + + GST_LOG_OBJECT (qtdemux, " muli_size : %" G_GUINT32_FORMAT, muli_size); + GST_LOG_OBJECT (qtdemux, " fourcc : %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (muli_fourcc)); + GST_LOG_OBJECT (qtdemux, " multilayer_flag : %d", multilayer_flag); + + + if (muli_fourcc != FOURCC_muli) { + GST_ERROR_OBJECT (qtdemux, "seems not to be MMT hint sample"); + ret = FALSE; + goto done; + } else if ((multilayer_flag && size != 13) || + (!multilayer_flag && muli_size != 11)) { + GST_ERROR_OBJECT (qtdemux, "wrong multilayer box size"); + ret = FALSE; + goto done; + } + + /* samplenumber of the first sample in MPU is 1, but qtdemux will increase + * it from zero. need to be compensated */ + *sample_index = samplenumber - 1; + + /* samplenumber should be started from 1 */ + if (samplenumber == 0) { + GST_ERROR_OBJECT (qtdemux, "Invalid samplenumber, ignore"); + qtdemux->ignore_hintsample = TRUE; + } + } else { + guint16 item_id = QT_UINT16 (data + 4); + GST_TRACE_OBJECT (qtdemux, " item_ID : %" G_GUINT16_FORMAT, item_id); + } + + ret = TRUE; + +done: + return ret; +} + +/* caller verifies at least 8 bytes in buf */ +static void +extract_initial_length_and_fourcc (const guint8 * data, guint size, + guint64 * plength, guint32 * pfourcc) +{ + guint64 length; + guint32 fourcc; + + length = QT_UINT32 (data); + GST_DEBUG ("length 0x%08" G_GINT64_MODIFIER "x", length); + fourcc = QT_FOURCC (data + 4); + GST_DEBUG ("atom type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + + /* Add fourcc condition to stop parse header at defective contents. + Forest Gump 1994-KickASS(MicroStar RG).mp4 from NC3 */ + if (length == 0 && fourcc != 0) { + length = G_MAXUINT64; + } else if (length == 1 && size >= 16 && fourcc != 0) { + /* this means we have an extended size, which is the 64 bit value of + * the next 8 bytes */ + length = QT_UINT64 (data + 8); + GST_DEBUG ("length 0x%08" G_GINT64_MODIFIER "x", length); + } + + if (plength) + *plength = length; + if (pfourcc) + *pfourcc = fourcc; +} + +static gboolean +qtdemux_parse_mehd (GstQTDemux * qtdemux, GstByteReader * br) +{ + guint32 version = 0; + GstClockTime duration = 0; + + if (!gst_byte_reader_get_uint32_be (br, &version)) + goto failed; + + version >>= 24; + if (version == 1) { + if (!gst_byte_reader_get_uint64_be (br, &duration)) + goto failed; + } else { + guint32 dur = 0; + + if (!gst_byte_reader_get_uint32_be (br, &dur)) + goto failed; + duration = dur; + } + + GST_INFO_OBJECT (qtdemux, "mehd duration: %" G_GUINT64_FORMAT, duration); + qtdemux->duration = duration; + + return TRUE; + +failed: + { + GST_DEBUG_OBJECT (qtdemux, "parsing mehd failed"); + return FALSE; + } +} + +static gboolean +qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 * ds_duration, guint32 * ds_size, guint32 * ds_flags) +{ + if (!stream->parsed_trex && qtdemux->moov_node) { + GNode *mvex, *trex; + GstByteReader trex_data; + + mvex = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_mvex); + if (mvex) { + trex = qtdemux_tree_get_child_by_type_full (mvex, FOURCC_trex, + &trex_data); + while (trex) { + guint32 id = 0, dur = 0, size = 0, flags = 0, index = 0; + + /* skip version/flags */ + if (!gst_byte_reader_skip (&trex_data, 4)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &id)) + goto next; + if (id != stream->track_id) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &index)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &dur)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &size)) + goto next; + if (!gst_byte_reader_get_uint32_be (&trex_data, &flags)) + goto next; + + GST_DEBUG_OBJECT (qtdemux, "fragment defaults for stream %d; " + "index %d, duration %d, size %d, flags 0x%x", stream->track_id, + index, dur, size, flags); + + stream->parsed_trex = TRUE; + stream->stsd_sample_description_id = index - 1; + stream->def_sample_duration = dur; + stream->def_sample_size = size; + stream->def_sample_flags = flags; + + next: + /* iterate all siblings */ + trex = qtdemux_tree_get_sibling_by_type_full (trex, FOURCC_trex, + &trex_data); + } + } + } + + *ds_duration = stream->def_sample_duration; + *ds_size = stream->def_sample_size; + *ds_flags = stream->def_sample_flags; + + /* even then, above values are better than random ... */ + if (G_UNLIKELY (!stream->parsed_trex)) { + GST_WARNING_OBJECT (qtdemux, + "failed to find fragment defaults for stream %d", stream->track_id); + return FALSE; + } + + return TRUE; +} + +/* This method should be called whenever a more accurate duration might + * have been found. It will update all relevant variables if/where needed + */ +static void +check_update_duration (GstQTDemux * qtdemux, GstClockTime duration) +{ + guint i; + guint64 movdur; + GstClockTime prevdur; + + movdur = GSTTIME_TO_QTTIME (qtdemux, duration); + + if (movdur > qtdemux->duration) { + prevdur = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration); + GST_DEBUG_OBJECT (qtdemux, + "Updating total duration to %" GST_TIME_FORMAT " was %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration), GST_TIME_ARGS (prevdur)); + qtdemux->duration = movdur; + GST_DEBUG_OBJECT (qtdemux, + "qtdemux->segment.duration: %" GST_TIME_FORMAT " .stop: %" + GST_TIME_FORMAT, GST_TIME_ARGS (qtdemux->segment.duration), + GST_TIME_ARGS (qtdemux->segment.stop)); + if (qtdemux->segment.duration == prevdur) { + /* If the current segment has duration/stop identical to previous duration + * update them also (because they were set at that point in time with + * the wrong duration */ + /* We convert the value *from* the timescale version to avoid rounding errors */ + GstClockTime fixeddur = QTTIME_TO_GSTTIME (qtdemux, movdur); + GST_DEBUG_OBJECT (qtdemux, "Updated segment.duration and segment.stop"); + qtdemux->segment.duration = fixeddur; + qtdemux->segment.stop = fixeddur; + } + } + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream = qtdemux->streams[i]; + if (stream) { + movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration); + if (movdur > stream->duration) { + GST_DEBUG_OBJECT (qtdemux, + "Updating stream #%d duration to %" GST_TIME_FORMAT, i, + GST_TIME_ARGS (duration)); + stream->duration = movdur; + if (stream->dummy_segment) { + /* Update all dummy values to new duration */ + stream->segments[0].stop_time = duration; + stream->segments[0].duration = duration; + stream->segments[0].media_stop = duration; + + /* let downstream know we possibly have a new stop time */ + if (stream->segment_index != -1) { + GstClockTime pos; + + if (qtdemux->segment.rate >= 0) { + pos = stream->segment.start; + } else { + pos = stream->segment.stop; + } + + gst_qtdemux_stream_update_segment (qtdemux, stream, + stream->segment_index, pos, NULL, NULL); + } + } + } + } + } +} + + +#define ABS_CLOCK_DIFF(s) (((s) >= 0) ? (s) : (-1 * (s))) + +static gboolean +qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, + QtDemuxStream * stream, guint32 d_sample_duration, guint32 d_sample_size, + guint32 d_sample_flags, gint64 moof_offset, gint64 moof_length, + gint64 * base_offset, gint64 * running_offset, gint64 decode_ts) +{ + GstClockTime gst_ts = GST_CLOCK_TIME_NONE; + guint64 timestamp; + gint32 data_offset = 0; + guint32 flags = 0, first_flags = 0, samples_count = 0; + gint i; + guint8 *data; + guint entry_size, dur_offset, size_offset, flags_offset = 0, ct_offset = 0; + QtDemuxSample *sample; + gboolean ismv = FALSE; + + GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; " + "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT ", " + "decode ts %" G_GINT64_FORMAT, stream->track_id, d_sample_duration, + d_sample_size, d_sample_flags, *base_offset, decode_ts); + + if (stream->pending_seek && moof_offset < stream->pending_seek->moof_offset) { + GST_INFO_OBJECT (stream->pad, "skipping trun before seek target fragment"); + return TRUE; + } + + /* presence of stss or not can't really tell us much, + * and flags and so on tend to be marginally reliable in these files */ + if (stream->subtype == FOURCC_soun) { + GST_DEBUG_OBJECT (qtdemux, + "sound track in fragmented file; marking all keyframes"); + stream->all_keyframe = TRUE; + } + + if (!gst_byte_reader_skip (trun, 1) || + !gst_byte_reader_get_uint24_be (trun, &flags)) + goto fail; + + if (!gst_byte_reader_get_uint32_be (trun, &samples_count)) + goto fail; + + if (flags & TR_DATA_OFFSET) { + /* note this is really signed */ + if (!gst_byte_reader_get_int32_be (trun, &data_offset)) + goto fail; + GST_LOG_OBJECT (qtdemux, "trun data offset %d", data_offset); + /* default base offset = first byte of moof */ + if (*base_offset == -1) { + GST_LOG_OBJECT (qtdemux, "base_offset at moof"); + *base_offset = moof_offset; + } + *running_offset = *base_offset + data_offset; + } else { + /* if no offset at all, that would mean data starts at moof start, + * which is a bit wrong and is ismv crappy way, so compensate + * assuming data is in mdat following moof */ + if (*base_offset == -1) { + *base_offset = moof_offset + moof_length + 8; + GST_LOG_OBJECT (qtdemux, "base_offset assumed in mdat after moof"); + ismv = TRUE; + } + if (*running_offset == -1) + *running_offset = *base_offset; + } + + GST_LOG_OBJECT (qtdemux, "running offset now %" G_GINT64_FORMAT, + *running_offset); + GST_LOG_OBJECT (qtdemux, "trun offset %d, flags 0x%x, entries %d", + data_offset, flags, samples_count); + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (G_UNLIKELY (flags & TR_SAMPLE_FLAGS)) { + GST_DEBUG_OBJECT (qtdemux, + "invalid flags; SAMPLE and FIRST_SAMPLE present, discarding latter"); + flags ^= TR_FIRST_SAMPLE_FLAGS; + } else { + if (!gst_byte_reader_get_uint32_be (trun, &first_flags)) + goto fail; + GST_LOG_OBJECT (qtdemux, "first flags: 0x%x", first_flags); + } + } + + /* FIXME ? spec says other bits should also be checked to determine + * entry size (and prefix size for that matter) */ + entry_size = 0; + dur_offset = size_offset = 0; + if (flags & TR_SAMPLE_DURATION) { + GST_LOG_OBJECT (qtdemux, "entry duration present"); + dur_offset = entry_size; + entry_size += 4; + } + if (flags & TR_SAMPLE_SIZE) { + GST_LOG_OBJECT (qtdemux, "entry size present"); + size_offset = entry_size; + entry_size += 4; + } + if (flags & TR_SAMPLE_FLAGS) { + GST_LOG_OBJECT (qtdemux, "entry flags present"); + flags_offset = entry_size; + entry_size += 4; + } + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + GST_LOG_OBJECT (qtdemux, "entry ct offset present"); + ct_offset = entry_size; + entry_size += 4; + } + + if (!qt_atom_parser_has_chunks (trun, samples_count, entry_size)) + goto fail; + data = (guint8 *) gst_byte_reader_peek_data_unchecked (trun); + + if (stream->n_samples + samples_count >= + QTDEMUX_MAX_SAMPLE_INDEX_SIZE / sizeof (QtDemuxSample)) + goto index_too_big; + + GST_DEBUG_OBJECT (qtdemux, "allocating n_samples %u * %u (%.2f MB)", + stream->n_samples + samples_count, (guint) sizeof (QtDemuxSample), + (stream->n_samples + samples_count) * + sizeof (QtDemuxSample) / (1024.0 * 1024.0)); + + /* create a new array of samples if it's the first sample parsed */ + if (stream->n_samples == 0) { + g_assert (stream->samples == NULL); + stream->samples = g_try_new0 (QtDemuxSample, samples_count); + /* or try to reallocate it with space enough to insert the new samples */ + } else + stream->samples = g_try_renew (QtDemuxSample, stream->samples, + stream->n_samples + samples_count); + + if (stream->samples == NULL) + goto out_of_memory; + +#ifdef MPEGDASH_MODE + if (qtdemux->dash_mode) { + timestamp = 0; + + if (qtdemux->dash_fragment_start != -1) { + timestamp = qtdemux->dash_fragment_start; + } + + if (qtdemux->dash_pts_offset != GST_CLOCK_STIME_NONE && + qtdemux->dash_segment_start != GST_CLOCK_TIME_NONE) { + guint64 abs_diff = ABS_CLOCK_DIFF (qtdemux->dash_pts_offset); + timestamp = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + + /* Add base time */ + timestamp += qtdemux->dash_segment_start; + + if (qtdemux->dash_pts_offset >= 0) { + timestamp += abs_diff; + } else { + if (timestamp < abs_diff) { + GST_WARNING_OBJECT (qtdemux, "Cannot compensate DASH pts offset"); + timestamp = 0; + } else { + timestamp -= abs_diff; + } + } + timestamp = GSTTIME_TO_QTSTREAMTIME (stream, timestamp); + } + + if (stream->n_samples > 0) { + /* subsequent fragments extend stream */ + timestamp = + stream->samples[stream->n_samples - 1].timestamp + + stream->samples[stream->n_samples - 1].duration; + + gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT + " (extends previous samples)", GST_TIME_ARGS (gst_ts)); + } + } else +#endif + if (qtdemux->fragment_start != -1) { + timestamp = GSTTIME_TO_QTSTREAMTIME (stream, qtdemux->fragment_start); + qtdemux->fragment_start = -1; + } else { + if (stream->n_samples == 0) { + if (decode_ts > 0) { + timestamp = decode_ts; + } else if (stream->pending_seek != NULL) { + /* if we don't have a timestamp from a tfdt box, we'll use the one + * from the mfra seek table */ + GST_INFO_OBJECT (stream->pad, "pending seek ts = %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->pending_seek->ts)); + + /* FIXME: this is not fully correct, the timestamp refers to the random + * access sample refered to in the tfra entry, which may not necessarily + * be the first sample in the tfrag/trun (but hopefully/usually is) */ + timestamp = GSTTIME_TO_QTSTREAMTIME (stream, stream->pending_seek->ts); + } else { + timestamp = 0; + } + + gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + GST_INFO_OBJECT (stream->pad, "first sample ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (gst_ts)); + + if (GST_CLOCK_TIME_IS_VALID (qtdemux->upstream_basetime)) { + GST_DEBUG_OBJECT (qtdemux, "Add upstream_basetime %" GST_TIME_FORMAT + " to baseDecodeTime", GST_TIME_ARGS (qtdemux->upstream_basetime)); + timestamp += + GSTTIME_TO_QTSTREAMTIME (stream, qtdemux->upstream_basetime); + } + + } else { + /* subsequent fragments extend stream */ + timestamp = + stream->samples[stream->n_samples - 1].timestamp + + stream->samples[stream->n_samples - 1].duration; + + gst_ts = QTSTREAMTIME_TO_GSTTIME (stream, timestamp); + GST_INFO_OBJECT (qtdemux, "first sample ts %" GST_TIME_FORMAT + " (extends previous samples)", GST_TIME_ARGS (gst_ts)); + } + } + + sample = stream->samples + stream->n_samples; + for (i = 0; i < samples_count; i++) { + guint32 dur, size, sflags, ct; + + /* first read sample data */ + if (flags & TR_SAMPLE_DURATION) { + dur = QT_UINT32 (data + dur_offset); + } else { + dur = d_sample_duration; + } + if (flags & TR_SAMPLE_SIZE) { + size = QT_UINT32 (data + size_offset); + } else { + size = d_sample_size; + } + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (i == 0) { + sflags = first_flags; + } else { + sflags = d_sample_flags; + } + } else if (flags & TR_SAMPLE_FLAGS) { + sflags = QT_UINT32 (data + flags_offset); + } else { + sflags = d_sample_flags; + } + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + ct = QT_UINT32 (data + ct_offset); + } else { + ct = 0; + } + data += entry_size; + + /* fill the sample information */ + sample->offset = *running_offset; + sample->pts_offset = ct; + sample->size = size; + sample->timestamp = timestamp; + sample->duration = dur; + /* sample-is-difference-sample */ + /* ismv seems to use 0x40 for keyframe, 0xc0 for non-keyframe, + * now idea how it relates to bitfield other than massive LE/BE confusion */ + sample->keyframe = ismv ? ((sflags & 0xff) == 0x40) : !(sflags & 0x10000); + if (sample->keyframe) + stream->n_sample_syncs++; + *running_offset += size; + timestamp += dur; + stream->duration_moof += dur; + sample++; + } + + /* Update total duration if needed */ + if (!qtdemux->upstream_format_is_time) + check_update_duration (qtdemux, QTSTREAMTIME_TO_GSTTIME (stream, + timestamp)); + + stream->n_samples += samples_count; + stream->n_samples_moof += samples_count; + + if (stream->pending_seek != NULL) + stream->pending_seek = NULL; + + return TRUE; + +fail: + { + GST_WARNING_OBJECT (qtdemux, "failed to parse trun"); + return FALSE; + } +out_of_memory: + { + GST_WARNING_OBJECT (qtdemux, "failed to allocate %d samples", + stream->n_samples); + return FALSE; + } +index_too_big: + { + GST_WARNING_OBJECT (qtdemux, "not allocating index of %d samples, would " + "be larger than %uMB (broken file?)", stream->n_samples, + QTDEMUX_MAX_SAMPLE_INDEX_SIZE >> 20); + return FALSE; + } +} + +/* find stream with @id */ +static inline QtDemuxStream * +qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id) +{ + QtDemuxStream *stream; + gint i; + + /* check */ + if (G_UNLIKELY (!id)) { + GST_DEBUG_OBJECT (qtdemux, "invalid track id 0"); + return NULL; + } + + /* try to get it fast and simple */ + if (G_LIKELY (id <= qtdemux->n_streams)) { + stream = qtdemux->streams[id - 1]; + if (G_LIKELY (stream->track_id == id)) + return stream; + } + + /* linear search otherwise */ + for (i = 0; i < qtdemux->n_streams; i++) { + stream = qtdemux->streams[i]; + if (stream->track_id == id) + return stream; + } + + if (qtdemux->mss_mode) { + /* mss should have only 1 stream anyway */ + return qtdemux->streams[0]; + } + + + return NULL; +} + +static gboolean +qtdemux_parse_mfhd (GstQTDemux * qtdemux, GstByteReader * mfhd, + guint32 * fragment_number) +{ + if (!gst_byte_reader_skip (mfhd, 4)) + goto fail; + if (!gst_byte_reader_get_uint32_be (mfhd, fragment_number)) + goto fail; + return TRUE; +fail: + { + GST_WARNING_OBJECT (qtdemux, "Failed to parse mfhd atom"); + return FALSE; + } +} + +static gboolean +qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, + QtDemuxStream ** stream, guint32 * default_sample_duration, + guint32 * default_sample_size, guint32 * default_sample_flags, + gint64 * base_offset) +{ + guint32 flags = 0; + guint32 track_id = 0; + + if (!gst_byte_reader_skip (tfhd, 1) || + !gst_byte_reader_get_uint24_be (tfhd, &flags)) + goto invalid_track; + + if (!gst_byte_reader_get_uint32_be (tfhd, &track_id)) + goto invalid_track; + + *stream = qtdemux_find_stream (qtdemux, track_id); + if (G_UNLIKELY (!*stream)) + goto unknown_stream; + + if (flags & TF_DEFAULT_BASE_IS_MOOF) + *base_offset = qtdemux->moof_offset; + + if (flags & TF_BASE_DATA_OFFSET) { + if (!gst_byte_reader_get_uint64_be (tfhd, (guint64 *) base_offset)) + goto invalid_track; + /* Because each MPU is independent, base_data_offset should be compensated + * with the offset of MPU */ + if (qtdemux->upstream_format_is_time && qtdemux->major_brand == FOURCC_mpuf) { + *base_offset += qtdemux->mpu_offset; + } + } + + /* obtain stream defaults */ + qtdemux_parse_trex (qtdemux, *stream, + default_sample_duration, default_sample_size, default_sample_flags); + + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) { + guint32 index; + if (!gst_byte_reader_get_uint32_be (tfhd, &index)) + goto invalid_track; + + (*stream)->stsd_sample_description_id = index - 1; + } + + + if (flags & TF_DEFAULT_SAMPLE_DURATION) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_duration)) + goto invalid_track; + + if (flags & TF_DEFAULT_SAMPLE_SIZE) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_size)) + goto invalid_track; + + if (flags & TF_DEFAULT_SAMPLE_FLAGS) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_flags)) + goto invalid_track; + + return TRUE; + +invalid_track: + { + GST_WARNING_OBJECT (qtdemux, "invalid track fragment header"); + return FALSE; + } +unknown_stream: + { + GST_DEBUG_OBJECT (qtdemux, "unknown stream in tfhd"); + return TRUE; + } +} + +static gboolean +qtdemux_parse_tfdt (GstQTDemux * qtdemux, GstByteReader * br, + guint64 * decode_time) +{ + guint32 version = 0; + + if (!gst_byte_reader_get_uint32_be (br, &version)) + return FALSE; + + version >>= 24; + if (version == 1) { + if (!gst_byte_reader_get_uint64_be (br, decode_time)) + goto failed; + } else { + guint32 dec_time = 0; + if (!gst_byte_reader_get_uint32_be (br, &dec_time)) + goto failed; + *decode_time = dec_time; + } + + GST_DEBUG_OBJECT (qtdemux, "Track fragment decode time: %" G_GUINT64_FORMAT, + *decode_time); + + return TRUE; + +failed: + { + GST_DEBUG_OBJECT (qtdemux, "parsing tfdt failed"); + return FALSE; + } +} + +static void +qtdemux_prepare_cenc_sample_properties (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + QtDemuxCencSampleSetInfo *info = NULL; + QtDemuxSampleToGroup *target_group = NULL; + guint i, j; + guint32 n_samples = 0; + + g_return_if_fail (stream != NULL); + g_return_if_fail (stream->protected); + g_return_if_fail (stream->protection_scheme_info != NULL); + + info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (info->sample_properties) + g_ptr_array_free (info->sample_properties, TRUE); + + info->sample_properties = NULL; + + if (!info->default_properties) { + GST_ERROR_OBJECT (qtdemux, "Default properties is not defined"); + return; + } + + if (stream->sample_to_group) { + GList *iter; + for (iter = stream->sample_to_group; iter; iter = g_list_next (iter)) { + QtDemuxSampleToGroup *group = (QtDemuxSampleToGroup *) iter->data; + if (group->grouping_type == FOURCC_seig) { + target_group = group; + break; + } + } + } + + if (!target_group) + return; + + GST_DEBUG_OBJECT (qtdemux, "target_group->sbgp_entries_length: %d", + target_group->sbgp_entries_length); + + for (i = 0; i < target_group->sbgp_entries_length; i++) { + QtDemuxSampleToGroupEntry *group_entry = &target_group->sbgp_entries[i]; + GST_DEBUG_OBJECT (qtdemux, "group_entry->sample_count: %d", + group_entry->sample_count); + n_samples += group_entry->sample_count; + if (!info->sample_properties) { + info->sample_properties = + g_ptr_array_new_full (n_samples, + (GDestroyNotify) qtdemux_gst_structure_free); + } else { + g_ptr_array_set_size (info->sample_properties, n_samples); + } + + for (j = 0; j < group_entry->sample_count; j++) { + GstStructure *properties; + + /* group->data might be NULL if group_description_index was zero */ + properties = group_entry->data ? + group_entry->data : info->default_properties; + g_ptr_array_add (info->sample_properties, + gst_structure_copy (properties)); + } + } +} + +/* Returns a pointer to a GstStructure containing the properties of + * the stream sample identified by @sample_index. The caller must unref + * the returned object after use. Returns NULL if unsuccessful. */ +static GstStructure * +qtdemux_get_cenc_sample_properties (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint sample_index) +{ + QtDemuxCencSampleSetInfo *info = NULL; + GstStructure *properties = NULL; + + g_return_val_if_fail (stream != NULL, NULL); + g_return_val_if_fail (stream->protected, NULL); + g_return_val_if_fail (stream->protection_scheme_info != NULL, NULL); + + info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (info->sample_properties && sample_index < info->sample_properties->len) { + properties = g_ptr_array_index (info->sample_properties, sample_index); + g_ptr_array_index (info->sample_properties, sample_index) = NULL; + } + if (!properties) + properties = gst_structure_copy (info->default_properties); + + return properties; +} + +/* Parses the sizes of sample auxiliary information contained within a stream, + * as given in a saiz box. Returns array of sample_count guint8 size values, + * or NULL on failure */ +static guint8 * +qtdemux_parse_saiz (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br, guint32 * sample_count) +{ + guint32 flags = 0; + guint8 *info_sizes; + guint8 default_info_size; + + g_return_val_if_fail (qtdemux != NULL, NULL); + g_return_val_if_fail (stream != NULL, NULL); + g_return_val_if_fail (br != NULL, NULL); + g_return_val_if_fail (sample_count != NULL, NULL); + + if (!gst_byte_reader_get_uint32_be (br, &flags)) + return NULL; + + if (flags & 0x1) { + /* aux_info_type and aux_info_type_parameter are ignored */ + if (!gst_byte_reader_skip (br, 8)) + return NULL; + } + + if (!gst_byte_reader_get_uint8 (br, &default_info_size)) + return NULL; + GST_DEBUG_OBJECT (qtdemux, "default_info_size: %u", default_info_size); + + if (!gst_byte_reader_get_uint32_be (br, sample_count)) + return NULL; + GST_DEBUG_OBJECT (qtdemux, "sample_count: %u", *sample_count); + + + if (default_info_size == 0) { + if (!gst_byte_reader_dup_data (br, *sample_count, &info_sizes)) { + return NULL; + } + } else { + info_sizes = g_new (guint8, *sample_count); + memset (info_sizes, default_info_size, *sample_count); + } + + return info_sizes; +} + +/* Parses the offset of sample auxiliary information contained within a stream, + * as given in a saio box. Returns TRUE if successful; FALSE otherwise. */ +static gboolean +qtdemux_parse_saio (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br, guint32 * info_type, guint32 * info_type_parameter, + guint64 * offset) +{ + guint8 version = 0; + guint32 flags = 0; + guint32 aux_info_type = 0; + guint32 aux_info_type_parameter = 0; + guint32 entry_count; + guint32 off_32; + guint64 off_64; + const guint8 *aux_info_type_data = NULL; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + g_return_val_if_fail (offset != NULL, FALSE); + + if (!gst_byte_reader_get_uint8 (br, &version)) + return FALSE; + + if (!gst_byte_reader_get_uint24_be (br, &flags)) + return FALSE; + + if (flags & 0x1) { + + if (!gst_byte_reader_get_data (br, 4, &aux_info_type_data)) + return FALSE; + aux_info_type = QT_FOURCC (aux_info_type_data); + + if (!gst_byte_reader_get_uint32_be (br, &aux_info_type_parameter)) + return FALSE; + } else if (stream->protected) { + aux_info_type = stream->protection_scheme_type; + } else { + aux_info_type = stream->fourcc; + } + + if (info_type) + *info_type = aux_info_type; + if (info_type_parameter) + *info_type_parameter = aux_info_type_parameter; + + GST_DEBUG_OBJECT (qtdemux, "aux_info_type: '%" GST_FOURCC_FORMAT "', " + "aux_info_type_parameter: %#06x", + GST_FOURCC_ARGS (aux_info_type), aux_info_type_parameter); + + if (!gst_byte_reader_get_uint32_be (br, &entry_count)) + return FALSE; + + if (entry_count != 1) { + GST_ERROR_OBJECT (qtdemux, "multiple offsets are not supported"); + return FALSE; + } + + if (version == 0) { + if (!gst_byte_reader_get_uint32_be (br, &off_32)) + return FALSE; + *offset = (guint64) off_32; + } else { + if (!gst_byte_reader_get_uint64_be (br, &off_64)) + return FALSE; + *offset = off_64; + } + + GST_DEBUG_OBJECT (qtdemux, "offset: %" G_GUINT64_FORMAT, *offset); + return TRUE; +} + +static void +qtdemux_gst_structure_free (GstStructure * gststructure) +{ + if (gststructure) { + gst_structure_free (gststructure); + } +} + +/* Parses auxiliary information relating to samples protected using Common + * Encryption (cenc); the format of this information is defined in + * ISO/IEC 23001-7. Returns TRUE if successful; FALSE otherwise. */ +static gboolean +qtdemux_parse_cenc_aux_info (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br, guint8 * info_sizes, guint32 sample_count) +{ + QtDemuxCencSampleSetInfo *ss_info = NULL; + guint8 size; + gint i; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + g_return_val_if_fail (stream->protected, FALSE); + g_return_val_if_fail (stream->protection_scheme_info != NULL, FALSE); + + ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (ss_info->crypto_info) { + GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info"); + g_ptr_array_free (ss_info->crypto_info, TRUE); + } + + ss_info->crypto_info = + g_ptr_array_new_full (sample_count, + (GDestroyNotify) qtdemux_gst_structure_free); + + qtdemux_prepare_cenc_sample_properties (qtdemux, stream); + + for (i = 0; i < sample_count; ++i) { + GstStructure *properties; + guint16 n_subsamples = 0; + guint8 *data; + guint iv_size; + GstBuffer *buf; + + properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i); + if (properties == NULL) { + GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i); + return FALSE; + } + if (!gst_structure_get_uint (properties, "iv_size", &iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get iv_size for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + if (!gst_byte_reader_dup_data (br, iv_size, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get IV for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + buf = gst_buffer_new_wrapped (data, iv_size); + gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + size = info_sizes[i]; + if (size > iv_size) { + if (!gst_byte_reader_get_uint16_be (br, &n_subsamples) + || !(n_subsamples > 0)) { + gst_structure_free (properties); + GST_ERROR_OBJECT (qtdemux, + "failed to get subsample count for sample %u", i); + return FALSE; + } + GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples); + if (!gst_byte_reader_dup_data (br, n_subsamples * 6, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", + i); + gst_structure_free (properties); + return FALSE; + } + buf = gst_buffer_new_wrapped (data, n_subsamples * 6); + if (!buf) { + gst_structure_free (properties); + return FALSE; + } + gst_structure_set (properties, + "subsample_count", G_TYPE_UINT, n_subsamples, + "subsamples", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } else { + gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); + } + g_ptr_array_add (ss_info->crypto_info, properties); + } + return TRUE; +} + +/* Converts a UUID in raw byte form to a string representation, as defined in + * RFC 4122. The caller takes ownership of the returned string and is + * responsible for freeing it after use. */ +static gchar * +qtdemux_uuid_bytes_to_string (gconstpointer uuid_bytes) +{ + const guint8 *uuid = (const guint8 *) uuid_bytes; + + return g_strdup_printf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); +} + +/* Parses a Protection System Specific Header box (pssh), as defined in the + * Common Encryption (cenc) standard (ISO/IEC 23001-7), which contains + * information needed by a specific content protection system in order to + * decrypt cenc-protected tracks. Returns TRUE if successful; FALSE + * otherwise. */ +static gboolean +qtdemux_parse_pssh (GstQTDemux * qtdemux, GNode * node) +{ + gchar *sysid_string; + guint32 pssh_size = QT_UINT32 (node->data); + GstBuffer *pssh = NULL; + GstEvent *event = NULL; + guint32 parent_box_type; + gint i; + + if (G_UNLIKELY (pssh_size < 32U)) { + GST_ERROR_OBJECT (qtdemux, "invalid box size"); + return FALSE; + } + + sysid_string = + qtdemux_uuid_bytes_to_string ((const guint8 *) node->data + 12); + + if (qtdemux->protection_system_ids && qtdemux->protection_system_ids->len > 0) { + GST_DEBUG_OBJECT (qtdemux, "we've got protection_system id's already"); + } else { + gst_qtdemux_append_protection_system_id (qtdemux, sysid_string); + } + + pssh = gst_buffer_new_wrapped (g_memdup (node->data, pssh_size), pssh_size); + GST_LOG_OBJECT (qtdemux, "cenc pssh size: %" G_GSIZE_FORMAT, + gst_buffer_get_size (pssh)); + + parent_box_type = QT_FOURCC ((const guint8 *) node->parent->data + 4); + + /* Push an event containing the pssh box onto the queues of all streams. */ + event = gst_event_new_protection (sysid_string, pssh, + (parent_box_type == FOURCC_moov) ? "isobmff/moov" : "isobmff/moof"); + for (i = 0; i < qtdemux->n_streams; ++i) { + g_queue_push_tail (&qtdemux->streams[i]->protection_scheme_event_queue, + gst_event_ref (event)); + } + g_free (sysid_string); + gst_event_unref (event); + gst_buffer_unref (pssh); + return TRUE; +} + +/* Parses Sample Encryption Box('senc') which contains the sample + * specific encryption data, including the initialization vectors + * needed for decryption and, optionally, alternative decryption + * parameters ; the format of this information is defined in + * Common File Format(CFF) derived from the ISO Base Media File + * Format 'iso6' brand specified in [ISO]. + * Returns TRUE if successful; FALSE otherwise. */ +static gboolean +qtdemux_parse_senc (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br) +{ + QtDemuxCencSampleSetInfo *ss_info = NULL; + gint i; + guint32 flags, sample_count; + gboolean uses_sub_sample_encryption = FALSE; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + g_return_val_if_fail (stream->protected, FALSE); + g_return_val_if_fail (stream->protection_scheme_info != NULL, FALSE); + + ss_info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + if (ss_info->crypto_info) { + GST_LOG_OBJECT (qtdemux, "unreffing existing crypto_info"); + g_ptr_array_free (ss_info->crypto_info, TRUE); + } + + if (!gst_byte_reader_skip (br, 1) + || !gst_byte_reader_get_uint24_be (br, &flags)) { + GST_ERROR_OBJECT (qtdemux, "failed to get flags"); + return FALSE; + } + + uses_sub_sample_encryption = flags & 0x000002; + + if (!gst_byte_reader_get_uint32_be (br, &sample_count)) { + GST_ERROR_OBJECT (qtdemux, "failed to get sample_count for sample"); + return FALSE; + } + + ss_info->crypto_info = + g_ptr_array_new_full (sample_count, + (GDestroyNotify) qtdemux_gst_structure_free); + + qtdemux_prepare_cenc_sample_properties (qtdemux, stream); + + for (i = 0; i < sample_count; ++i) { + GstStructure *properties; + guint16 n_subsamples = 0; + guint8 *data; + guint iv_size; + GstBuffer *buf; + + properties = qtdemux_get_cenc_sample_properties (qtdemux, stream, i); + if (properties == NULL) { + GST_ERROR_OBJECT (qtdemux, "failed to get properties for sample %u", i); + return FALSE; + } + if (!gst_structure_get_uint (properties, "iv_size", &iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get iv_size for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + if (!gst_byte_reader_dup_data (br, iv_size, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get IV for sample %u", i); + gst_structure_free (properties); + return FALSE; + } + buf = gst_buffer_new_wrapped (data, iv_size); + gst_structure_set (properties, "iv", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + if (uses_sub_sample_encryption) { + if (!gst_byte_reader_get_uint16_be (br, &n_subsamples) + || !(n_subsamples > 0)) { + gst_structure_free (properties); + GST_ERROR_OBJECT (qtdemux, + "failed to get subsample count for sample %u", i); + return FALSE; + } + GST_LOG_OBJECT (qtdemux, "subsample count: %u", n_subsamples); + if (!gst_byte_reader_dup_data (br, n_subsamples * 6, &data)) { + GST_ERROR_OBJECT (qtdemux, "failed to get subsample data for sample %u", + i); + gst_structure_free (properties); + return FALSE; + } + buf = gst_buffer_new_wrapped (data, n_subsamples * 6); + if (!buf) { + gst_structure_free (properties); + return FALSE; + } + gst_structure_set (properties, + "subsample_count", G_TYPE_UINT, n_subsamples, + "subsamples", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } else { + gst_structure_set (properties, "subsample_count", G_TYPE_UINT, 0, NULL); + } + g_ptr_array_add (ss_info->crypto_info, properties); + } + return TRUE; +} + +static gboolean +qtdemux_parse_sbgp (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br) +{ + guint32 version, grouping_type, entry_count; + guint32 grouping_type_parameter = 0; + guint i; + QtDemuxSampleToGroup *sample_to_group = NULL; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + + if (!gst_byte_reader_get_uint32_be (br, &version)) + return FALSE; + + version >>= 24; + + if (!qt_atom_parser_get_fourcc (br, &grouping_type)) + return FALSE; + + if (version == 1) { + if (!gst_byte_reader_get_uint32_be (br, &grouping_type_parameter)) { + return FALSE; + } + } + + switch (grouping_type) { + case FOURCC_seig: + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { + GST_ERROR_OBJECT (qtdemux, "Common Encryption is required for seig"); + return FALSE; + } + break; + default: + /* FIXME: now we consider only seig type for protected stream. + * There might be informal types more */ + GST_DEBUG_OBJECT (qtdemux, "Unknown grouping_type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (grouping_type)); + return FALSE; + } + + if (!gst_byte_reader_get_uint32_be (br, &entry_count)) + return FALSE; + + sample_to_group = g_new0 (QtDemuxSampleToGroup, 1); + sample_to_group->sbgp_entries = + g_new0 (QtDemuxSampleToGroupEntry, entry_count); + sample_to_group->sbgp_entries_length = entry_count; + sample_to_group->entry_free_func = + (GDestroyNotify) qtdemux_gst_structure_free; + + sample_to_group->grouping_type = grouping_type; + sample_to_group->grouping_type_parameter = grouping_type_parameter; + + GST_DEBUG_OBJECT (qtdemux, "grouping_type: %" GST_FOURCC_FORMAT + ", entry_count: %" G_GUINT32_FORMAT, + GST_FOURCC_ARGS (grouping_type), entry_count); + + for (i = 0; i < entry_count; i++) { + QtDemuxSampleToGroupEntry *entry = &sample_to_group->sbgp_entries[i]; + guint32 sample_count; + guint32 group_description_idx; + + if (!gst_byte_reader_get_uint32_be (br, &sample_count)) + goto failed; + if (!gst_byte_reader_get_uint32_be (br, &group_description_idx)) + goto failed; + + GST_LOG_OBJECT (qtdemux, "entry %u (sample_count: %" G_GUINT32_FORMAT "), " + "group_description_index: %" G_GUINT32_FORMAT, + i, sample_count, group_description_idx); + + entry->sample_count = sample_count; + entry->group_description_idx = group_description_idx; + } + + stream->sample_to_group = + g_list_append (stream->sample_to_group, sample_to_group); + + return TRUE; + +failed: + GST_ERROR_OBJECT (qtdemux, "Failed to parse SampleToGroup box"); + if (sample_to_group) + gst_qtdemux_sample_to_group_free_internal (sample_to_group); + + return FALSE; +} + +static gboolean +qtdemux_parse_seig (GstQTDemux * qtdemux, QtDemuxStream * stream, + QtDemuxSampleToGroupEntry * group_entry, GstByteReader * br) +{ + guint8 crypt_byte_block = 0; + guint8 skip_byte_block = 0; + guint8 *kid; + guint8 is_protected, iv_size; + GstBuffer *kid_buf; + guint8 constant_iv_size = 0; + guint8 *constant_iv = NULL; + GstBuffer *constant_iv_buf = NULL; + + if (!gst_byte_reader_skip (br, 1)) + return FALSE; + + if (stream->protection_scheme_type == FOURCC_cens || + stream->protection_scheme_type == FOURCC_cbcs) { + crypt_byte_block = QT_UINT8 ((guint8 *) br->data) >> 4; + skip_byte_block = QT_UINT8 ((guint8 *) br->data) & 0x0F; + } + if (!gst_byte_reader_get_uint8 (br, &is_protected)) { + GST_ERROR_OBJECT (qtdemux, "failed to get is protected"); + return FALSE; + } + if (!gst_byte_reader_get_uint8 (br, &iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get iv size"); + return FALSE; + } + if (!gst_byte_reader_dup_data (br, 16, &kid)) { + GST_ERROR_OBJECT (qtdemux, "failed to get kid"); + return FALSE; + } + kid_buf = gst_buffer_new_wrapped (kid, 16); + + if (iv_size == 0) { + if (!gst_byte_reader_get_uint8 (br, &constant_iv_size)) { + GST_ERROR_OBJECT (qtdemux, "failed to get constant_iv_size"); + return FALSE; + } + + if (!gst_byte_reader_dup_data (br, constant_iv_size, &constant_iv)) { + GST_ERROR_OBJECT (qtdemux, "failed to get constant iv"); + return FALSE; + } + + constant_iv_buf = gst_buffer_new_wrapped (constant_iv, constant_iv_size); + } + if (G_UNLIKELY (group_entry->data)) { + gst_structure_free ((GstStructure *) group_entry->data); + } + group_entry->data = + gst_structure_new ("application/x-cenc", + "crypt_byte_block", G_TYPE_UINT, crypt_byte_block, + "skip_byte_block", G_TYPE_UINT, skip_byte_block, + "iv_size", G_TYPE_UINT, iv_size, + "encrypted", G_TYPE_BOOLEAN, (is_protected == 1), + "kid", GST_TYPE_BUFFER, kid_buf, NULL); + + if (constant_iv_size != 0 && constant_iv_buf != NULL) { + gst_structure_set (group_entry->data, + "constant_iv_size", G_TYPE_UINT, constant_iv_size, + "constant_iv", GST_TYPE_BUFFER, constant_iv_buf, NULL); + gst_buffer_unref (constant_iv_buf); + } + + GST_DEBUG_OBJECT (qtdemux, "sample properties from seig: " + "is_encrypted=%u, iv_size=%u", is_protected, iv_size); + gst_buffer_unref (kid_buf); + + return TRUE; +} + +static gboolean +qtdemux_parse_sgpd (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstByteReader * br) +{ + guint32 version = 0; + guint32 grouping_type; + guint32 default_length = 0, default_sample_description_idx = 0; + guint32 entry_count; + guint i; + QtDemuxSampleToGroup *ref_sbgp = NULL; + QtDemuxSampleToGroupEntry *group_entry; + GList *iter; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (br != NULL, FALSE); + + if (!gst_byte_reader_get_uint32_be (br, &version)) + return FALSE; + + if (!qt_atom_parser_get_fourcc (br, &grouping_type)) + return FALSE; + + switch (grouping_type) { + case FOURCC_seig: + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { + GST_ERROR_OBJECT (qtdemux, "Common Encryption is required for seig"); + return FALSE; + } + break; + default: + /* FIXME: now we consider only seig type for protected stream. + * There might be informal types more */ + GST_DEBUG_OBJECT (qtdemux, "Unknown grouping_type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (grouping_type)); + return FALSE; + } + + for (iter = stream->sample_to_group; iter; iter = g_list_next (iter)) { + QtDemuxSampleToGroup *sbgp = (QtDemuxSampleToGroup *) iter->data; + if (sbgp->grouping_type == grouping_type) { + ref_sbgp = sbgp; + break; + } + } + + if (!ref_sbgp) { + GST_ERROR_OBJECT (qtdemux, "Cannot find matching SampleToGroup"); + return FALSE; + } + + version >>= 24; + if (version == 1 && !gst_byte_reader_get_uint32_be (br, &default_length)) { + return FALSE; + } else if (version > 1 && + !gst_byte_reader_get_uint32_be (br, &default_sample_description_idx)) { + return FALSE; + } + + if (!gst_byte_reader_get_uint32_be (br, &entry_count)) + return FALSE; + + GST_DEBUG_OBJECT (qtdemux, "grouping_type: %" GST_FOURCC_FORMAT + ", entry_count: %" G_GUINT32_FORMAT, + GST_FOURCC_ARGS (grouping_type), entry_count); + + group_entry = ref_sbgp->sbgp_entries; + + for (i = 0; i < entry_count; i++) { + /* group_description_idx of value zero indicates + * that this sample is a member of no group of this type */ + while (group_entry->group_description_idx == 0) + group_entry++; + + if (!qtdemux_parse_seig (qtdemux, stream, group_entry, br)) + return FALSE; + + group_entry++; + } + + return TRUE; +} + +static gboolean +qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, + guint64 moof_offset, QtDemuxStream * stream) +{ + GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node, *mfhd_node; + GNode *uuid_node; + GstByteReader mfhd_data, trun_data, tfhd_data, tfdt_data; + GNode *saiz_node, *saio_node, *pssh_node; + GstByteReader saiz_data, saio_data; + GNode *senc_node; + GstByteReader senc_data; + guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; + gint64 base_offset, running_offset; + guint32 frag_num; + GNode *sbgp_node, *sgpd_node; + GstByteReader sbgp_data, sgpd_data; + + /* NOTE @stream ignored */ + + moof_node = g_node_new ((guint8 *) buffer); + qtdemux_parse_node (qtdemux, moof_node, buffer, length); + qtdemux_node_dump (qtdemux, moof_node); + + /* Get fragment number from mfhd and check it's valid */ + mfhd_node = + qtdemux_tree_get_child_by_type_full (moof_node, FOURCC_mfhd, &mfhd_data); + if (mfhd_node == NULL) + goto missing_mfhd; + if (!qtdemux_parse_mfhd (qtdemux, &mfhd_data, &frag_num)) + goto fail; + GST_DEBUG_OBJECT (qtdemux, "Fragment #%d", frag_num); + + /* unknown base_offset to start with */ + base_offset = running_offset = -1; + traf_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_traf); + while (traf_node) { + guint64 decode_time = 0; + + /* Fragment Header node */ + tfhd_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfhd, + &tfhd_data); + if (!tfhd_node) + goto missing_tfhd; + if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &stream, &ds_duration, + &ds_size, &ds_flags, &base_offset)) + goto missing_tfhd; + + /* The following code assumes at most a single set of sample auxiliary + * data in the fragment (consisting of a saiz box and a corresponding saio + * box); in theory, however, there could be multiple sets of sample + * auxiliary data in a fragment. */ + senc_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_senc, + &senc_data); + if (senc_node) { + if (!qtdemux_parse_senc (qtdemux, stream, &senc_data)) { + GST_ERROR_OBJECT (qtdemux, "failed to parse senc box"); + goto fail; + } + } else { + saiz_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saiz, + &saiz_data); + if (saiz_node) { + guint32 info_type = 0; + guint64 offset = 0; + guint32 info_type_parameter = 0; + + g_free (qtdemux->cenc_aux_info_sizes); + + qtdemux->cenc_aux_info_sizes = + qtdemux_parse_saiz (qtdemux, stream, &saiz_data, + &qtdemux->cenc_aux_sample_count); + if (qtdemux->cenc_aux_info_sizes == NULL) { + GST_ERROR_OBJECT (qtdemux, "failed to parse saiz box"); + goto fail; + } + saio_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_saio, + &saio_data); + if (!saio_node) { + GST_ERROR_OBJECT (qtdemux, + "saiz box without a corresponding saio box"); + g_free (qtdemux->cenc_aux_info_sizes); + qtdemux->cenc_aux_info_sizes = NULL; + goto fail; + } + + if (G_UNLIKELY (!qtdemux_parse_saio (qtdemux, stream, &saio_data, + &info_type, &info_type_parameter, &offset))) { + GST_ERROR_OBJECT (qtdemux, "failed to parse saio box"); + g_free (qtdemux->cenc_aux_info_sizes); + qtdemux->cenc_aux_info_sizes = NULL; + goto fail; + } + if (base_offset > -1 && base_offset > qtdemux->moof_offset) + offset += (guint64) (base_offset - qtdemux->moof_offset); + if (is_common_enc_scheme_type (info_type) && info_type_parameter == 0U) { + GstByteReader br; + if (offset > length) { + GST_DEBUG_OBJECT (qtdemux, + "cenc auxiliary info stored out of moof"); + qtdemux->cenc_aux_info_offset = offset; + } else { + gst_byte_reader_init (&br, buffer + offset, length - offset); + if (!qtdemux_parse_cenc_aux_info (qtdemux, stream, &br, + qtdemux->cenc_aux_info_sizes, + qtdemux->cenc_aux_sample_count)) { + GST_ERROR_OBJECT (qtdemux, "failed to parse cenc auxiliary info"); + g_free (qtdemux->cenc_aux_info_sizes); + qtdemux->cenc_aux_info_sizes = NULL; + goto fail; + } + } + } + } + } + + sbgp_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sbgp, + &sbgp_data); + while (sbgp_node) { + qtdemux_parse_sbgp (qtdemux, stream, &sbgp_data); + /* iterate all siblings */ + sbgp_node = qtdemux_tree_get_sibling_by_type_full (sbgp_node, FOURCC_sbgp, + &sbgp_data); + } + + sgpd_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_sgpd, + &sgpd_data); + + while (sgpd_node) { + qtdemux_parse_sgpd (qtdemux, stream, &sgpd_data); + /* iterate all siblings */ + sgpd_node = qtdemux_tree_get_sibling_by_type_full (sgpd_node, FOURCC_sgpd, + &sgpd_data); + } + + if (stream == NULL) + goto fail; + + tfdt_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfdt, + &tfdt_data); + if (tfdt_node) { + GstClockTime decode_time_ts; + + /* We'll use decode_time to interpolate timestamps + * in case the input timestamps are missing */ + qtdemux_parse_tfdt (qtdemux, &tfdt_data, &decode_time); + + decode_time_ts = QTSTREAMTIME_TO_GSTTIME (stream, decode_time); + + if (qtdemux->major_brand == FOURCC_mpuf) { + if (frag_num == 1 && qtdemux->upstream_format_is_time && + GST_CLOCK_TIME_IS_VALID (decode_time_ts) && decode_time_ts != 0) { + /* FIXME: the first pts should be indicated by mpu_presentation_time. + * In some erroneous cases, however, it's possible that both mpu_presentation_time + * (indicated by upstream) and non-zero baseDecodeTime (written in tfdt) + * exist. To handle this case, let's subtract the first moof's baseDecodeTime + * to the all moof's which belong to the mpu */ + GST_DEBUG_OBJECT (qtdemux, "nonzero baseDecodeTime %" GST_TIME_FORMAT + " in the tfdt of the fisrt moof", GST_TIME_ARGS (decode_time_ts)); + qtdemux->upstream_basetime_offset = decode_time_ts; + } + } else { + qtdemux->upstream_basetime_offset = GST_CLOCK_TIME_NONE; + } + + if (qtdemux->upstream_format_is_time && + GST_CLOCK_TIME_IS_VALID (qtdemux->upstream_basetime)) { + if (GST_CLOCK_TIME_IS_VALID (qtdemux->upstream_basetime_offset) + && qtdemux->upstream_basetime >= qtdemux->upstream_basetime_offset) { + GST_DEBUG_OBJECT (qtdemux, "Add (upstream_basetime %" GST_TIME_FORMAT + " - upstream_basetime_offset %" GST_TIME_FORMAT + ") to decode time", GST_TIME_ARGS (qtdemux->upstream_basetime), + GST_TIME_ARGS (qtdemux->upstream_basetime_offset)); + decode_time_ts += + (qtdemux->upstream_basetime - qtdemux->upstream_basetime_offset); + } else { + GST_DEBUG_OBJECT (qtdemux, "Add upstream_basetime %" GST_TIME_FORMAT + " to decode time", GST_TIME_ARGS (qtdemux->upstream_basetime)); + decode_time_ts += qtdemux->upstream_basetime; + } + } + + GST_INFO_OBJECT (qtdemux, "decode time %" G_GINT64_FORMAT + " (%" GST_TIME_FORMAT ")", decode_time, + GST_TIME_ARGS (decode_time_ts)); + + /* Discard the fragment buffer timestamp info to avoid using it. + * Rely on tfdt instead as it is more accurate than the timestamp + * that is fetched from a manifest/playlist and is usually + * less accurate. */ + qtdemux->fragment_start = -1; + +#ifdef ATSC3_MODE + /* FIXME: do we need to handle PTS discont. as follows ? */ + if (GST_CLOCK_TIME_IS_VALID (qtdemux->prev_decode_time) && + (qtdemux->prev_decode_time > decode_time_ts) && qtdemux->atsc3_mode && + (qtdemux->n_video_streams > 0 || qtdemux->n_audio_streams > 0)) { + GST_INFO_OBJECT (qtdemux, "decode time discont. from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->prev_decode_time), + GST_TIME_ARGS (decode_time_ts)); + + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("PTS discont, Cannot be played.")), (NULL)); + } + + qtdemux->prev_decode_time = decode_time_ts; +#endif + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + qtdemux->dash_fragment_start = decode_time; + if (QTDEMUX_IS_CUSTOM_MODE (qtdemux) && qtdemux->pending_newsegment) { + GstSegment segment; + guint64 updated_start_time = GST_CLOCK_TIME_NONE; + guint64 time = GST_CLOCK_TIME_NONE; + + gst_event_copy_segment (qtdemux->pending_newsegment, &segment); + if (segment.format == GST_FORMAT_TIME) { + segment.start = decode_time_ts; + + g_signal_emit (qtdemux, qtdemux_signals[START_TIME], 0, + &segment.start, &updated_start_time, &time); + + segment.start = updated_start_time; + segment.time = time; + segment.position = time; + + GST_INFO_OBJECT (qtdemux, + "segment start time %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment.start), GST_TIME_ARGS (segment.time)); + + if (qtdemux->dash_segment_start == GST_CLOCK_TIME_NONE) { + qtdemux->dash_segment_start = segment.start; + } else { + segment.start = qtdemux->dash_segment_start; + } + + gst_event_replace (&qtdemux->pending_newsegment, + gst_event_new_segment (&segment)); + /* ref added when replaced, release the original _new one */ + gst_event_unref (qtdemux->pending_newsegment); + } + } +#endif + } + + if (G_UNLIKELY (!stream)) { + /* we lost track of offset, we'll need to regain it, + * but can delay complaining until later or avoid doing so altogether */ + base_offset = -2; + goto next; + } + if (G_UNLIKELY (base_offset < -1)) + goto lost_offset; + + /* In case of MMT, we will reset samples per MPU (not per moof) */ + if (qtdemux->upstream_format_is_time && qtdemux->major_brand != FOURCC_mpuf) + gst_qtdemux_stream_flush_samples_data (qtdemux, stream); + + /* initialise moof sample data */ + stream->n_samples_moof = 0; + stream->duration_moof = 0; + + /* Track Run node */ + trun_node = + qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_trun, + &trun_data); + + while (trun_node) { + qtdemux_parse_trun (qtdemux, &trun_data, stream, + ds_duration, ds_size, ds_flags, moof_offset, length, &base_offset, + &running_offset, decode_time); + /* iterate all siblings */ + trun_node = qtdemux_tree_get_sibling_by_type_full (trun_node, FOURCC_trun, + &trun_data); + } + + uuid_node = qtdemux_tree_get_child_by_type (traf_node, FOURCC_uuid); + if (uuid_node) { + guint8 *uuid_buffer = (guint8 *) uuid_node->data; + guint32 box_length = QT_UINT32 (uuid_buffer); + + qtdemux_parse_uuid (qtdemux, uuid_buffer, box_length); + } + + /* if no new base_offset provided for next traf, + * base is end of current traf */ + base_offset = running_offset; + running_offset = -1; + + if (stream->n_samples_moof && stream->duration_moof) + stream->new_caps = TRUE; + + next: + /* iterate all siblings */ + traf_node = qtdemux_tree_get_sibling_by_type (traf_node, FOURCC_traf); + } + + /* parse any protection system info */ + pssh_node = qtdemux_tree_get_child_by_type (moof_node, FOURCC_pssh); + while (pssh_node) { + GST_LOG_OBJECT (qtdemux, "Parsing pssh box."); + qtdemux_parse_pssh (qtdemux, pssh_node); + pssh_node = qtdemux_tree_get_sibling_by_type (pssh_node, FOURCC_pssh); + } + + g_node_destroy (moof_node); + return TRUE; + +missing_tfhd: + { + GST_DEBUG_OBJECT (qtdemux, "missing tfhd box"); + goto fail; + } +missing_mfhd: + { + GST_DEBUG_OBJECT (qtdemux, "Missing mfhd box"); + goto fail; + } +lost_offset: + { + GST_DEBUG_OBJECT (qtdemux, "lost offset"); + goto fail; + } +fail: + { + g_node_destroy (moof_node); + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + return FALSE; + } +} + +#if 0 +/* might be used if some day we actually use mfra & co + * for random access to fragments, + * but that will require quite some modifications and much less relying + * on a sample array */ +#endif + +static gboolean +qtdemux_parse_tfra (GstQTDemux * qtdemux, GNode * tfra_node) +{ + QtDemuxStream *stream; + guint32 ver_flags, track_id, len, num_entries, i; + guint value_size, traf_size, trun_size, sample_size; + guint64 time = 0, moof_offset = 0; +#if 0 + GstBuffer *buf = NULL; + GstFlowReturn ret; +#endif + GstByteReader tfra; + + gst_byte_reader_init (&tfra, tfra_node->data, QT_UINT32 (tfra_node->data)); + + if (!gst_byte_reader_skip (&tfra, 8)) + return FALSE; + + if (!gst_byte_reader_get_uint32_be (&tfra, &ver_flags)) + return FALSE; + + if (!gst_byte_reader_get_uint32_be (&tfra, &track_id) + || !gst_byte_reader_get_uint32_be (&tfra, &len) + || !gst_byte_reader_get_uint32_be (&tfra, &num_entries)) + return FALSE; + + GST_DEBUG_OBJECT (qtdemux, "parsing tfra box for track id %u", track_id); + + stream = qtdemux_find_stream (qtdemux, track_id); + if (stream == NULL) + goto unknown_trackid; + + value_size = ((ver_flags >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + sample_size = (len & 3) + 1; + trun_size = ((len & 12) >> 2) + 1; + traf_size = ((len & 48) >> 4) + 1; + + GST_DEBUG_OBJECT (qtdemux, "%u entries, sizes: value %u, traf %u, trun %u, " + "sample %u", num_entries, value_size, traf_size, trun_size, sample_size); + + if (num_entries == 0) + goto no_samples; + + if (!qt_atom_parser_has_chunks (&tfra, num_entries, + value_size + value_size + traf_size + trun_size + sample_size)) + goto corrupt_file; + + g_free (stream->ra_entries); + stream->ra_entries = g_new (QtDemuxRandomAccessEntry, num_entries); + stream->n_ra_entries = num_entries; + + for (i = 0; i < num_entries; i++) { + qt_atom_parser_get_offset (&tfra, value_size, &time); + qt_atom_parser_get_offset (&tfra, value_size, &moof_offset); + qt_atom_parser_get_uint_with_size_unchecked (&tfra, traf_size); + qt_atom_parser_get_uint_with_size_unchecked (&tfra, trun_size); + qt_atom_parser_get_uint_with_size_unchecked (&tfra, sample_size); + + time = QTSTREAMTIME_TO_GSTTIME (stream, time); + + GST_LOG_OBJECT (qtdemux, "fragment time: %" GST_TIME_FORMAT ", " + " moof_offset: %" G_GUINT64_FORMAT, GST_TIME_ARGS (time), moof_offset); + + stream->ra_entries[i].ts = time; + stream->ra_entries[i].moof_offset = moof_offset; + + /* don't want to go through the entire file and read all moofs at startup */ +#if 0 + ret = gst_qtdemux_pull_atom (qtdemux, moof_offset, 0, &buf); + if (ret != GST_FLOW_OK) + goto corrupt_file; + qtdemux_parse_moof (qtdemux, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), + moof_offset, stream); + gst_buffer_unref (buf); +#endif + } + + if (!qtdemux->upstream_format_is_time) + check_update_duration (qtdemux, time); + + return TRUE; + +/* ERRORS */ +unknown_trackid: + { + GST_WARNING_OBJECT (qtdemux, "Couldn't find stream for track %u", track_id); + return FALSE; + } +corrupt_file: + { + GST_WARNING_OBJECT (qtdemux, "broken traf box, ignoring"); + return FALSE; + } +no_samples: + { + GST_WARNING_OBJECT (qtdemux, "stream has no samples"); + return FALSE; + } +} + +static gboolean +qtdemux_pull_mfro_mfra (GstQTDemux * qtdemux) +{ + GstMapInfo mfro_map = GST_MAP_INFO_INIT; + GstMapInfo mfra_map = GST_MAP_INFO_INIT; + GstBuffer *mfro = NULL, *mfra = NULL; + GstFlowReturn flow; + gboolean ret = FALSE; + GNode *mfra_node, *tfra_node; + guint64 mfra_offset = 0; + guint32 fourcc, mfra_size; + gint64 len; + + /* query upstream size in bytes */ + if (!gst_pad_peer_query_duration (qtdemux->sinkpad, GST_FORMAT_BYTES, &len)) + goto size_query_failed; + + /* mfro box should be at the very end of the file */ + flow = gst_qtdemux_pull_atom (qtdemux, len - 16, 16, &mfro); + if (flow != GST_FLOW_OK) + goto exit; + + gst_buffer_map (mfro, &mfro_map, GST_MAP_READ); + + fourcc = QT_FOURCC (mfro_map.data + 4); + if (fourcc != FOURCC_mfro) + goto exit; + + GST_INFO_OBJECT (qtdemux, "Found mfro box"); + if (mfro_map.size < 16) + goto invalid_mfro_size; + + mfra_size = QT_UINT32 (mfro_map.data + 12); + if (mfra_size >= len) + goto invalid_mfra_size; + + mfra_offset = len - mfra_size; + + GST_INFO_OBJECT (qtdemux, "mfra offset: %" G_GUINT64_FORMAT ", size %u", + mfra_offset, mfra_size); + + /* now get and parse mfra box */ + flow = gst_qtdemux_pull_atom (qtdemux, mfra_offset, mfra_size, &mfra); + if (flow != GST_FLOW_OK) + goto broken_file; + + gst_buffer_map (mfra, &mfra_map, GST_MAP_READ); + + mfra_node = g_node_new ((guint8 *) mfra_map.data); + qtdemux_parse_node (qtdemux, mfra_node, mfra_map.data, mfra_map.size); + + tfra_node = qtdemux_tree_get_child_by_type (mfra_node, FOURCC_tfra); + + while (tfra_node) { + qtdemux_parse_tfra (qtdemux, tfra_node); + /* iterate all siblings */ + tfra_node = qtdemux_tree_get_sibling_by_type (tfra_node, FOURCC_tfra); + } + g_node_destroy (mfra_node); + + GST_INFO_OBJECT (qtdemux, "parsed movie fragment random access box (mfra)"); + ret = TRUE; + +exit: + + if (mfro) { + if (mfro_map.memory != NULL) + gst_buffer_unmap (mfro, &mfro_map); + gst_buffer_unref (mfro); + } + if (mfra) { + if (mfra_map.memory != NULL) + gst_buffer_unmap (mfra, &mfra_map); + gst_buffer_unref (mfra); + } + return ret; + +/* ERRORS */ +size_query_failed: + { + GST_WARNING_OBJECT (qtdemux, "could not query upstream size"); + goto exit; + } +invalid_mfro_size: + { + GST_WARNING_OBJECT (qtdemux, "mfro size is too small"); + goto exit; + } +invalid_mfra_size: + { + GST_WARNING_OBJECT (qtdemux, "mfra_size in mfro box is invalid"); + goto exit; + } +broken_file: + { + GST_WARNING_OBJECT (qtdemux, "bogus mfra offset or size, broken file"); + goto exit; + } +} + +static guint64 +add_offset (guint64 offset, guint64 advance) +{ + /* Avoid 64-bit overflow by clamping */ + if (offset > G_MAXUINT64 - advance) + return G_MAXUINT64; + return offset + advance; +} + +static GstFlowReturn +gst_qtdemux_loop_state_header (GstQTDemux * qtdemux) +{ + guint64 length = 0; + guint32 fourcc = 0; + GstBuffer *buf = NULL; + GstFlowReturn ret = GST_FLOW_OK; + guint64 cur_offset = qtdemux->offset; + GstMapInfo map; + + ret = gst_pad_pull_range (qtdemux->sinkpad, cur_offset, 16, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto beach; + gst_buffer_map (buf, &map, GST_MAP_READ); + if (G_LIKELY (map.size >= 8)) + extract_initial_length_and_fourcc (map.data, map.size, &length, &fourcc); + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + + /* maybe we already got most we needed, so only consider this eof */ + if (G_UNLIKELY (length == 0)) { + GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX, + (_("Invalid atom size.")), + ("Header atom '%" GST_FOURCC_FORMAT "' has empty length", + GST_FOURCC_ARGS (fourcc))); + ret = GST_FLOW_EOS; + goto beach; + } + + switch (fourcc) { + case FOURCC_moof: + /* record for later parsing when needed */ + if (!qtdemux->moof_offset) { + qtdemux->moof_offset = qtdemux->offset; + } + if (qtdemux_pull_mfro_mfra (qtdemux)) { + /* FIXME */ + } else { + qtdemux->offset += length; /* skip moof and keep going */ + } + if (qtdemux->got_moov) { + GST_INFO_OBJECT (qtdemux, "moof header, got moov, done with headers"); + ret = GST_FLOW_EOS; + goto beach; + } + break; + case FOURCC_mdat: + case FOURCC_free: + case FOURCC_wide: + case FOURCC_PICT: + case FOURCC_pnot: + { + GST_LOG_OBJECT (qtdemux, + "skipping atom '%" GST_FOURCC_FORMAT "' at %" G_GUINT64_FORMAT, + GST_FOURCC_ARGS (fourcc), cur_offset); + qtdemux->offset = add_offset (qtdemux->offset, length); + break; + } + case FOURCC_moov: + { + GstBuffer *moov = NULL; + + if (qtdemux->got_moov) { + GST_DEBUG_OBJECT (qtdemux, "Skipping moov atom as we have one already"); + qtdemux->offset = add_offset (qtdemux->offset, length); + goto beach; + } + + ret = gst_pad_pull_range (qtdemux->sinkpad, cur_offset, length, &moov); + if (ret != GST_FLOW_OK) + goto beach; + gst_buffer_map (moov, &map, GST_MAP_READ); + + if (length != map.size) { + /* Some files have a 'moov' atom at the end of the file which contains + * a terminal 'free' atom where the body of the atom is missing. + * Check for, and permit, this special case. + */ + if (map.size >= 8) { + guint8 *final_data = map.data + (map.size - 8); + guint32 final_length = QT_UINT32 (final_data); + guint32 final_fourcc = QT_FOURCC (final_data + 4); + + if (final_fourcc == FOURCC_free + && map.size + final_length - 8 == length) { + /* Ok, we've found that special case. Allocate a new buffer with + * that free atom actually present. */ + GstBuffer *newmoov = gst_buffer_new_and_alloc (length); + gst_buffer_fill (newmoov, 0, map.data, map.size); + gst_buffer_memset (newmoov, map.size, 0, final_length - 8); + gst_buffer_unmap (moov, &map); + gst_buffer_unref (moov); + moov = newmoov; + gst_buffer_map (moov, &map, GST_MAP_READ); + } + } + } + + if (length != map.size) { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is incomplete and cannot be played.")), + ("We got less than expected (received %" G_GSIZE_FORMAT + ", wanted %u, offset %" G_GUINT64_FORMAT ")", map.size, + (guint) length, cur_offset)); + gst_buffer_unmap (moov, &map); + gst_buffer_unref (moov); + ret = GST_FLOW_ERROR; + goto beach; + } + qtdemux->offset += length; + + qtdemux_parse_moov (qtdemux, map.data, length); + qtdemux_node_dump (qtdemux, qtdemux->moov_node); + + qtdemux_parse_tree (qtdemux); + + g_node_destroy (qtdemux->moov_node); + gst_buffer_unmap (moov, &map); + gst_buffer_unref (moov); + qtdemux->moov_node = NULL; + qtdemux->got_moov = TRUE; + + break; + } + case FOURCC_ftyp: + { + GstBuffer *ftyp = NULL; + + /* extract major brand; might come in handy for ISO vs QT issues */ + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &ftyp); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (ftyp, &map, GST_MAP_READ); + qtdemux_parse_ftyp (qtdemux, map.data, map.size); + gst_buffer_unmap (ftyp, &map); + gst_buffer_unref (ftyp); + break; + } + case FOURCC_uuid: + { + GstBuffer *uuid = NULL; + + /* uuid are extension atoms */ + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &uuid); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (uuid, &map, GST_MAP_READ); + qtdemux_parse_uuid (qtdemux, map.data, map.size); + gst_buffer_unmap (uuid, &map); + gst_buffer_unref (uuid); + break; + } + case FOURCC_sidx: + { + GstBuffer *sidx = NULL; + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &sidx); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (sidx, &map, GST_MAP_READ); + qtdemux_parse_sidx (qtdemux, map.data, map.size); + gst_buffer_unmap (sidx, &map); + gst_buffer_unref (sidx); + break; + } + case FOURCC_mmpu: + { + GstBuffer *mmpu = NULL; + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &mmpu); + if (ret != GST_FLOW_OK) + goto beach; + qtdemux->offset += length; + gst_buffer_map (mmpu, &map, GST_MAP_READ); + qtdemux_parse_mmpu (qtdemux, map.data, map.size); + gst_buffer_unmap (mmpu, &map); + gst_buffer_unref (mmpu); + break; + } + default: + { + GstBuffer *unknown = NULL; + + GST_LOG_OBJECT (qtdemux, + "unknown %08x '%" GST_FOURCC_FORMAT "' of size %" G_GUINT64_FORMAT + " at %" G_GUINT64_FORMAT, fourcc, GST_FOURCC_ARGS (fourcc), length, + cur_offset); + ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &unknown); + if (ret != GST_FLOW_OK) + goto beach; + gst_buffer_map (unknown, &map, GST_MAP_READ); + GST_MEMDUMP ("Unknown tag", map.data, map.size); + gst_buffer_unmap (unknown, &map); + gst_buffer_unref (unknown); + qtdemux->offset += length; + break; + } + } + +beach: + if (ret == GST_FLOW_EOS && (qtdemux->got_moov || qtdemux->media_caps)) { + /* digested all data, show what we have */ + qtdemux_prepare_streams (qtdemux); + QTDEMUX_EXPOSE_LOCK (qtdemux); + ret = qtdemux_expose_streams (qtdemux); + QTDEMUX_EXPOSE_UNLOCK (qtdemux); + + qtdemux->state = QTDEMUX_STATE_MOVIE; + GST_DEBUG_OBJECT (qtdemux, "switching state to STATE_MOVIE (%d)", + qtdemux->state); + return ret; + } + return ret; +} + +/* Seeks to the previous keyframe of the indexed stream and + * aligns other streams with respect to the keyframe timestamp + * of indexed stream. Only called in case of Reverse Playback + */ +static GstFlowReturn +gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux) +{ + guint8 n = 0; + guint32 seg_idx = 0, k_index = 0; + guint32 ref_seg_idx, ref_k_index; + GstClockTime k_pos = 0, last_stop = 0; + QtDemuxSegment *seg = NULL; + QtDemuxStream *ref_str = NULL; + guint64 seg_media_start_mov; /* segment media start time in mov format */ + guint64 target_ts; + + /* Now we choose an arbitrary stream, get the previous keyframe timestamp + * and finally align all the other streams on that timestamp with their + * respective keyframes */ + for (n = 0; n < qtdemux->n_streams; n++) { + QtDemuxStream *str = qtdemux->streams[n]; + + /* No candidate yet, take the first stream */ + if (!ref_str) { + ref_str = str; + continue; + } + + /* So that stream has a segment, we prefer video streams */ + if (str->subtype == FOURCC_vide) { + ref_str = str; + break; + } + } + + if (G_UNLIKELY (!ref_str)) { + GST_DEBUG_OBJECT (qtdemux, "couldn't find any stream"); + goto eos; + } + + if (G_UNLIKELY (!ref_str->from_sample)) { + GST_DEBUG_OBJECT (qtdemux, "reached the beginning of the file"); + goto eos; + } + + /* So that stream has been playing from from_sample to to_sample. We will + * get the timestamp of the previous sample and search for a keyframe before + * that. For audio streams we do an arbitrary jump in the past (10 samples) */ + if (ref_str->subtype == FOURCC_vide) { + k_index = gst_qtdemux_find_keyframe (qtdemux, ref_str, + ref_str->from_sample - 1); + } else { + if (ref_str->from_sample >= 10) + k_index = ref_str->from_sample - 10; + else + k_index = 0; + } + + target_ts = + ref_str->samples[k_index].timestamp + + ref_str->samples[k_index].pts_offset; + + /* get current segment for that stream */ + seg = &ref_str->segments[ref_str->segment_index]; + /* Use segment start in original timescale for comparisons */ + seg_media_start_mov = seg->trak_media_start; + + GST_LOG_OBJECT (qtdemux, "keyframe index %u ts %" G_GUINT64_FORMAT + " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT "\n", + k_index, target_ts, seg_media_start_mov, + GST_TIME_ARGS (seg->media_start)); + + /* Crawl back through segments to find the one containing this I frame */ + while (target_ts < seg_media_start_mov) { + GST_DEBUG_OBJECT (qtdemux, + "keyframe position (sample %u) is out of segment %u " " target %" + G_GUINT64_FORMAT " seg start %" G_GUINT64_FORMAT, k_index, + ref_str->segment_index, target_ts, seg_media_start_mov); + + if (G_UNLIKELY (!ref_str->segment_index)) { + /* Reached first segment, let's consider it's EOS */ + goto eos; + } + ref_str->segment_index--; + seg = &ref_str->segments[ref_str->segment_index]; + /* Use segment start in original timescale for comparisons */ + seg_media_start_mov = seg->trak_media_start; + } + /* Calculate time position of the keyframe and where we should stop */ + k_pos = + QTSTREAMTIME_TO_GSTTIME (ref_str, + target_ts - seg->trak_media_start) + seg->time; + last_stop = + QTSTREAMTIME_TO_GSTTIME (ref_str, + ref_str->samples[ref_str->from_sample].timestamp - + seg->trak_media_start) + seg->time; + + GST_DEBUG_OBJECT (qtdemux, "preferred stream played from sample %u, " + "now going to sample %u (pts %" GST_TIME_FORMAT ")", ref_str->from_sample, + k_index, GST_TIME_ARGS (k_pos)); + + /* Set last_stop with the keyframe timestamp we pushed of that stream */ + qtdemux->segment.position = last_stop; + GST_DEBUG_OBJECT (qtdemux, "last_stop now is %" GST_TIME_FORMAT, + GST_TIME_ARGS (last_stop)); + + if (G_UNLIKELY (last_stop < qtdemux->segment.start)) { + GST_DEBUG_OBJECT (qtdemux, "reached the beginning of segment"); + goto eos; + } + + ref_seg_idx = ref_str->segment_index; + ref_k_index = k_index; + + /* Align them all on this */ + for (n = 0; n < qtdemux->n_streams; n++) { + guint32 index = 0; + GstClockTime seg_time = 0; + QtDemuxStream *str = qtdemux->streams[n]; + + /* aligning reference stream again might lead to backing up to yet another + * keyframe (due to timestamp rounding issues), + * potentially putting more load on downstream; so let's try to avoid */ + if (str == ref_str) { + seg_idx = ref_seg_idx; + seg = &str->segments[seg_idx]; + k_index = ref_k_index; + GST_DEBUG_OBJECT (qtdemux, "reference stream %d segment %d, " + "sample at index %d", n, ref_str->segment_index, k_index); + } else { + seg_idx = gst_qtdemux_find_segment (qtdemux, str, k_pos); + GST_DEBUG_OBJECT (qtdemux, + "stream %d align segment %d for keyframe pos %" GST_TIME_FORMAT, n, + seg_idx, GST_TIME_ARGS (k_pos)); + + /* get segment and time in the segment */ + seg = &str->segments[seg_idx]; + seg_time = k_pos - seg->time; + + /* get the media time in the segment. + * No adjustment for empty "filler" segments */ + if (seg->media_start != GST_CLOCK_TIME_NONE) + seg_time += seg->media_start; + + /* get the index of the sample with media time */ + index = gst_qtdemux_find_index_linear (qtdemux, str, seg_time); + GST_DEBUG_OBJECT (qtdemux, + "stream %d sample for %" GST_TIME_FORMAT " at %u", n, + GST_TIME_ARGS (seg_time), index); + + /* find previous keyframe */ + k_index = gst_qtdemux_find_keyframe (qtdemux, str, index); + } + + /* Remember until where we want to go */ + str->to_sample = str->from_sample - 1; + /* Define our time position */ + target_ts = + str->samples[k_index].timestamp + str->samples[k_index].pts_offset; + str->time_position = QTSTREAMTIME_TO_GSTTIME (str, target_ts) + seg->time; + if (seg->media_start != GST_CLOCK_TIME_NONE) + str->time_position -= seg->media_start; + + /* Now seek back in time */ + gst_qtdemux_move_stream (qtdemux, str, k_index); + GST_DEBUG_OBJECT (qtdemux, "stream %d keyframe at %u, time position %" + GST_TIME_FORMAT " playing from sample %u to %u", n, k_index, + GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample); + } + + return GST_FLOW_OK; + +eos: + return GST_FLOW_EOS; +} + +/* + * Gets the current qt segment start, stop and position for the + * given time offset. This is used in update_segment() + */ +static void +gst_qtdemux_stream_segment_get_boundaries (GstQTDemux * qtdemux, + QtDemuxStream * stream, GstClockTime offset, + GstClockTime * _start, GstClockTime * _stop, GstClockTime * _time) +{ + GstClockTime seg_time; + GstClockTime start, stop, time; + QtDemuxSegment *segment; + + segment = &stream->segments[stream->segment_index]; + + /* get time in this segment */ + seg_time = offset - segment->time; + + GST_LOG_OBJECT (stream->pad, "seg_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (seg_time)); + + if (G_UNLIKELY (seg_time > segment->duration)) { + GST_LOG_OBJECT (stream->pad, + "seg_time > segment->duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->duration)); + seg_time = segment->duration; + } + + /* qtdemux->segment.stop is in outside-time-realm, whereas + * segment->media_stop is in track-time-realm. + * + * In order to compare the two, we need to bring segment.stop + * into the track-time-realm + * + * FIXME - does this comment still hold? Don't see any conversion here */ + + stop = qtdemux->segment.stop; + if (stop == GST_CLOCK_TIME_NONE) + stop = qtdemux->segment.duration; + if (stop == GST_CLOCK_TIME_NONE) + stop = segment->media_stop; + else + stop = + MIN (segment->media_stop, stop - segment->time + segment->media_start); + + if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (segment))) { + start = segment->time + seg_time; + time = offset; + stop = start - seg_time + segment->duration; + } else if (qtdemux->segment.rate >= 0) { + start = MIN (segment->media_start + seg_time, stop); + time = offset; + } else { + if (segment->media_start >= qtdemux->segment.start) { + time = segment->time; + } else { + time = segment->time + (qtdemux->segment.start - segment->media_start); + } + + start = MAX (segment->media_start, qtdemux->segment.start); + stop = MIN (segment->media_start + seg_time, stop); + } + + *_start = start; + *_stop = stop; + *_time = time; +} + +/* + * Updates the qt segment used for the stream and pushes a new segment event + * downstream on this stream's pad. + */ +static gboolean +gst_qtdemux_stream_update_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, + gint seg_idx, GstClockTime offset, GstClockTime * _start, + GstClockTime * _stop) +{ + QtDemuxSegment *segment; + GstClockTime start = 0, stop = GST_CLOCK_TIME_NONE, time = 0; + gdouble rate; + GstEvent *event; + + /* update the current segment */ + stream->segment_index = seg_idx; + + /* get the segment */ + segment = &stream->segments[seg_idx]; + + if (G_UNLIKELY (offset < segment->time)) { + GST_WARNING_OBJECT (stream->pad, "offset < segment->time %" GST_TIME_FORMAT, + GST_TIME_ARGS (segment->time)); + return FALSE; + } + + /* segment lies beyond total indicated duration */ + if (G_UNLIKELY (qtdemux->segment.duration != GST_CLOCK_TIME_NONE && + segment->time > qtdemux->segment.duration)) { + GST_WARNING_OBJECT (stream->pad, "file duration %" GST_TIME_FORMAT + " < segment->time %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->segment.duration), + GST_TIME_ARGS (segment->time)); + return FALSE; + } + + gst_qtdemux_stream_segment_get_boundaries (qtdemux, stream, offset, + &start, &stop, &time); + + GST_DEBUG_OBJECT (stream->pad, "new segment %d from %" GST_TIME_FORMAT + " to %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, seg_idx, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (time)); + + /* combine global rate with that of the segment */ + rate = segment->rate * qtdemux->segment.rate; + + /* Copy flags from main segment */ + stream->segment.flags = qtdemux->segment.flags; + + /* update the segment values used for clipping */ + stream->segment.offset = qtdemux->segment.offset; + stream->segment.base = qtdemux->segment.base + stream->accumulated_base; + stream->segment.applied_rate = qtdemux->segment.applied_rate; + stream->segment.rate = rate; + stream->segment.start = start + QTSTREAMTIME_TO_GSTTIME (stream, + stream->cslg_shift); + stream->segment.stop = stop + QTSTREAMTIME_TO_GSTTIME (stream, + stream->cslg_shift); + stream->segment.time = time; + stream->segment.position = stream->segment.start; + + GST_DEBUG_OBJECT (stream->pad, "New segment: %" GST_SEGMENT_FORMAT, + &stream->segment); + + /* now prepare and send the segment */ + if (stream->pad) { + event = gst_event_new_segment (&stream->segment); + if (qtdemux->segment_seqnum) { + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + } + gst_pad_push_event (stream->pad, event); + /* assume we can send more data now */ + GST_PAD_LAST_FLOW_RETURN (stream->pad) = GST_FLOW_OK; + /* clear to send tags on this pad now */ + gst_qtdemux_push_tags (qtdemux, stream); + } + + if (_start) + *_start = start; + if (_stop) + *_stop = stop; + + return TRUE; +} + +/* activate the given segment number @seg_idx of @stream at time @offset. + * @offset is an absolute global position over all the segments. + * + * This will push out a NEWSEGMENT event with the right values and + * position the stream index to the first decodable sample before + * @offset. + */ +static gboolean +gst_qtdemux_activate_segment (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 seg_idx, GstClockTime offset) +{ + QtDemuxSegment *segment; + guint32 index, kf_index; + GstClockTime start = 0, stop = GST_CLOCK_TIME_NONE; + + GST_LOG_OBJECT (stream->pad, "activate segment %d, offset %" GST_TIME_FORMAT, + seg_idx, GST_TIME_ARGS (offset)); + + if (!gst_qtdemux_stream_update_segment (qtdemux, stream, seg_idx, offset, + &start, &stop)) + return FALSE; + + segment = &stream->segments[stream->segment_index]; + + /* in the fragmented case, we pick a fragment that starts before our + * desired position and rely on downstream to wait for a keyframe + * (FIXME: doesn't seem to work so well with ismv and wmv, as no parser; the + * tfra entries tells us which trun/sample the key unit is in, but we don't + * make use of this additional information at the moment) */ + if (qtdemux->fragmented) { + stream->to_sample = G_MAXUINT32; + return TRUE; + } + + /* We don't need to look for a sample in push-based */ + if (!qtdemux->pullbased) + return TRUE; + + /* and move to the keyframe before the indicated media time of the + * segment */ + if (G_LIKELY (!QTSEGMENT_IS_EMPTY (segment))) { + if (qtdemux->segment.rate >= 0) { + index = gst_qtdemux_find_index_linear (qtdemux, stream, start); + stream->to_sample = G_MAXUINT32; + GST_DEBUG_OBJECT (stream->pad, + "moving data pointer to %" GST_TIME_FORMAT ", index: %u, pts %" + GST_TIME_FORMAT, GST_TIME_ARGS (start), index, + GST_TIME_ARGS (QTSAMPLE_PTS (stream, &stream->samples[index]))); + } else { + index = gst_qtdemux_find_index_linear (qtdemux, stream, stop); + stream->to_sample = index; + GST_DEBUG_OBJECT (stream->pad, + "moving data pointer to %" GST_TIME_FORMAT ", index: %u, pts %" + GST_TIME_FORMAT, GST_TIME_ARGS (stop), index, + GST_TIME_ARGS (QTSAMPLE_PTS (stream, &stream->samples[index]))); + } + } else { + GST_DEBUG_OBJECT (stream->pad, "No need to look for keyframe, " + "this is an empty segment"); + return TRUE; + } + + /* gst_qtdemux_parse_sample () called from gst_qtdemux_find_index_linear () + * encountered an error and printed a message so we return appropriately */ + if (index == -1) + return FALSE; + + /* we're at the right spot */ + if (index == stream->sample_index) { + GST_DEBUG_OBJECT (stream->pad, "we are at the right index"); + return TRUE; + } + + /* find keyframe of the target index */ + kf_index = gst_qtdemux_find_keyframe (qtdemux, stream, index); + +/* *INDENT-OFF* */ +/* indent does stupid stuff with stream->samples[].timestamp */ + + /* if we move forwards, we don't have to go back to the previous + * keyframe since we already sent that. We can also just jump to + * the keyframe right before the target index if there is one. */ + if (index > stream->sample_index) { + /* moving forwards check if we move past a keyframe */ + if (kf_index > stream->sample_index) { + GST_DEBUG_OBJECT (stream->pad, + "moving forwards to keyframe at %u (pts %" GST_TIME_FORMAT " dts %"GST_TIME_FORMAT" )", kf_index, + GST_TIME_ARGS (QTSAMPLE_PTS(stream, &stream->samples[kf_index])), + GST_TIME_ARGS (QTSAMPLE_DTS(stream, &stream->samples[kf_index]))); + gst_qtdemux_move_stream (qtdemux, stream, kf_index); + } else { + GST_DEBUG_OBJECT (stream->pad, + "moving forwards, keyframe at %u (pts %" GST_TIME_FORMAT " dts %"GST_TIME_FORMAT" ) already sent", kf_index, + GST_TIME_ARGS (QTSAMPLE_PTS (stream, &stream->samples[kf_index])), + GST_TIME_ARGS (QTSAMPLE_DTS (stream, &stream->samples[kf_index]))); + } + } else { + GST_DEBUG_OBJECT (stream->pad, + "moving backwards to keyframe at %u (pts %" GST_TIME_FORMAT " dts %"GST_TIME_FORMAT" )", kf_index, + GST_TIME_ARGS (QTSAMPLE_PTS(stream, &stream->samples[kf_index])), + GST_TIME_ARGS (QTSAMPLE_DTS(stream, &stream->samples[kf_index]))); + gst_qtdemux_move_stream (qtdemux, stream, kf_index); + } + +/* *INDENT-ON* */ + + return TRUE; +} + +/* prepare to get the current sample of @stream, getting essential values. + * + * This function will also prepare and send the segment when needed. + * + * Return FALSE if the stream is EOS. + * + * PULL-BASED + */ +static gboolean +gst_qtdemux_prepare_current_sample (GstQTDemux * qtdemux, + QtDemuxStream * stream, gboolean * empty, guint64 * offset, guint * size, + GstClockTime * dts, GstClockTime * pts, GstClockTime * duration, + gboolean * keyframe) +{ + QtDemuxSample *sample; + GstClockTime time_position; + guint32 seg_idx; + + g_return_val_if_fail (stream != NULL, FALSE); + + time_position = stream->time_position; + if (G_UNLIKELY (time_position == GST_CLOCK_TIME_NONE)) + goto eos; + + seg_idx = stream->segment_index; + if (G_UNLIKELY (seg_idx == -1)) { + /* find segment corresponding to time_position if we are looking + * for a segment. */ + seg_idx = gst_qtdemux_find_segment (qtdemux, stream, time_position); + } + + /* different segment, activate it, sample_index will be set. */ + if (G_UNLIKELY (stream->segment_index != seg_idx)) + gst_qtdemux_activate_segment (qtdemux, stream, seg_idx, time_position); + + if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (&stream-> + segments[stream->segment_index]))) { + QtDemuxSegment *seg = &stream->segments[stream->segment_index]; + + GST_LOG_OBJECT (qtdemux, "Empty segment activated," + " prepare empty sample"); + + *empty = TRUE; + *pts = *dts = time_position; + *duration = seg->duration - (time_position - seg->time); + + return TRUE; + } + + *empty = FALSE; + + if (stream->sample_index == -1) + stream->sample_index = 0; + + GST_LOG_OBJECT (qtdemux, "segment active, index = %u of %u", + stream->sample_index, stream->n_samples); + + if (G_UNLIKELY (stream->sample_index >= stream->n_samples)) { + if (!qtdemux->fragmented) + goto eos; + + GST_INFO_OBJECT (qtdemux, "out of samples, trying to add more"); + do { + GstFlowReturn flow; + + GST_OBJECT_LOCK (qtdemux); + flow = qtdemux_add_fragmented_samples (qtdemux); + GST_OBJECT_UNLOCK (qtdemux); + + if (flow != GST_FLOW_OK) + goto eos; + } + while (stream->sample_index >= stream->n_samples); + } + + if (!qtdemux_parse_samples (qtdemux, stream, stream->sample_index)) { + GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", + stream->sample_index); + return FALSE; + } + + /* now get the info for the sample we're at */ + sample = &stream->samples[stream->sample_index]; + + *dts = QTSAMPLE_DTS (stream, sample); + *pts = QTSAMPLE_PTS (stream, sample); + *offset = sample->offset; + *size = sample->size; + *duration = QTSAMPLE_DUR_DTS (stream, sample, *dts); + *keyframe = QTSAMPLE_KEYFRAME (stream, sample); + + return TRUE; + + /* special cases */ +eos: + { + stream->time_position = GST_CLOCK_TIME_NONE; + return FALSE; + } +} + +/* move to the next sample in @stream. + * + * Moves to the next segment when needed. + */ +static void +gst_qtdemux_advance_sample (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + QtDemuxSample *sample; + QtDemuxSegment *segment; + + /* get current segment */ + segment = &stream->segments[stream->segment_index]; + + if (G_UNLIKELY (QTSEGMENT_IS_EMPTY (segment))) { + GST_DEBUG_OBJECT (qtdemux, "Empty segment, no samples to advance"); + goto next_segment; + } + + if (G_UNLIKELY (stream->sample_index >= stream->to_sample)) { + /* Mark the stream as EOS */ + GST_DEBUG_OBJECT (qtdemux, + "reached max allowed sample %u, mark EOS", stream->to_sample); + stream->time_position = GST_CLOCK_TIME_NONE; + return; + } + + /* move to next sample */ + stream->sample_index++; + stream->offset_in_sample = 0; + + /* reached the last sample, we need the next segment */ + if (G_UNLIKELY (stream->sample_index >= stream->n_samples)) + goto next_segment; + + if (!qtdemux_parse_samples (qtdemux, stream, stream->sample_index)) { + GST_LOG_OBJECT (qtdemux, "Parsing of index %u failed!", + stream->sample_index); + return; + } + + /* get next sample */ + sample = &stream->samples[stream->sample_index]; + + /* see if we are past the segment */ + if (G_UNLIKELY (QTSAMPLE_DTS (stream, sample) >= segment->media_stop)) + goto next_segment; + + if (QTSAMPLE_DTS (stream, sample) >= segment->media_start) { + /* inside the segment, update time_position, looks very familiar to + * GStreamer segments, doesn't it? */ + stream->time_position = + QTSAMPLE_DTS (stream, sample) - segment->media_start + segment->time; + } else { + /* not yet in segment, time does not yet increment. This means + * that we are still prerolling keyframes to the decoder so it can + * decode the first sample of the segment. */ + stream->time_position = segment->time; + } + return; + + /* move to the next segment */ +next_segment: + { + GST_DEBUG_OBJECT (qtdemux, "segment %d ended ", stream->segment_index); + + if (stream->segment_index == stream->n_segments - 1) { + /* are we at the end of the last segment, we're EOS */ + stream->time_position = GST_CLOCK_TIME_NONE; + } else { + /* else we're only at the end of the current segment */ + stream->time_position = segment->stop_time; + } + /* make sure we select a new segment */ + + /* accumulate previous segments */ + if (GST_CLOCK_TIME_IS_VALID (stream->segment.stop)) + stream->accumulated_base += + (stream->segment.stop - + stream->segment.start) / ABS (stream->segment.rate); + + stream->segment_index = -1; + } +} + +static void +gst_qtdemux_sync_streams (GstQTDemux * demux) +{ + gint i; + + if (demux->n_streams <= 1) + return; + + for (i = 0; i < demux->n_streams; i++) { + QtDemuxStream *stream; + GstClockTime end_time; + + stream = demux->streams[i]; + + if (!stream->pad) + continue; + + /* TODO advance time on subtitle streams here, if any some day */ + + /* some clips/trailers may have unbalanced streams at the end, + * so send EOS on shorter stream to prevent stalling others */ + + /* do not mess with EOS if SEGMENT seeking */ + if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) + continue; + + if (demux->pullbased) { + /* loop mode is sample time based */ + if (!STREAM_IS_EOS (stream)) + continue; + } else { + /* push mode is byte position based */ + if (stream->n_samples && + stream->samples[stream->n_samples - 1].offset >= demux->offset) + continue; + } + + if (stream->sent_eos) + continue; + + /* only act if some gap */ + end_time = stream->segments[stream->n_segments - 1].stop_time; + GST_LOG_OBJECT (demux, "current position: %" GST_TIME_FORMAT + ", stream end: %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment.position), GST_TIME_ARGS (end_time)); + if (GST_CLOCK_TIME_IS_VALID (end_time) + && (end_time + 2 * GST_SECOND < demux->segment.position)) { + GstEvent *event; + + GST_DEBUG_OBJECT (demux, "sending EOS for stream %s", + GST_PAD_NAME (stream->pad)); + stream->sent_eos = TRUE; + event = gst_event_new_eos (); + if (demux->segment_seqnum) + gst_event_set_seqnum (event, demux->segment_seqnum); + gst_pad_push_event (stream->pad, event); + } + } +} + +/* EOS and NOT_LINKED need to be combined. This means that we return: + * + * GST_FLOW_NOT_LINKED: when all pads NOT_LINKED. + * GST_FLOW_EOS: when all pads EOS or NOT_LINKED. + */ +static GstFlowReturn +gst_qtdemux_combine_flows (GstQTDemux * demux, QtDemuxStream * stream, + GstFlowReturn ret) +{ + GST_LOG_OBJECT (demux, "flow return: %s", gst_flow_get_name (ret)); + + if (stream->pad) + ret = gst_flow_combiner_update_pad_flow (demux->flowcombiner, stream->pad, + ret); + else + ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret); + + GST_LOG_OBJECT (demux, "combined flow return: %s", gst_flow_get_name (ret)); + return ret; +} + +/* the input buffer metadata must be writable. Returns NULL when the buffer is + * completely clipped + * + * Should be used only with raw buffers */ +static GstBuffer * +gst_qtdemux_clip_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstBuffer * buf) +{ + guint64 start, stop, cstart, cstop, diff; + GstClockTime pts, duration; + gsize size, osize; + gint num_rate, denom_rate; + gint frame_size; + gboolean clip_data; + guint offset; + + osize = size = gst_buffer_get_size (buf); + offset = 0; + + /* depending on the type, setup the clip parameters */ + if (stream->subtype == FOURCC_soun) { + frame_size = stream->bytes_per_frame; + num_rate = GST_SECOND; + denom_rate = (gint) stream->rate; + clip_data = TRUE; + } else if (stream->subtype == FOURCC_vide) { + frame_size = size; + num_rate = stream->fps_n; + denom_rate = stream->fps_d; + clip_data = FALSE; + } else + goto wrong_type; + + if (frame_size <= 0) + goto bad_frame_size; + + /* we can only clip if we have a valid pts */ + pts = GST_BUFFER_PTS (buf); + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pts))) + goto no_pts; + + duration = GST_BUFFER_DURATION (buf); + + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (duration))) { + duration = + gst_util_uint64_scale_int (size / frame_size, num_rate, denom_rate); + } + + start = pts; + stop = start + duration; + + if (G_UNLIKELY (!gst_segment_clip (&stream->segment, + GST_FORMAT_TIME, start, stop, &cstart, &cstop))) + goto clipped; + + /* see if some clipping happened */ + diff = cstart - start; + if (diff > 0) { + pts += diff; + duration -= diff; + + if (clip_data) { + /* bring clipped time to samples and to bytes */ + diff = gst_util_uint64_scale_int (diff, denom_rate, num_rate); + diff *= frame_size; + + GST_DEBUG_OBJECT (qtdemux, + "clipping start to %" GST_TIME_FORMAT " %" + G_GUINT64_FORMAT " bytes", GST_TIME_ARGS (cstart), diff); + + offset = diff; + size -= diff; + } + } + diff = stop - cstop; + if (diff > 0) { + duration -= diff; + + if (clip_data) { + /* bring clipped time to samples and then to bytes */ + diff = gst_util_uint64_scale_int (diff, denom_rate, num_rate); + diff *= frame_size; + GST_DEBUG_OBJECT (qtdemux, + "clipping stop to %" GST_TIME_FORMAT " %" G_GUINT64_FORMAT + " bytes", GST_TIME_ARGS (cstop), diff); + size -= diff; + } + } + + if (offset != 0 || size != osize) + gst_buffer_resize (buf, offset, size); + + GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_PTS (buf) = pts; + GST_BUFFER_DURATION (buf) = duration; + + return buf; + + /* dropped buffer */ +wrong_type: + { + GST_DEBUG_OBJECT (qtdemux, "unknown stream type"); + return buf; + } +bad_frame_size: + { + GST_DEBUG_OBJECT (qtdemux, "bad frame size"); + return buf; + } +no_pts: + { + GST_DEBUG_OBJECT (qtdemux, "no pts on buffer"); + return buf; + } +clipped: + { + GST_DEBUG_OBJECT (qtdemux, "clipped buffer"); + gst_buffer_unref (buf); + return NULL; + } +} + +/* the input buffer metadata must be writable, + * but time/duration etc not yet set and need not be preserved */ +static GstBuffer * +gst_qtdemux_process_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstBuffer * buf) +{ + GstMapInfo map; + guint nsize = 0; + gchar *str; + + /* not many cases for now */ + if (G_UNLIKELY (stream->fourcc == FOURCC_mp4s)) { + /* send a one time dvd clut event */ + if (stream->pending_event && stream->pad) + gst_pad_push_event (stream->pad, stream->pending_event); + stream->pending_event = NULL; + } + + if (G_UNLIKELY (stream->subtype != FOURCC_text + && stream->subtype != FOURCC_sbtl && + stream->subtype != FOURCC_subp)) { + return buf; + } + + gst_buffer_map (buf, &map, GST_MAP_READ); + + /* empty buffer is sent to terminate previous subtitle */ + if (map.size <= 2) { + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + return NULL; + } + if (stream->subtype == FOURCC_subp) { + /* That's all the processing needed for subpictures */ + gst_buffer_unmap (buf, &map); + return buf; + } + + nsize = GST_READ_UINT16_BE (map.data); + nsize = MIN (nsize, map.size - 2); + + GST_LOG_OBJECT (qtdemux, "3GPP timed text subtitle: %d/%" G_GSIZE_FORMAT "", + nsize, map.size); + + /* takes care of UTF-8 validation or UTF-16 recognition, + * no other encoding expected */ + str = gst_tag_freeform_string_to_utf8 ((gchar *) map.data + 2, nsize, NULL); + gst_buffer_unmap (buf, &map); + if (str) { + gst_buffer_unref (buf); + buf = _gst_buffer_new_wrapped (str, strlen (str), g_free); + } else { + /* this should not really happen unless the subtitle is corrupted */ + gst_buffer_unref (buf); + buf = NULL; + } + + /* FIXME ? convert optional subsequent style info to markup */ + + return buf; +} + +/* Sets a buffer's attributes properly and pushes it downstream. + * Also checks for additional actions and custom processing that may + * need to be done first. + */ +static GstFlowReturn +gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, + QtDemuxStream * stream, GstBuffer * buf, + GstClockTime dts, GstClockTime pts, GstClockTime duration, + gboolean keyframe, GstClockTime position, guint64 byte_position) +{ + GstFlowReturn ret = GST_FLOW_OK; + + /* offset the timestamps according to the edit list */ + + if (G_UNLIKELY (stream->fourcc == FOURCC_rtsp)) { + gchar *url; + GstMapInfo map; + + gst_buffer_map (buf, &map, GST_MAP_READ); + url = g_strndup ((gchar *) map.data, map.size); + gst_buffer_unmap (buf, &map); + if (url != NULL && strlen (url) != 0) { + /* we have RTSP redirect now */ + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), + gst_message_new_element (GST_OBJECT_CAST (qtdemux), + gst_structure_new ("redirect", + "new-location", G_TYPE_STRING, url, NULL))); + qtdemux->posted_redirect = TRUE; + } else { + GST_WARNING_OBJECT (qtdemux, "Redirect URI of stream is empty, not " + "posting"); + } + g_free (url); + } + + /* position reporting */ + if (qtdemux->segment.rate >= 0) { + qtdemux->segment.position = position; + gst_qtdemux_sync_streams (qtdemux); + } + + + if (G_UNLIKELY (!stream->pad)) { + GST_DEBUG_OBJECT (qtdemux, "No output pad for stream, ignoring"); + gst_buffer_unref (buf); + goto exit; + } + + /* send out pending buffers */ + while (stream->buffers) { + GstBuffer *buffer = (GstBuffer *) stream->buffers->data; + + if (G_UNLIKELY (stream->discont)) { + GST_LOG_OBJECT (qtdemux, "marking discont buffer"); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + + gst_pad_push (stream->pad, buffer); + + stream->buffers = g_slist_delete_link (stream->buffers, stream->buffers); + } + + /* we're going to modify the metadata */ + buf = gst_buffer_make_writable (buf); + + if (G_UNLIKELY (stream->need_process)) + buf = gst_qtdemux_process_buffer (qtdemux, stream, buf); + + if (!buf) { + goto exit; + } + + GST_BUFFER_DTS (buf) = dts; + GST_BUFFER_PTS (buf) = pts; + GST_BUFFER_DURATION (buf) = duration; + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = -1; + + if (G_UNLIKELY (stream->rgb8_palette)) + gst_buffer_append_memory (buf, gst_memory_ref (stream->rgb8_palette)); + + if (G_UNLIKELY (stream->padding)) { + gst_buffer_resize (buf, stream->padding, -1); + } +#if 0 + if (G_UNLIKELY (qtdemux->element_index)) { + GstClockTime stream_time; + + stream_time = + gst_segment_to_stream_time (&stream->segment, GST_FORMAT_TIME, + timestamp); + if (GST_CLOCK_TIME_IS_VALID (stream_time)) { + GST_LOG_OBJECT (qtdemux, + "adding association %" GST_TIME_FORMAT "-> %" + G_GUINT64_FORMAT, GST_TIME_ARGS (stream_time), byte_position); + gst_index_add_association (qtdemux->element_index, + qtdemux->index_id, + keyframe ? GST_ASSOCIATION_FLAG_KEY_UNIT : + GST_ASSOCIATION_FLAG_DELTA_UNIT, GST_FORMAT_TIME, stream_time, + GST_FORMAT_BYTES, byte_position, NULL); + } + } +#endif + + if (stream->need_clip) + buf = gst_qtdemux_clip_buffer (qtdemux, stream, buf); + + if (G_UNLIKELY (buf == NULL)) + goto exit; + + if (G_UNLIKELY (stream->discont)) { + GST_LOG_OBJECT (qtdemux, "marking discont buffer"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + stream->discont = FALSE; + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + + if (!keyframe) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + stream->on_keyframe = FALSE; + } else { + stream->on_keyframe = TRUE; + } + + + GST_LOG_OBJECT (qtdemux, + "Pushing buffer with dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT " on pad %s", GST_TIME_ARGS (dts), + GST_TIME_ARGS (pts), GST_TIME_ARGS (duration), + GST_PAD_NAME (stream->pad)); + + if (stream->protected + && is_common_enc_scheme_type (stream->protection_scheme_type)) { + GstStructure *crypto_info; + QtDemuxCencSampleSetInfo *info = + (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + gint index; + GstEvent *event; + gboolean is_encrypted = TRUE; + + while ((event = g_queue_pop_head (&stream->protection_scheme_event_queue))) { + gst_pad_push_event (stream->pad, event); + } + + if (info->default_properties && + gst_structure_get_boolean (info->default_properties, "encrypted", + &is_encrypted) && !is_encrypted) { + if (!gst_buffer_add_protection_meta (buf, + gst_structure_copy (info->default_properties))) + GST_ERROR_OBJECT (qtdemux, "failed to attach cenc metadata to buffer"); + GST_DEBUG_OBJECT (qtdemux, "pass clear cenc buffer"); + goto push; + } + + if (info->crypto_info == NULL) { + GST_DEBUG_OBJECT (qtdemux, "cenc metadata hasn't been parsed yet"); + gst_buffer_unref (buf); + goto exit; + } + + index = stream->sample_index - (stream->n_samples - info->crypto_info->len); + if (G_LIKELY (index >= 0 && index < info->crypto_info->len)) { + /* steal structure from array */ + crypto_info = g_ptr_array_index (info->crypto_info, index); + g_ptr_array_index (info->crypto_info, index) = NULL; + GST_LOG_OBJECT (qtdemux, "attaching cenc metadata [%u]", index); + if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info)) + GST_ERROR_OBJECT (qtdemux, "failed to attach cenc metadata to buffer"); + } + } + +push: + if (stream->fourcc == FOURCC_stpp) { + if (!gst_buffer_add_subtitle_data_meta (buf, qtdemux->dash_period_start, + qtdemux->dash_subtitle_offset, qtdemux->dash_subtitle_index)) { + GST_ERROR_OBJECT (qtdemux, + "failed to attach subtitle metadata to buffer"); + } + } + ret = gst_pad_push (stream->pad, buf); + + if (GST_CLOCK_TIME_IS_VALID (pts) && GST_CLOCK_TIME_IS_VALID (duration)) { + /* mark position in stream, we'll need this to know when to send GAP event */ + stream->segment.position = pts + duration; + } + +exit: + return ret; +} + +static const QtDemuxRandomAccessEntry * +gst_qtdemux_stream_seek_fragment (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstClockTime pos, gboolean after, gboolean use_sidx) +{ + QtDemuxRandomAccessEntry *entries; + guint n_entries; + guint i; + + if (use_sidx) { + entries = stream->seg_idx_entries; + n_entries = stream->n_seg_idx_entries; + } else { + entries = stream->ra_entries; + n_entries = stream->n_ra_entries; + } + + /* we assume the table is sorted */ + for (i = 0; i < n_entries; ++i) { + if (entries[i].ts > pos) + break; + } + + /* FIXME: maybe save first moof_offset somewhere instead, but for now it's + * probably okay to assume that the index lists the very first fragment */ + if (i == 0) + return &entries[0]; + + if (after) + return &entries[i]; + else + return &entries[i - 1]; +} + +static gboolean +gst_qtdemux_do_fragmented_seek (GstQTDemux * qtdemux) +{ + const QtDemuxRandomAccessEntry *best_entry = NULL; + guint i; + gboolean use_sidx = FALSE; + + GST_OBJECT_LOCK (qtdemux); + + g_assert (qtdemux->n_streams > 0); + + for (i = 0; i < qtdemux->n_streams; i++) { + const QtDemuxRandomAccessEntry *entry; + QtDemuxStream *stream; + gboolean is_audio_or_video; + + stream = qtdemux->streams[i]; + + gst_qtdemux_sample_to_group_free (stream); + + g_free (stream->samples); + stream->samples = NULL; + stream->n_samples = 0; + stream->stbl_index = -1; /* no samples have yet been parsed */ + stream->sample_index = -1; + + if (stream->ra_entries == NULL) { + if (stream->n_seg_idx_entries > 0) { + GST_LOG_OBJECT (qtdemux, "Do fragmented seek using sidx"); + use_sidx = TRUE; + } else { + continue; + } + } + + if (stream->subtype == FOURCC_vide || stream->subtype == FOURCC_soun) + is_audio_or_video = TRUE; + else + is_audio_or_video = FALSE; + + entry = + gst_qtdemux_stream_seek_fragment (qtdemux, stream, + stream->time_position, !is_audio_or_video, use_sidx); + + GST_INFO_OBJECT (stream->pad, "%" GST_TIME_FORMAT " at offset " + "%" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->ts), entry->moof_offset); + + stream->pending_seek = entry; + + /* decide position to jump to just based on audio/video tracks, not subs */ + if (!is_audio_or_video) + continue; + + if (best_entry == NULL || entry->moof_offset < best_entry->moof_offset) + best_entry = entry; + } + + if (best_entry == NULL) { + GST_OBJECT_UNLOCK (qtdemux); + return FALSE; + } + + GST_INFO_OBJECT (qtdemux, "seek to %" GST_TIME_FORMAT ", best fragment " + "moof offset: %" G_GUINT64_FORMAT ", ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (qtdemux->streams[0]->time_position), + best_entry->moof_offset, GST_TIME_ARGS (best_entry->ts)); + + qtdemux->moof_offset = best_entry->moof_offset; + + qtdemux_add_fragmented_samples (qtdemux); + + GST_OBJECT_UNLOCK (qtdemux); + return TRUE; +} + +static GstFlowReturn +gst_qtdemux_loop_state_movie (GstQTDemux * qtdemux) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buf = NULL; + QtDemuxStream *stream; + GstClockTime min_time; + guint64 offset = 0; + GstClockTime dts = GST_CLOCK_TIME_NONE; + GstClockTime pts = GST_CLOCK_TIME_NONE; + GstClockTime duration = 0; + gboolean keyframe = FALSE; + guint sample_size = 0; + gboolean empty = 0; + guint size; + gint index; + gint i; + + gst_qtdemux_push_pending_newsegment (qtdemux); + + if (qtdemux->fragmented_seek_pending) { + GST_INFO_OBJECT (qtdemux, "pending fragmented seek"); + gst_qtdemux_do_fragmented_seek (qtdemux); + GST_INFO_OBJECT (qtdemux, "fragmented seek done!"); + qtdemux->fragmented_seek_pending = FALSE; + } + + /* Figure out the next stream sample to output, min_time is expressed in + * global time and runs over the edit list segments. */ + min_time = G_MAXUINT64; + index = -1; + for (i = 0; i < qtdemux->n_streams; i++) { + GstClockTime position; + + stream = qtdemux->streams[i]; + position = stream->time_position; + + /* position of -1 is EOS */ + if (position != GST_CLOCK_TIME_NONE && position < min_time) { + min_time = position; + index = i; + } + } + /* all are EOS */ + if (G_UNLIKELY (index == -1)) { + GST_DEBUG_OBJECT (qtdemux, "all streams are EOS"); + goto eos; + } + + /* check for segment end */ + if (G_UNLIKELY (qtdemux->segment.stop != -1 + && ((qtdemux->segment.rate >= 0 && qtdemux->segment.stop <= min_time) + || (qtdemux->segment.rate < 0 + && qtdemux->segment.start > min_time)) + && qtdemux->streams[index]->on_keyframe)) { + GST_DEBUG_OBJECT (qtdemux, "we reached the end of our segment."); + qtdemux->streams[index]->time_position = GST_CLOCK_TIME_NONE; + goto eos_stream; + } + + /* gap events for subtitle streams */ + for (i = 0; i < qtdemux->n_streams; i++) { + stream = qtdemux->streams[i]; + if (stream->pad && (stream->subtype == FOURCC_subp + || stream->subtype == FOURCC_text + || stream->subtype == FOURCC_sbtl)) { + /* send one second gap events until the stream catches up */ + /* gaps can only be sent after segment is activated (segment.stop is no longer -1) */ + while (GST_CLOCK_TIME_IS_VALID (stream->segment.stop) && + GST_CLOCK_TIME_IS_VALID (stream->segment.position) && + stream->segment.position + GST_SECOND < min_time) { + GstEvent *gap = + gst_event_new_gap (stream->segment.position, GST_SECOND); + gst_pad_push_event (stream->pad, gap); + stream->segment.position += GST_SECOND; + } + } + } + + stream = qtdemux->streams[index]; + /* fetch info for the current sample of this stream */ + if (G_UNLIKELY (!gst_qtdemux_prepare_current_sample (qtdemux, stream, &empty, + &offset, &sample_size, &dts, &pts, &duration, &keyframe))) + goto eos_stream; + + gst_qtdemux_stream_check_and_change_stsd_index (qtdemux, stream); + if (stream->new_caps) { + gst_qtdemux_configure_stream (qtdemux, stream); + qtdemux_do_allocation (qtdemux, stream); + } + + /* If we're doing a keyframe-only trickmode, only push keyframes on video streams */ + if (G_UNLIKELY (qtdemux->segment. + flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) { + if (stream->subtype == FOURCC_vide && !keyframe) { + GST_LOG_OBJECT (qtdemux, "Skipping non-keyframe on stream %d", index); + goto next; + } + } else { + qtdemux->seek_to_key_frame = FALSE; + } + + GST_DEBUG_OBJECT (qtdemux, + "pushing from stream %d, empty %d offset %" G_GUINT64_FORMAT + ", size %d, dts=%" GST_TIME_FORMAT ", pts=%" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, index, empty, offset, sample_size, + GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), GST_TIME_ARGS (duration)); + + if (G_UNLIKELY (empty)) { + /* empty segment, push a gap and move to the next one */ + gst_pad_push_event (stream->pad, gst_event_new_gap (pts, duration)); + stream->segment.position = pts + duration; + goto next; + } + if (qtdemux_is_keyframe_trick_condition (qtdemux) + && g_strrstr (GST_PAD_NAME (stream->pad), "video") && !keyframe + && stream->n_sample_syncs > 1) { + qtdemux->seek_to_key_frame = TRUE; + stream->discont = TRUE; + goto next; + } else { + qtdemux->seek_to_key_frame = FALSE; + } + + /* hmm, empty sample, skip and move to next sample */ + if (G_UNLIKELY (sample_size <= 0)) + goto next; + + /* last pushed sample was out of boundary, goto next sample */ + if (G_UNLIKELY (GST_PAD_LAST_FLOW_RETURN (stream->pad) == GST_FLOW_EOS)) + goto next; + + if (stream->max_buffer_size == 0 || sample_size <= stream->max_buffer_size) { + size = sample_size; + } else { + GST_DEBUG_OBJECT (qtdemux, + "size %d larger than stream max_buffer_size %d, trimming", + sample_size, stream->max_buffer_size); + size = + MIN (sample_size - stream->offset_in_sample, stream->max_buffer_size); + } + + if (qtdemux->cenc_aux_info_offset > 0) { + GstMapInfo map; + GstByteReader br; + GstBuffer *aux_info = NULL; + + /* pull the data stored before the sample */ + ret = + gst_qtdemux_pull_atom (qtdemux, qtdemux->offset, + offset + stream->offset_in_sample - qtdemux->offset, &aux_info); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto beach; + gst_buffer_map (aux_info, &map, GST_MAP_READ); + GST_DEBUG_OBJECT (qtdemux, "parsing cenc auxiliary info"); + gst_byte_reader_init (&br, map.data + 8, map.size); + if (!qtdemux_parse_cenc_aux_info (qtdemux, stream, &br, + qtdemux->cenc_aux_info_sizes, qtdemux->cenc_aux_sample_count)) { + GST_ERROR_OBJECT (qtdemux, "failed to parse cenc auxiliary info"); + gst_buffer_unmap (aux_info, &map); + gst_buffer_unref (aux_info); + ret = GST_FLOW_ERROR; + goto beach; + } + gst_buffer_unmap (aux_info, &map); + gst_buffer_unref (aux_info); + } + + GST_LOG_OBJECT (qtdemux, "reading %d bytes @ %" G_GUINT64_FORMAT, size, + offset); + + if (stream->use_allocator) { + /* if we have a per-stream allocator, use it */ + buf = gst_buffer_new_allocate (stream->allocator, size, &stream->params); + } + + ret = gst_qtdemux_pull_atom (qtdemux, offset + stream->offset_in_sample, + size, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto beach; + + if (size != sample_size) { + pts += gst_util_uint64_scale_int (GST_SECOND, + stream->offset_in_sample / stream->bytes_per_frame, stream->timescale); + dts += gst_util_uint64_scale_int (GST_SECOND, + stream->offset_in_sample / stream->bytes_per_frame, stream->timescale); + duration = gst_util_uint64_scale_int (GST_SECOND, + size / stream->bytes_per_frame, stream->timescale); + } + + if (g_strrstr (GST_PAD_NAME (stream->pad), "video") + && stream->fourcc == FOURCC_avc1) { + guint64 size_of_stream = 0; + guint8 length_offset = 0; + GstMapInfo map; + + gst_buffer_map (buf, &map, GST_MAP_READ); + switch (stream->length_size_avcC) { + case 1: + size_of_stream = QT_UINT8 (map.data); + length_offset = 1; + break; + case 2: + size_of_stream = QT_UINT16 (map.data); + length_offset = 2; + break; + case 4: + size_of_stream = QT_UINT32 (map.data); + length_offset = 4; + break; + default: + GST_DEBUG_OBJECT (qtdemux, "wrong size: length_size_avcC %d", + stream->length_size_avcC); + break; + } + + GST_DEBUG_OBJECT (qtdemux, "size %d, size_of_stream %" G_GUINT64_FORMAT, + sample_size, size_of_stream); + gst_buffer_unmap (buf, &map); + if ((size != size_of_stream + length_offset) + && (size_of_stream > QTDEMUX_MAX_ATOM_SIZE)) { + gst_buffer_unref (buf); + buf = NULL; + goto next; + } + } + + ret = gst_qtdemux_decorate_and_push_buffer (qtdemux, stream, buf, + dts, pts, duration, keyframe, min_time, offset); + + if (size != sample_size) { + QtDemuxSample *sample = &stream->samples[stream->sample_index]; + QtDemuxSegment *segment = &stream->segments[stream->segment_index]; + + GstClockTime time_position = QTSTREAMTIME_TO_GSTTIME (stream, + sample->timestamp + stream->offset_in_sample / stream->bytes_per_frame); + if (time_position >= segment->media_start) { + /* inside the segment, update time_position, looks very familiar to + * GStreamer segments, doesn't it? */ + stream->time_position = (time_position - segment->media_start) + + segment->time; + } else { + /* not yet in segment, time does not yet increment. This means + * that we are still prerolling keyframes to the decoder so it can + * decode the first sample of the segment. */ + stream->time_position = segment->time; + } + } + + /* combine flows */ + ret = gst_qtdemux_combine_flows (qtdemux, stream, ret); + /* ignore unlinked, we will not push on the pad anymore and we will EOS when + * we have no more data for the pad to push */ + if (ret == GST_FLOW_EOS) + ret = GST_FLOW_OK; + + stream->offset_in_sample += size; + if (stream->offset_in_sample >= sample_size) { + gst_qtdemux_advance_sample (qtdemux, stream); + } + goto beach; + +next: + gst_qtdemux_advance_sample (qtdemux, stream); + +beach: + return ret; + + /* special cases */ +eos: + { + GST_DEBUG_OBJECT (qtdemux, "No samples left for any streams - EOS"); + ret = GST_FLOW_EOS; + goto beach; + } +eos_stream: + { + GST_DEBUG_OBJECT (qtdemux, "No samples left for stream"); + /* EOS will be raised if all are EOS */ + ret = GST_FLOW_OK; + goto beach; + } +} + +static void +gst_qtdemux_loop (GstPad * pad) +{ + GstQTDemux *qtdemux; + guint64 cur_offset; + GstFlowReturn ret; + + qtdemux = GST_QTDEMUX (gst_pad_get_parent (pad)); + + cur_offset = qtdemux->offset; + GST_LOG_OBJECT (qtdemux, "loop at position %" G_GUINT64_FORMAT ", state %d", + cur_offset, qtdemux->state); + + switch (qtdemux->state) { + case QTDEMUX_STATE_INITIAL: + case QTDEMUX_STATE_HEADER: + ret = gst_qtdemux_loop_state_header (qtdemux); + break; + case QTDEMUX_STATE_MOVIE: + ret = gst_qtdemux_loop_state_movie (qtdemux); + if (qtdemux->segment.rate < 0 && (qtdemux->seek_to_key_frame + || ret == GST_FLOW_EOS)) { + ret = gst_qtdemux_seek_to_previous_keyframe (qtdemux); + } + break; + default: + /* ouch */ + goto invalid_state; + } + + /* if something went wrong, pause */ + if (ret != GST_FLOW_OK) + goto pause; + +done: + gst_object_unref (qtdemux); + return; + + /* ERRORS */ +invalid_state: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, FAILED, + (NULL), ("streaming stopped, invalid state")); + gst_pad_pause_task (pad); + gst_qtdemux_push_event (qtdemux, gst_event_new_eos ()); + goto done; + } +pause: + { + const gchar *reason = gst_flow_get_name (ret); + + GST_LOG_OBJECT (qtdemux, "pausing task, reason %s", reason); + + gst_pad_pause_task (pad); + + /* fatal errors need special actions */ + /* check EOS */ + if (ret == GST_FLOW_EOS) { + if (qtdemux->n_streams == 0) { + /* we have no streams, post an error */ + gst_qtdemux_post_no_playable_stream_error (qtdemux); + } + if (qtdemux->segment.flags & GST_SEEK_FLAG_SEGMENT) { + gint64 stop; + + if ((stop = qtdemux->segment.stop) == -1) + stop = qtdemux->segment.duration; + + if (qtdemux->segment.rate >= 0) { + GstMessage *message; + GstEvent *event; + + GST_LOG_OBJECT (qtdemux, "Sending segment done, at end of segment"); + message = gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux), + GST_FORMAT_TIME, stop); + event = gst_event_new_segment_done (GST_FORMAT_TIME, stop); + if (qtdemux->segment_seqnum) { + gst_message_set_seqnum (message, qtdemux->segment_seqnum); + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + } + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), message); + gst_qtdemux_push_event (qtdemux, event); + } else { + GstMessage *message; + GstEvent *event; + + /* For Reverse Playback */ + GST_LOG_OBJECT (qtdemux, "Sending segment done, at start of segment"); + message = gst_message_new_segment_done (GST_OBJECT_CAST (qtdemux), + GST_FORMAT_TIME, qtdemux->segment.start); + event = gst_event_new_segment_done (GST_FORMAT_TIME, + qtdemux->segment.start); + if (qtdemux->segment_seqnum) { + gst_message_set_seqnum (message, qtdemux->segment_seqnum); + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + } + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), message); + gst_qtdemux_push_event (qtdemux, event); + } + } else { + GstEvent *event; + + GST_LOG_OBJECT (qtdemux, "Sending EOS at end of segment"); + event = gst_event_new_eos (); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + gst_qtdemux_push_event (qtdemux, event); + } + } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { + GST_ELEMENT_ERROR (qtdemux, STREAM, FAILED, + (NULL), ("streaming stopped, reason %s", reason)); + gst_qtdemux_push_event (qtdemux, gst_event_new_eos ()); + } + goto done; + } +} + +/* + * has_next_entry + * + * Returns if there are samples to be played. + */ +static gboolean +has_next_entry (GstQTDemux * demux) +{ + QtDemuxStream *stream; + int i; + + GST_DEBUG_OBJECT (demux, "Checking if there are samples not played yet"); + + for (i = 0; i < demux->n_streams; i++) { + stream = demux->streams[i]; + + if (stream->sample_index == -1) { + stream->sample_index = 0; + stream->offset_in_sample = 0; + } + + if (stream->sample_index >= stream->n_samples) { + GST_LOG_OBJECT (demux, "stream %d samples exhausted", i); + continue; + } + GST_DEBUG_OBJECT (demux, "Found a sample"); + return TRUE; + } + + GST_DEBUG_OBJECT (demux, "There wasn't any next sample"); + return FALSE; +} + +/* + * next_entry_size + * + * Returns the size of the first entry at the current offset. + * If -1, there are none (which means EOS or empty file). + */ +static guint64 +next_entry_size (GstQTDemux * demux) +{ + QtDemuxStream *stream; + int i; + int smallidx = -1; + guint64 smalloffs = (guint64) - 1; + QtDemuxSample *sample; + + GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT, + demux->offset); + + for (i = 0; i < demux->n_streams; i++) { + stream = demux->streams[i]; + + if (stream->sample_index == -1) { + stream->sample_index = 0; + stream->offset_in_sample = 0; + } + + if (stream->sample_index >= stream->n_samples) { + GST_LOG_OBJECT (demux, "stream %d samples exhausted", i); + continue; + } + + if (!qtdemux_parse_samples (demux, stream, stream->sample_index)) { + GST_LOG_OBJECT (demux, "Parsing of index %u from stbl atom failed!", + stream->sample_index); + return -1; + } + + sample = &stream->samples[stream->sample_index]; + + GST_LOG_OBJECT (demux, + "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT + " / size:%" G_GUINT32_FORMAT ")", i, stream->sample_index, + sample->offset, sample->size); + + if (!((stream->subtype == FOURCC_vide) || (stream->subtype == FOURCC_soun)) + && ((sample->offset < demux->offset) && sample->size)) { + GST_LOG_OBJECT (demux, + "ignore subtitle sample located before demux offset (stream %d)", i); + continue; + } + + if (((smalloffs == -1) + || (sample->offset < smalloffs)) && (sample->size)) { + smallidx = i; + smalloffs = sample->offset; + } + } + + GST_LOG_OBJECT (demux, + "stream %d offset %" G_GUINT64_FORMAT " demux->offset :%" + G_GUINT64_FORMAT, smallidx, smalloffs, demux->offset); + + if (smallidx == -1) + return -1; + + stream = demux->streams[smallidx]; + sample = &stream->samples[stream->sample_index]; + + if (sample->offset >= demux->offset) { + demux->todrop = sample->offset - demux->offset; + return sample->size + demux->todrop; + } + + GST_DEBUG_OBJECT (demux, + "There wasn't any entry at offset %" G_GUINT64_FORMAT, demux->offset); + return -1; +} + +static void +gst_qtdemux_post_progress (GstQTDemux * demux, gint num, gint denom) +{ + gint perc = (gint) ((gdouble) num * 100.0 / (gdouble) denom); + + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT_CAST (demux), + gst_structure_new ("progress", "percent", G_TYPE_INT, perc, NULL))); +} + +static gboolean +qtdemux_seek_offset (GstQTDemux * demux, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + GST_DEBUG_OBJECT (demux, "Seeking to %" G_GUINT64_FORMAT, offset); + + event = + gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset, + GST_SEEK_TYPE_NONE, -1); + + /* store seqnum to drop flush events, they don't need to reach downstream */ + demux->offset_seek_seqnum = gst_event_get_seqnum (event); + res = gst_pad_push_event (demux->sinkpad, event); + demux->offset_seek_seqnum = 0; + + return res; +} + +/* check for seekable upstream, above and beyond a mere query */ +static void +gst_qtdemux_check_seekability (GstQTDemux * demux) +{ + GstQuery *query; + gboolean seekable = FALSE; + gint64 start = -1, stop = -1; + + if (demux->upstream_size) + return; + + query = gst_query_new_seeking (GST_FORMAT_BYTES); + if (!gst_pad_peer_query (demux->sinkpad, query)) { + GST_DEBUG_OBJECT (demux, "seeking query failed"); + goto done; + } + + gst_query_parse_seeking (query, NULL, &seekable, &start, &stop); + + /* try harder to query upstream size if we didn't get it the first time */ + if (seekable && stop == -1) { + GST_DEBUG_OBJECT (demux, "doing duration query to fix up unset stop"); + gst_pad_peer_query_duration (demux->sinkpad, GST_FORMAT_BYTES, &stop); + } + + /* if upstream doesn't know the size, it's likely that it's not seekable in + * practice even if it technically may be seekable */ + if (seekable && (start != 0 || stop <= start)) { + GST_DEBUG_OBJECT (demux, "seekable but unknown start/stop -> disable"); + seekable = FALSE; + } + +done: + gst_query_unref (query); + + GST_DEBUG_OBJECT (demux, "seekable: %d (%" G_GUINT64_FORMAT " - %" + G_GUINT64_FORMAT ")", seekable, start, stop); + demux->upstream_seekable = seekable; + demux->upstream_size = seekable ? stop : -1; +} + +static void +gst_qtdemux_drop_data (GstQTDemux * demux, gint bytes) +{ + g_return_if_fail (bytes <= demux->todrop); + + GST_LOG_OBJECT (demux, "Dropping %d bytes", bytes); + gst_adapter_flush (demux->adapter, bytes); + demux->neededbytes -= bytes; + demux->offset += bytes; + demux->todrop -= bytes; +} + +static void +gst_qtdemux_check_send_pending_segment (GstQTDemux * demux) +{ + if (G_UNLIKELY (demux->pending_newsegment)) { + gint i; + + gst_qtdemux_push_pending_newsegment (demux); + /* clear to send tags on all streams */ + for (i = 0; i < demux->n_streams; i++) { + QtDemuxStream *stream; + stream = demux->streams[i]; + gst_qtdemux_push_tags (demux, stream); + if (stream->sparse) { + GST_INFO_OBJECT (demux, "Sending gap event on stream %d", i); + gst_pad_push_event (stream->pad, + gst_event_new_gap (stream->segment.position, GST_CLOCK_TIME_NONE)); + } + } + } +} + +static void +gst_qtdemux_send_gap_for_segment (GstQTDemux * demux, + QtDemuxStream * stream, gint segment_index, GstClockTime pos) +{ + GstClockTime ts, dur; + GstEvent *gap; + + ts = pos; + dur = + stream->segments[segment_index].duration - (pos - + stream->segments[segment_index].time); + gap = gst_event_new_gap (ts, dur); + stream->time_position += dur; + + GST_DEBUG_OBJECT (stream->pad, "Pushing gap for empty " + "segment: %" GST_PTR_FORMAT, gap); + gst_pad_push_event (stream->pad, gap); +} + +static GstFlowReturn +gst_qtdemux_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * inbuf) +{ + GstQTDemux *demux; + + demux = GST_QTDEMUX (parent); + + if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT)) { + gint i; + + GST_DEBUG_OBJECT (demux, "Got DISCONT, marking all streams as DISCONT"); + + for (i = 0; i < demux->n_streams; i++) { + demux->streams[i]->discont = TRUE; + } + + /* Reverse fragmented playback, need to flush all we have before + * consuming a new fragment. + * The samples array have the timestamps calculated by accumulating the + * durations but this won't work for reverse playback of fragments as + * the timestamps of a subsequent fragment should be smaller than the + * previously received one. */ + if (demux->fragmented && demux->segment.rate < 0) { + gst_qtdemux_process_adapter (demux, TRUE); + for (i = 0; i < demux->n_streams; i++) + gst_qtdemux_stream_flush_samples_data (demux, demux->streams[i]); + } + } + + gst_adapter_push (demux->adapter, inbuf); + + GST_DEBUG_OBJECT (demux, + "pushing in inbuf %p, neededbytes:%u, available:%" G_GSIZE_FORMAT, inbuf, + demux->neededbytes, gst_adapter_available (demux->adapter)); + + return gst_qtdemux_process_adapter (demux, FALSE); +} + +static GstFlowReturn +gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force) +{ + GstFlowReturn ret = GST_FLOW_OK; + + /* we never really mean to buffer that much */ + if (demux->neededbytes == -1) { + goto eos; + } + + while (((gst_adapter_available (demux->adapter)) >= demux->neededbytes) && + (ret == GST_FLOW_OK || (ret == GST_FLOW_NOT_LINKED && force))) { + + GST_DEBUG_OBJECT (demux, + "state:%d , demux->neededbytes:%d, demux->offset:%" G_GUINT64_FORMAT, + demux->state, demux->neededbytes, demux->offset); + + switch (demux->state) { + case QTDEMUX_STATE_INITIAL: + case QTDEMUX_STATE_LOST_SYNC: + { + const guint8 *data; + guint32 fourcc; + guint64 size; + + gst_qtdemux_check_seekability (demux); + + data = gst_adapter_map (demux->adapter, demux->neededbytes); + + /* get fourcc/length, set neededbytes */ + extract_initial_length_and_fourcc ((guint8 *) data, demux->neededbytes, + &size, &fourcc); + gst_adapter_unmap (demux->adapter); + data = NULL; + GST_DEBUG_OBJECT (demux, "Peeking found [%" GST_FOURCC_FORMAT "] " + "size: %" G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), size); + + if (demux->state == QTDEMUX_STATE_LOST_SYNC) { + if (demux->atsc3_mode) { + if (fourcc == FOURCC_ftyp || fourcc == FOURCC_moov || + fourcc == FOURCC_styp || fourcc == FOURCC_sidx || + fourcc == FOURCC_moof || fourcc == FOURCC_mdat) { + GST_DEBUG_OBJECT (demux, + "[ATSC] Find sync, Carrying on normally"); + demux->state = QTDEMUX_STATE_INITIAL; + } else { + gst_adapter_clear (demux->adapter); + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_LOST_SYNC; + break; + } + } else { + if (fourcc == FOURCC_ftyp) { + GST_DEBUG_OBJECT (demux, "Find sync, Carrying on normally"); + demux->state = QTDEMUX_STATE_INITIAL; + } else { + demux->todrop = gst_adapter_available (demux->adapter); + GST_LOG_OBJECT (demux, "Lost sync, drop %u byte", demux->todrop); + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) < demux->todrop) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("LOST SYNC, need to reset demux")), + ("LOST SYNC, need to reset demux")); + return GST_FLOW_ERROR; + } + gst_qtdemux_drop_data (demux, demux->todrop); + demux->neededbytes = 16; + break; + } + } + } + + if (size == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("This file is invalid and cannot be played.")), + ("initial atom '%" GST_FOURCC_FORMAT "' has empty length", + GST_FOURCC_ARGS (fourcc))); + ret = GST_FLOW_ERROR; + break; + } + if (fourcc == FOURCC_mdat) { + gint next_entry = next_entry_size (demux); + if (demux->n_streams > 0 && (next_entry != -1 || !demux->fragmented)) { + /* we have the headers, start playback */ + demux->state = QTDEMUX_STATE_MOVIE; + demux->neededbytes = next_entry; + demux->mdatleft = size; + + /* re-calculate sample offset for interleaved MMT (AU track + Hint track) */ + if (demux->upstream_format_is_time && + demux->major_brand == FOURCC_mpuf && demux->has_mmth) { + guint i; + QtDemuxStream *hint_stream = NULL, *media_stream = NULL; + guint32 hint_sample_idx = -1, media_sample_idx = -1; + if (demux->n_streams > 2) + GST_ERROR_OBJECT (demux, "# streams over two"); + + for (i = 0; i < demux->n_streams; i++) { + QtDemuxStream *tmp = demux->streams[i]; + guint32 sample_index = tmp->sample_index; + if (tmp->subtype == FOURCC_hint) { + hint_stream = tmp; + hint_sample_idx = sample_index; + if (hint_sample_idx == -1) + hint_sample_idx = 0; + } else { + media_stream = tmp; + media_sample_idx = sample_index; + if (media_sample_idx == -1) + media_sample_idx = 0; + } + } + + if (hint_stream && media_stream) { + gint64 running_offset = demux->offset + 8; + for (i = media_sample_idx; i < media_stream->n_samples; i++) { + hint_stream->samples[hint_sample_idx].offset = running_offset; + running_offset += hint_stream->samples[hint_sample_idx].size; + + media_stream->samples[media_sample_idx].offset = + running_offset; + running_offset += + media_stream->samples[media_sample_idx].size; + + hint_sample_idx++; + media_sample_idx++; + } + + demux->neededbytes = next_entry_size (demux); + } else { + GST_ERROR_OBJECT (demux, "no medai track or hint track"); + } + } + } else { + /* no headers yet, try to get them */ + guint bs; + gboolean res; + guint64 old, target; + + buffer_data: + old = demux->offset; + target = old + size; + + /* try to jump over the atom with a seek */ + /* only bother if it seems worth doing so, + * and avoids possible upstream/server problems */ + if (demux->upstream_seekable && + demux->upstream_size > 4 * (1 << 20)) { + res = qtdemux_seek_offset (demux, target); + } else { + GST_DEBUG_OBJECT (demux, "skipping seek"); + res = FALSE; + } + + if (res) { + GST_DEBUG_OBJECT (demux, "seek success"); + /* remember the offset fo the first mdat so we can seek back to it + * after we have the headers */ + if (fourcc == FOURCC_mdat && demux->first_mdat == -1) { + demux->first_mdat = old; + GST_DEBUG_OBJECT (demux, "first mdat at %" G_GUINT64_FORMAT, + demux->first_mdat); + } + /* seek worked, continue reading */ + demux->offset = target; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + } else { + /* seek failed, need to buffer */ + demux->offset = old; + GST_DEBUG_OBJECT (demux, "seek failed/skipped"); + /* there may be multiple mdat (or alike) buffers */ + /* sanity check */ + if (demux->mdatbuffer) + bs = gst_buffer_get_size (demux->mdatbuffer); + else + bs = 0; + if (size + bs > 10 * (1 << 20)) + goto no_moov; + demux->state = QTDEMUX_STATE_BUFFER_MDAT; + demux->neededbytes = size; + if (!demux->mdatbuffer) + demux->mdatoffset = demux->offset; + } + } + } else if (G_UNLIKELY (size > QTDEMUX_MAX_ATOM_SIZE)) { + if (demux->atsc3_mode) { + gst_adapter_clear (demux->adapter); + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_LOST_SYNC; + } else { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("This file is invalid and cannot be played.")), + ("atom %" GST_FOURCC_FORMAT " has bogus size %" + G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), size)); + ret = GST_FLOW_ERROR; + } + break; + } else { + /* this means we already started buffering and still no moov header, + * let's continue buffering everything till we get moov */ + if (demux->mdatbuffer && !(fourcc == FOURCC_moov + || fourcc == FOURCC_moof)) { + goto buffer_data; +#ifdef ATSC3_MODE + } else if (fourcc != FOURCC_moov && fourcc != FOURCC_moof && + size > QTDEMUX_ATSC3_MAX_ATOM_SIZE && demux->atsc3_mode && + demux->n_streams > 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("This file is invalid and cannot be played.")), + ("[ATSC3] atom %" GST_FOURCC_FORMAT " has bogus size %" + G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), size)); +#endif + } + demux->neededbytes = size; + demux->state = QTDEMUX_STATE_HEADER; + } + break; + } + case QTDEMUX_STATE_HEADER:{ + const guint8 *data; + guint32 fourcc; + + GST_DEBUG_OBJECT (demux, "In header"); + + data = gst_adapter_map (demux->adapter, demux->neededbytes); + + /* parse the header */ + extract_initial_length_and_fourcc (data, demux->neededbytes, NULL, + &fourcc); + if (fourcc == FOURCC_moov) { + gint n; + + /* in usual fragmented setup we could try to scan for more + * and end up at the the moov (after mdat) again */ + if (demux->got_moov && demux->n_streams > 0 && + (!demux->fragmented + || demux->last_moov_offset == demux->offset)) { + GST_DEBUG_OBJECT (demux, + "Skipping moov atom as we have (this) one already"); + } else { + GST_DEBUG_OBJECT (demux, "Parsing [moov]"); + + if (demux->got_moov && demux->fragmented) { + GST_DEBUG_OBJECT (demux, + "Got a second moov, clean up data from old one"); + if (demux->moov_node) + g_node_destroy (demux->moov_node); + demux->moov_node = NULL; + demux->moov_node_compressed = NULL; + } else { + /* prepare newsegment to send when streaming actually starts */ + if (!demux->pending_newsegment) { + demux->pending_newsegment = + gst_event_new_segment (&demux->segment); + if (demux->segment_seqnum) + gst_event_set_seqnum (demux->pending_newsegment, + demux->segment_seqnum); + } + } + + demux->last_moov_offset = demux->offset; + + qtdemux_parse_moov (demux, data, demux->neededbytes); + qtdemux_node_dump (demux, demux->moov_node); + qtdemux_parse_tree (demux); + qtdemux_prepare_streams (demux); + if (!demux->got_moov || demux->new_collection) { + QTDEMUX_EXPOSE_LOCK (demux); + qtdemux_expose_streams (demux); + QTDEMUX_EXPOSE_UNLOCK (demux); + } else { + + for (n = 0; n < demux->n_streams; n++) { + QtDemuxStream *stream = demux->streams[n]; + + gst_qtdemux_configure_stream (demux, stream); + } + } + + demux->got_moov = TRUE; + + if (demux->fragmented) { + /* fragmented streams headers shouldn't contain edts atoms */ +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + if (!QTDEMUX_IS_CUSTOM_MODE (demux)) + gst_qtdemux_check_send_pending_segment (demux); +#else + gst_qtdemux_check_send_pending_segment (demux); +#endif + } else { + gst_event_replace (&demux->pending_newsegment, NULL); + gst_qtdemux_map_and_push_segments (demux, &demux->segment); + } + + g_node_destroy (demux->moov_node); + demux->moov_node = NULL; + GST_DEBUG_OBJECT (demux, "Finished parsing the header"); + } + } else if (fourcc == FOURCC_moof) { + if ((demux->got_moov || demux->media_caps) && demux->fragmented) { + guint64 dist = 0; + GstClockTime prev_pts; + guint64 prev_offset; + + GST_DEBUG_OBJECT (demux, "Parsing [moof]"); + + /* + * The timestamp of the moof buffer is relevant as some scenarios + * won't have the initial timestamp in the atoms. Whenever a new + * buffer has started, we get that buffer's PTS and use it as a base + * timestamp for the trun entries. + * + * To keep track of the current buffer timestamp and starting point + * we use gst_adapter_prev_pts that gives us the PTS and the distance + * from the beggining of the buffer, with the distance and demux->offset + * we know if it is still the same buffer or not. + */ + prev_pts = gst_adapter_prev_pts (demux->adapter, &dist); + prev_offset = demux->offset - dist; + if (demux->fragment_start_offset == -1 + || prev_offset > demux->fragment_start_offset) { + demux->fragment_start_offset = prev_offset; + demux->fragment_start = prev_pts; + if (demux->upstream_format_is_time + && demux->major_brand == FOURCC_mpuf) { + GST_DEBUG_OBJECT (demux, "major brand: %" GST_FOURCC_FORMAT + ", update upstream_basetime: %" GST_TIME_FORMAT, + GST_FOURCC_ARGS (FOURCC_mpuf), GST_TIME_ARGS (prev_pts)); +#ifdef ATSC3_MODE + /* MMT pts wrap-around case + * Please don't be confused by below condition. + * "upstream_basetime" is the base PTS of previous MPU buffer + * and "prev_pts" is PTS of current MPU buffer + */ + if (demux->atsc3_mode && + GST_CLOCK_TIME_IS_VALID (demux->upstream_basetime) && + GST_CLOCK_TIME_IS_VALID (prev_pts) && + demux->upstream_basetime > prev_pts && + (demux->n_video_streams > 0 + || demux->n_audio_streams > 0)) { + GST_INFO_OBJECT (demux, + "MMT upstream_basetime wrap-around" "from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, + GST_TIME_ARGS (demux->upstream_basetime), + GST_TIME_ARGS (prev_pts)); + + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("PTS discont, Cannot be played.")), (NULL)); + + ret = GST_FLOW_ERROR; + goto done; + } +#endif + demux->upstream_basetime = prev_pts; + } + GST_DEBUG_OBJECT (demux, + "New fragment start found at: %" G_GUINT64_FORMAT " : %" + GST_TIME_FORMAT, demux->fragment_start_offset, + GST_TIME_ARGS (demux->fragment_start)); + } + + demux->moof_offset = demux->offset; + if (!qtdemux_parse_moof (demux, data, demux->neededbytes, + demux->offset, NULL)) { + gst_adapter_unmap (demux->adapter); + ret = GST_FLOW_ERROR; + goto done; + } + /* in MSS we need to expose the pads after the first moof as we won't get a moov */ + if (demux->mss_mode && !demux->exposed) { + if (!demux->pending_newsegment) { + GST_DEBUG_OBJECT (demux, "new pending_newsegment"); + demux->pending_newsegment = + gst_event_new_segment (&demux->segment); + if (demux->segment_seqnum) + gst_event_set_seqnum (demux->pending_newsegment, + demux->segment_seqnum); + } + QTDEMUX_EXPOSE_LOCK (demux); + qtdemux_expose_streams (demux); + QTDEMUX_EXPOSE_UNLOCK (demux); + } + + /* we may get new stream-start event without moov atom. + * maybe, this is the case that DASH period change without + * codec data change, and usually, PTS time-line only be changed */ + if (demux->new_collection) { + gint n; + for (n = 0; n < demux->n_streams; n++) { + QtDemuxStream *stream = demux->streams[n]; + gst_qtdemux_configure_stream (demux, stream); + } + + /* Emit collection message */ + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_stream_collection (GST_OBJECT_CAST (demux), + demux->collection)); + /* Send collection event */ + gst_qtdemux_push_event (demux, + gst_event_new_stream_collection (demux->collection)); + + demux->new_collection = FALSE; + } + + } else { + GST_DEBUG_OBJECT (demux, "Discarding [moof]"); + } + } else if (fourcc == FOURCC_ftyp) { + GST_DEBUG_OBJECT (demux, "Parsing [ftyp]"); + qtdemux_parse_ftyp (demux, data, demux->neededbytes); + if (demux->major_brand == FOURCC_mpuf) { + demux->mpu_offset = demux->offset; + GST_DEBUG_OBJECT (demux, "Update mpu offset %" G_GUINT64_FORMAT, + demux->mpu_offset); + } + } else if (fourcc == FOURCC_uuid) { + GST_DEBUG_OBJECT (demux, "Parsing [uuid]"); + qtdemux_parse_uuid (demux, data, demux->neededbytes); + } else if (fourcc == FOURCC_sidx) { + GST_DEBUG_OBJECT (demux, "Parsing [sidx]"); + qtdemux_parse_sidx (demux, data, demux->neededbytes); + } else if (fourcc == FOURCC_mmpu) { + GST_DEBUG_OBJECT (demux, "Parsing [mmpu]"); + qtdemux_parse_mmpu (demux, data, demux->neededbytes); + } else { + GST_WARNING_OBJECT (demux, + "Unknown fourcc while parsing header : %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + /* Let's jump that one and go back to initial state */ + } + gst_adapter_unmap (demux->adapter); + data = NULL; + + if (demux->mdatbuffer && demux->n_streams) { + gsize remaining_data_size = 0; + + /* the mdat was before the header */ + GST_DEBUG_OBJECT (demux, "We have n_streams:%d and mdatbuffer:%p", + demux->n_streams, demux->mdatbuffer); + + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) < demux->neededbytes) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot flush demux, data is not enouth")), + ("Cannot flush demux, data is not enouth")); + return GST_FLOW_ERROR; + } + + /* restore our adapter/offset view of things with upstream; + * put preceding buffered data ahead of current moov data. + * This should also handle evil mdat, moov, mdat cases and alike */ + gst_adapter_flush (demux->adapter, demux->neededbytes); + + /* Store any remaining data after the mdat for later usage */ + remaining_data_size = gst_adapter_available (demux->adapter); + if (remaining_data_size > 0) { + g_assert (demux->restoredata_buffer == NULL); + demux->restoredata_buffer = + gst_adapter_take_buffer (demux->adapter, remaining_data_size); + demux->restoredata_offset = demux->offset + demux->neededbytes; + GST_DEBUG_OBJECT (demux, + "Stored %" G_GSIZE_FORMAT " post mdat bytes at offset %" + G_GUINT64_FORMAT, remaining_data_size, + demux->restoredata_offset); + } + + gst_adapter_push (demux->adapter, demux->mdatbuffer); + demux->mdatbuffer = NULL; + demux->offset = demux->mdatoffset; + demux->neededbytes = next_entry_size (demux); + demux->state = QTDEMUX_STATE_MOVIE; + demux->mdatleft = gst_adapter_available (demux->adapter); + } else { + GST_DEBUG_OBJECT (demux, "Carrying on normally"); + gst_adapter_flush (demux->adapter, demux->neededbytes); + + /* only go back to the mdat if there are samples to play */ + if (demux->got_moov && demux->first_mdat != -1 + && has_next_entry (demux)) { + gboolean res; + + /* we need to seek back */ + res = qtdemux_seek_offset (demux, demux->first_mdat); + if (res) { + demux->offset = demux->first_mdat; + } else { + GST_DEBUG_OBJECT (demux, "Seek back failed"); + } + } else { + demux->offset += demux->neededbytes; + } + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + } + + break; + } + case QTDEMUX_STATE_BUFFER_MDAT:{ + GstBuffer *buf; + guint8 fourcc[4]; + + GST_DEBUG_OBJECT (demux, "Got our buffer at offset %" G_GUINT64_FORMAT, + demux->offset); + buf = gst_adapter_take_buffer (demux->adapter, demux->neededbytes); + if (demux->atsc3_mode && buf == NULL) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot take buffer, data is not enouth")), + ("Cannot take buffer, data is not enouth")); + return GST_FLOW_ERROR; + } + gst_buffer_extract (buf, 0, fourcc, 4); + GST_DEBUG_OBJECT (demux, "mdatbuffer starts with %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_FOURCC (fourcc))); + if (demux->mdatbuffer) + demux->mdatbuffer = gst_buffer_append (demux->mdatbuffer, buf); + else + demux->mdatbuffer = buf; + demux->offset += demux->neededbytes; + demux->neededbytes = 16; + demux->state = QTDEMUX_STATE_INITIAL; + gst_qtdemux_post_progress (demux, 1, 1); + + break; + } + case QTDEMUX_STATE_MOVIE:{ + QtDemuxStream *stream = NULL; + QtDemuxSample *sample; + int i = -1; + GstClockTime dts, pts, duration; + gboolean keyframe; + gboolean rewind_check, forward_check; + GST_DEBUG_OBJECT (demux, + "BEGIN // in MOVIE for offset %" G_GUINT64_FORMAT, demux->offset); +#ifdef MP4_PUSHMODE_TRICK + + rewind_check = (demux->segment.rate < 0.0 && ((demux->pushed_Iframe + && demux->all_audio_pushed) + || demux->offset >= demux->prev_seek_offset) + && demux->segment_event_recvd); + forward_check = (demux->segment.rate > 2.0 + && ((demux->pushed_Iframe && demux->pushed_Audio) + || demux->offset <= demux->next_seek_offset) + && demux->segment_event_recvd); + if (rewind_check || forward_check) { + demux->all_audio_pushed = FALSE; + demux->pushed_Audio = FALSE; + demux->pushed_Iframe = FALSE; + + gst_qtdemux_handle_trick (demux); + goto eos; + } else if (demux->segment.rate == 0.5 && (demux->dlna_opval == 0x01 || + demux->dlna_opval == 0x11) && demux->pushed_Iframe) { + demux->pushed_Iframe = FALSE; + demux->rate_changed = FALSE; + qtdemux_seek_offset (demux, demux->offset); + } else { +#endif + if (demux->fragmented) { + GST_DEBUG_OBJECT (demux, "mdat remaining %" G_GUINT64_FORMAT, + demux->mdatleft); + if (G_LIKELY (demux->todrop < demux->mdatleft)) { + /* if needed data starts within this atom, + * then it should not exceed this atom */ + if (G_UNLIKELY (demux->neededbytes > demux->mdatleft)) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("This file is invalid and cannot be played.")), + ("sample data crosses atom boundary")); + ret = GST_FLOW_ERROR; + break; + } + demux->mdatleft -= demux->neededbytes; + } else { + GST_DEBUG_OBJECT (demux, "data atom emptied; resuming atom scan"); + /* so we are dropping more than left in this atom */ + gst_qtdemux_drop_data (demux, demux->mdatleft); + demux->mdatleft = 0; + + /* need to resume atom parsing so we do not miss any other pieces */ + if (demux->atsc3_mode) + demux->state = QTDEMUX_STATE_LOST_SYNC; + else + demux->state = QTDEMUX_STATE_INITIAL; + demux->neededbytes = 16; + + /* check if there was any stored post mdat data from previous buffers */ + if (demux->restoredata_buffer) { + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) != 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot restore buffer, need to reset demux")), + ("Cannot restore buffer, need to reset demux")); + return GST_FLOW_ERROR; + } + + g_assert (gst_adapter_available (demux->adapter) == 0); + + gst_adapter_push (demux->adapter, demux->restoredata_buffer); + demux->restoredata_buffer = NULL; + demux->offset = demux->restoredata_offset; + } + + break; + } + } + + if (demux->todrop) { + if (demux->cenc_aux_info_offset > 0) { + GstByteReader br; + const guint8 *data; + + GST_DEBUG_OBJECT (demux, "parsing cenc auxiliary info"); + data = gst_adapter_map (demux->adapter, demux->todrop); + gst_byte_reader_init (&br, data + 8, demux->todrop); + if (!qtdemux_parse_cenc_aux_info (demux, demux->streams[0], &br, + demux->cenc_aux_info_sizes, + demux->cenc_aux_sample_count)) { + GST_ERROR_OBJECT (demux, "failed to parse cenc auxiliary info"); + ret = GST_FLOW_ERROR; + gst_adapter_unmap (demux->adapter); + g_free (demux->cenc_aux_info_sizes); + demux->cenc_aux_info_sizes = NULL; + goto done; + } + demux->cenc_aux_info_offset = 0; + g_free (demux->cenc_aux_info_sizes); + demux->cenc_aux_info_sizes = NULL; + gst_adapter_unmap (demux->adapter); + } + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) < demux->todrop) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("LOST SYNC, need to reset demux")), + ("LOST SYNC, need to reset demux")); + return GST_FLOW_ERROR; + } + gst_qtdemux_drop_data (demux, demux->todrop); + } + + /* first buffer? */ + /* initial newsegment sent here after having added pads, + * possible others in sink_event */ + gst_qtdemux_check_send_pending_segment (demux); + + /* Figure out which stream this packet belongs to */ + for (i = 0; i < demux->n_streams; i++) { + stream = demux->streams[i]; + if (stream->sample_index >= stream->n_samples) + continue; + GST_LOG_OBJECT (demux, + "Checking stream %d (sample_index:%d / offset:%" + G_GUINT64_FORMAT " / size:%d)", i, stream->sample_index, + stream->samples[stream->sample_index].offset, + stream->samples[stream->sample_index].size); + + if (stream->samples[stream->sample_index].offset == demux->offset) + break; + } + + if (G_UNLIKELY (stream == NULL || i == demux->n_streams)) + goto unknown_stream; + + if (!demux->pullbased && !demux->isInterleaved) { + goto unknown_stream; + } + + gst_qtdemux_stream_check_and_change_stsd_index (demux, stream); + + if (stream->new_caps) { + gst_qtdemux_configure_stream (demux, stream); + } + + /* + * From ISOBMFF spec, each sample which belongs to each other tracks + * cannot be interleaved in a mdat. e.g., front half of mdat should be for track A, + * and the other should be for track B based on the ISOBMFF spec. + * However, MMT spec said that hint sample and media sample should be interleaved + * from sender side, and receiver must identiy the media sample + * based on corresponding hint sample. + * So, we cannot believe sample offset constructed by trun, because the trun + * indicates the mdat structure of before interleaved. + * + * + * + * [ mdat ] [ mdat ] [ mdat ] + * | AU#1 | | hint#1 | | hint#1 | + * | AU#2 | | hint#2 | convert | AU#1 | + * | .... | OR | .... | =======> | hint#2 | + * | hint#1 | | AU#1 | | AU#2 | + * | hint#2 | | AU#2 | | .... | + * | .... | | .... | | .... | + * + */ + if (demux->upstream_format_is_time && + demux->major_brand == FOURCC_mpuf && demux->has_mmth && + demux->is_mmth_timed && stream->subtype == FOURCC_hint) { + guint32 sample_index = -1; + const guint8 *data; + guint64 dropped = 0; + + if (gst_adapter_available (demux->adapter) == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Data is not available, reset demux")), + ("Data is not available, reset demux")); + return GST_FLOW_ERROR; + } + + data = gst_adapter_map (demux->adapter, + gst_adapter_available (demux->adapter)); + + if (qtdemux_parse_mmth_sample (demux, data, + gst_adapter_available (demux->adapter), &sample_index)) { + if (demux->ignore_hintsample) { + GST_DEBUG_OBJECT (demux, "ignore resync by hint sample"); + } else if (sample_index != stream->sample_index) { + GST_ERROR_OBJECT (demux, "sample_index jump from %" + G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT, + stream->sample_index, sample_index); + for (i = 0; i < demux->n_streams; i++) { + QtDemuxStream *stream_iter = demux->streams[i]; + stream_iter->sample_index = sample_index; + } + dropped = + stream->samples[stream->sample_index].offset - + demux->offset; + + GST_ERROR_OBJECT (demux, "%" G_GUINT64_FORMAT + " byte MFU dropped", dropped); + + demux->neededbytes = stream->samples[stream->sample_index].size; + demux->offset = stream->samples[stream->sample_index].offset; + demux->mdatleft -= dropped; + } + gst_adapter_unmap (demux->adapter); + } else { + GstBuffer *restore_data; + gst_adapter_unmap (demux->adapter); + + if (gst_adapter_available (demux->adapter) == 0) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Hint sample is broken but no available data")), + ("Hint sample is broken but no available data")); + return GST_FLOW_ERROR; + } + + restore_data = gst_adapter_take_buffer (demux->adapter, + gst_adapter_available (demux->adapter)); + + GST_ERROR_OBJECT (demux, "fail to parse hint sample. Reset"); + gst_qtdemux_reset (demux, FALSE); + + /* upstream will not resend segment event, so let's do this ourselves */ + demux->upstream_format_is_time = TRUE; + demux->state = QTDEMUX_STATE_LOST_SYNC; + gst_adapter_push (demux->adapter, restore_data); + + break; + } + } + + /* Put data in a buffer, set timestamps, caps, ... */ + sample = &stream->samples[stream->sample_index]; + + if (G_LIKELY (!(STREAM_IS_EOS (stream)))) { + GST_DEBUG_OBJECT (demux, "stream : %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->fourcc)); + + dts = QTSAMPLE_DTS (stream, sample); + pts = QTSAMPLE_PTS (stream, sample); + duration = QTSAMPLE_DUR_DTS (stream, sample, dts); + keyframe = QTSAMPLE_KEYFRAME (stream, sample); + +#ifdef MP4_PUSHMODE_TRICK + if (qtdemux_is_keyframe_trick_condition (demux) + && (!demux->pullbased)) { + if (strstr (GST_PAD_NAME (stream->pad), "video")) { + + if (demux->pushed_Audio) { + demux->all_audio_pushed = TRUE; + } + + if (keyframe) { + GstBuffer *outbuf; + + outbuf = + gst_adapter_take_buffer (demux->adapter, + demux->neededbytes); + + /* FIXME: should either be an assert or a plain check */ + g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); + + ret = + gst_qtdemux_decorate_and_push_buffer (demux, stream, + outbuf, dts, pts, duration, keyframe, dts, demux->offset); + demux->pushed_Iframe = TRUE; + + ret = gst_qtdemux_combine_flows (demux, stream, ret); + } else { + gst_adapter_flush (demux->adapter, demux->neededbytes); + } + } else if (strstr (GST_PAD_NAME (stream->pad), "audio")) { + GstBuffer *outbuf; + + outbuf = + gst_adapter_take_buffer (demux->adapter, + demux->neededbytes); + + /* FIXME: should either be an assert or a plain check */ + g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); + + ret = + gst_qtdemux_decorate_and_push_buffer (demux, stream, + outbuf, dts, pts, duration, keyframe, dts, demux->offset); + demux->pushed_Audio = TRUE; + if (demux->segment.rate > 2.0 && demux->pushed_Iframe) + demux->segment_event_recvd = FALSE; + + ret = gst_qtdemux_combine_flows (demux, stream, ret); + } else { + stream->buffers = + g_slist_delete_link (stream->buffers, stream->buffers); + } + } else { +#endif + /* check for segment end */ + if (G_UNLIKELY (demux->segment.stop != -1 + && demux->segment.stop <= pts && stream->on_keyframe)) { + GST_DEBUG_OBJECT (demux, "we reached the end of our segment."); + stream->time_position = GST_CLOCK_TIME_NONE; /* this means EOS */ + + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) < + demux->neededbytes) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot flush demux, data is not enouth")), + ("Cannot flush demux, data is not enouth")); + return GST_FLOW_ERROR; + } + + /* skip this data, stream is EOS */ + gst_adapter_flush (demux->adapter, demux->neededbytes); + + /* check if all streams are eos */ + ret = GST_FLOW_EOS; + for (i = 0; i < demux->n_streams; i++) { + if (!STREAM_IS_EOS (demux->streams[i])) { + ret = GST_FLOW_OK; + break; + } + } + + if (ret == GST_FLOW_EOS) { + GST_DEBUG_OBJECT (demux, + "All streams are EOS, signal upstream"); + goto eos; + } + } else { + GstBuffer *outbuf; + + outbuf = + gst_adapter_take_buffer (demux->adapter, + demux->neededbytes); + + if (demux->atsc3_mode && outbuf == NULL) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot take buffer, data is not enouth")), + ("Cannot take buffer, data is not enouth")); + return GST_FLOW_ERROR; + } + + /* FIXME: should either be an assert or a plain check */ + g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); + + if (demux->segment.rate == 0.5 && keyframe + && strstr (GST_PAD_NAME (stream->pad), "video")) { + demux->pushed_Iframe = TRUE; + } + + /* non-interleaved (BL & EL) Dolby Vision dual track file + * needs to drop buffer when perform push-mode seek, + * because non-key frame buffer can be followed by + * key frame buffe which is the desirable seek position */ + if (demux->is_dolby_hdr && demux->has_dolby_bl_cand && + demux->has_dolby_el_cand && demux->segment.start > dts) { + GST_LOG_OBJECT (demux, + "Drop buffer with dts %" GST_TIME_FORMAT ", pts %" + GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT + " on pad %s", GST_TIME_ARGS (dts), GST_TIME_ARGS (pts), + GST_TIME_ARGS (duration), GST_PAD_NAME (stream->pad)); + gst_buffer_unref (outbuf); + ret = GST_FLOW_OK; + } else { + ret = + gst_qtdemux_decorate_and_push_buffer (demux, stream, + outbuf, dts, pts, duration, keyframe, dts, demux->offset); + } + } + + /* combine flows */ + ret = gst_qtdemux_combine_flows (demux, stream, ret); + if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED) + goto non_ok_unlinked_flow; + +#ifdef MP4_PUSHMODE_TRICK + } +#endif + } else { + if (demux->atsc3_mode && + gst_adapter_available (demux->adapter) < demux->neededbytes) { + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, + (_("Cannot flush demux, data is not enouth")), + ("Cannot flush demux, data is not enouth")); + return GST_FLOW_ERROR; + } + + /* skip this data, stream is EOS */ + gst_adapter_flush (demux->adapter, demux->neededbytes); + } + + stream->sample_index++; + stream->offset_in_sample = 0; + + /* update current offset and figure out size of next buffer */ + GST_LOG_OBJECT (demux, + "increasing offset %" G_GUINT64_FORMAT " by %u", demux->offset, + demux->neededbytes); + demux->offset += demux->neededbytes; + GST_LOG_OBJECT (demux, "offset is now %" G_GUINT64_FORMAT, + demux->offset); +#ifdef MP4_PUSHMODE_TRICK + } +#endif + if ((demux->neededbytes = next_entry_size (demux)) == -1) { + if (demux->fragmented) { + GST_DEBUG_OBJECT (demux, "(temporarily) out of fragmented samples"); + /* there may be more to follow, only finish this atom */ + demux->todrop = demux->mdatleft; + demux->neededbytes = demux->todrop; + break; + } + goto eos; + } + break; + } + default: + goto invalid_state; + } + } + + /* when buffering movie data, at least show user something is happening */ + if (ret == GST_FLOW_OK && demux->state == QTDEMUX_STATE_BUFFER_MDAT && + gst_adapter_available (demux->adapter) <= demux->neededbytes) { + gst_qtdemux_post_progress (demux, gst_adapter_available (demux->adapter), + demux->neededbytes); + } +done: + + return ret; + + /* ERRORS */ +non_ok_unlinked_flow: + { + GST_DEBUG_OBJECT (demux, "Stopping, combined return flow %s", + gst_flow_get_name (ret)); + return ret; + } +unknown_stream: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL), ("unknown stream found")); + ret = GST_FLOW_ERROR; + goto done; + } +eos: + { + GST_DEBUG_OBJECT (demux, "no next entry, EOS"); + ret = GST_FLOW_EOS; + goto done; + } +invalid_state: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (NULL), ("qtdemuxer invalid state %d", demux->state)); + ret = GST_FLOW_ERROR; + goto done; + } +no_moov: + { + GST_ELEMENT_ERROR (demux, STREAM, FAILED, + (NULL), ("no 'moov' atom within the first 10 MB")); + ret = GST_FLOW_ERROR; + goto done; + } +} + +static gboolean +qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent) +{ + GstQuery *query; + gboolean pull_mode; + + query = gst_query_new_scheduling (); + + if (!gst_pad_peer_query (sinkpad, query)) { + gst_query_unref (query); + goto activate_push; + } + + pull_mode = gst_query_has_scheduling_mode_with_flags (query, + GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE); + gst_query_unref (query); + + if (!pull_mode) + goto activate_push; + + GST_DEBUG_OBJECT (sinkpad, "activating pull"); + return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE); + +activate_push: + { + GST_DEBUG_OBJECT (sinkpad, "activating push"); + return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE); + } +} + +static gboolean +qtdemux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, + GstPadMode mode, gboolean active) +{ + gboolean res; + GstQTDemux *demux = GST_QTDEMUX (parent); + + switch (mode) { + case GST_PAD_MODE_PUSH: + demux->pullbased = FALSE; + res = TRUE; + break; + case GST_PAD_MODE_PULL: + if (active) { + demux->pullbased = TRUE; + res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_qtdemux_loop, + sinkpad, NULL); + } else { + res = gst_pad_stop_task (sinkpad); + } + break; + default: + res = FALSE; + break; + } + return res; +} + +#ifdef HAVE_ZLIB +static void * +qtdemux_zalloc (void *opaque, unsigned int items, unsigned int size) +{ + return g_malloc (items * size); +} + +static void +qtdemux_zfree (void *opaque, void *addr) +{ + g_free (addr); +} + +static void * +qtdemux_inflate (void *z_buffer, guint z_length, guint length) +{ + guint8 *buffer; + z_stream *z; + int ret; + + z = g_new0 (z_stream, 1); + z->zalloc = qtdemux_zalloc; + z->zfree = qtdemux_zfree; + z->opaque = NULL; + + z->next_in = z_buffer; + z->avail_in = z_length; + + buffer = (guint8 *) g_malloc (length); + ret = inflateInit (z); + while (z->avail_in > 0) { + if (z->avail_out == 0) { + length += 1024; + buffer = (guint8 *) g_realloc (buffer, length); + z->next_out = buffer + z->total_out; + z->avail_out = 1024; + } + ret = inflate (z, Z_SYNC_FLUSH); + if (ret != Z_OK) + break; + } + if (ret != Z_STREAM_END) { + g_warning ("inflate() returned %d", ret); + } + + g_free (z); + return buffer; +} +#endif /* HAVE_ZLIB */ + +static gboolean +qtdemux_parse_moov (GstQTDemux * qtdemux, const guint8 * buffer, guint length) +{ + GNode *cmov; + + qtdemux->moov_node = g_node_new ((guint8 *) buffer); + + /* counts as header data */ + qtdemux->header_size += length; + + GST_DEBUG_OBJECT (qtdemux, "parsing 'moov' atom"); + qtdemux_parse_node (qtdemux, qtdemux->moov_node, buffer, length); + + cmov = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_cmov); + if (cmov) { + guint32 method; + GNode *dcom; + GNode *cmvd; + + dcom = qtdemux_tree_get_child_by_type (cmov, FOURCC_dcom); + cmvd = qtdemux_tree_get_child_by_type (cmov, FOURCC_cmvd); + if (dcom == NULL || cmvd == NULL) + goto invalid_compression; + + method = QT_FOURCC ((guint8 *) dcom->data + 8); + switch (method) { +#ifdef HAVE_ZLIB + case FOURCC_zlib:{ + guint uncompressed_length; + guint compressed_length; + guint8 *buf; + + uncompressed_length = QT_UINT32 ((guint8 *) cmvd->data + 8); + compressed_length = QT_UINT32 ((guint8 *) cmvd->data + 4) - 12; + GST_LOG ("length = %u", uncompressed_length); + + buf = + (guint8 *) qtdemux_inflate ((guint8 *) cmvd->data + 12, + compressed_length, uncompressed_length); + + qtdemux->moov_node_compressed = qtdemux->moov_node; + qtdemux->moov_node = g_node_new (buf); + + qtdemux_parse_node (qtdemux, qtdemux->moov_node, buf, + uncompressed_length); + break; + } +#endif /* HAVE_ZLIB */ + default: + GST_WARNING_OBJECT (qtdemux, "unknown or unhandled header compression " + "type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (method)); + break; + } + } + return TRUE; + + /* ERRORS */ +invalid_compression: + { + GST_ERROR_OBJECT (qtdemux, "invalid compressed header"); + return FALSE; + } +} + +static gboolean +qtdemux_parse_container (GstQTDemux * qtdemux, GNode * node, const guint8 * buf, + const guint8 * end) +{ + while (G_UNLIKELY (buf < end)) { + GNode *child; + guint32 len; + + if (G_UNLIKELY (buf + 4 > end)) { + GST_LOG_OBJECT (qtdemux, "buffer overrun"); + break; + } + len = QT_UINT32 (buf); + if (G_UNLIKELY (len == 0)) { + GST_LOG_OBJECT (qtdemux, "empty container"); + break; + } + if (G_UNLIKELY (len < 8)) { + GST_WARNING_OBJECT (qtdemux, "length too short (%d < 8)", len); + break; + } + if (G_UNLIKELY (len > (end - buf))) { + GST_WARNING_OBJECT (qtdemux, "length too long (%d > %d)", len, + (gint) (end - buf)); + break; + } + + child = g_node_new ((guint8 *) buf); + g_node_append (node, child); + GST_LOG_OBJECT (qtdemux, "adding new node of len %d", len); + qtdemux_parse_node (qtdemux, child, buf, len); + + buf += len; + } + return TRUE; +} + +static gboolean +qtdemux_parse_theora_extension (GstQTDemux * qtdemux, QtDemuxStream * stream, + GNode * xdxt) +{ + int len = QT_UINT32 (xdxt->data); + guint8 *buf = xdxt->data; + guint8 *end = buf + len; + GstBuffer *buffer; + + /* skip size and type */ + buf += 8; + end -= 8; + + while (buf < end) { + gint size; + guint32 type; + + size = QT_UINT32 (buf); + type = QT_FOURCC (buf + 4); + + GST_LOG_OBJECT (qtdemux, "%p %p", buf, end); + + if (buf + size > end || size <= 0) + break; + + buf += 8; + size -= 8; + + GST_WARNING_OBJECT (qtdemux, "have cookie %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (type)); + + switch (type) { + case FOURCC_tCtH: + buffer = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buffer, 0, buf, size); + stream->buffers = g_slist_append (stream->buffers, buffer); + GST_LOG_OBJECT (qtdemux, "parsing theora header"); + break; + case FOURCC_tCt_: + buffer = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buffer, 0, buf, size); + stream->buffers = g_slist_append (stream->buffers, buffer); + GST_LOG_OBJECT (qtdemux, "parsing theora comment"); + break; + case FOURCC_tCtC: + buffer = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buffer, 0, buf, size); + stream->buffers = g_slist_append (stream->buffers, buffer); + GST_LOG_OBJECT (qtdemux, "parsing theora codebook"); + break; + default: + GST_WARNING_OBJECT (qtdemux, + "unknown theora cookie %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (type)); + break; + } + buf += size; + } + return TRUE; +} + +static gboolean +qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, + guint length) +{ + guint32 fourcc = 0; + guint32 node_length = 0; + const QtNodeType *type; + const guint8 *end; + + GST_LOG_OBJECT (qtdemux, "qtdemux_parse buffer %p length %u", buffer, length); + + if (G_UNLIKELY (length < 8)) + goto not_enough_data; + + node_length = QT_UINT32 (buffer); + fourcc = QT_FOURCC (buffer + 4); + + /* ignore empty nodes */ + if (G_UNLIKELY (fourcc == 0 || node_length == 8)) + return TRUE; + + type = qtdemux_type_get (fourcc); + + end = buffer + length; + + GST_LOG_OBJECT (qtdemux, + "parsing '%" GST_FOURCC_FORMAT "', length=%u, name '%s'", + GST_FOURCC_ARGS (fourcc), node_length, type->name); + + if (node_length > length) + goto broken_atom_size; + + if (type->flags & QT_FLAG_CONTAINER) { + qtdemux_parse_container (qtdemux, node, buffer + 8, end); + } else { + switch (fourcc) { + case FOURCC_stsd: + { + if (node_length < 20) { + GST_LOG_OBJECT (qtdemux, "skipping small stsd box"); + break; + } + GST_DEBUG_OBJECT (qtdemux, + "parsing stsd (sample table, sample description) atom"); + /* Skip over 8 byte atom hdr + 1 byte version, 3 bytes flags, 4 byte num_entries */ + qtdemux_parse_container (qtdemux, node, buffer + 16, end); + break; + } + case FOURCC_mp4a: + case FOURCC_alac: + { + guint32 version; + guint32 offset; + guint min_size; + + /* also read alac (or whatever) in stead of mp4a in the following, + * since a similar layout is used in other cases as well */ + if (fourcc == FOURCC_mp4a) + min_size = 20; + else + min_size = 40; + + /* There are two things we might encounter here: a true mp4a atom, and + an mp4a entry in an stsd atom. The latter is what we're interested + in, and it looks like an atom, but isn't really one. The true mp4a + atom is short, so we detect it based on length here. */ + if (length < min_size) { + GST_LOG_OBJECT (qtdemux, "skipping small %" GST_FOURCC_FORMAT " box", + GST_FOURCC_ARGS (fourcc)); + break; + } + + /* 'version' here is the sound sample description version. Types 0 and + 1 are documented in the QTFF reference, but type 2 is not: it's + described in Apple header files instead (struct SoundDescriptionV2 + in Movies.h) */ + version = QT_UINT16 (buffer + 16); + + GST_DEBUG_OBJECT (qtdemux, "%" GST_FOURCC_FORMAT " version 0x%08x", + GST_FOURCC_ARGS (fourcc), version); + + /* parse any esds descriptors */ + switch (version) { + case 0: + offset = 0x24; + break; + case 1: + offset = 0x34; + break; + case 2: + offset = 0x48; + break; + default: + GST_WARNING_OBJECT (qtdemux, + "unhandled %" GST_FOURCC_FORMAT " version 0x%08x", + GST_FOURCC_ARGS (fourcc), version); + offset = 0; + break; + } + if (offset) + qtdemux_parse_container (qtdemux, node, buffer + offset, end); + break; + } + case FOURCC_mp4v: + case FOURCC_MP4V: + case FOURCC_fmp4: + case FOURCC_FMP4: + case FOURCC_apcs: + case FOURCC_apch: + case FOURCC_apcn: + case FOURCC_apco: + case FOURCC_ap4h: + { + const guint8 *buf; + guint32 version; + int tlen; + + /* codec_data is contained inside these atoms, which all have + * the same format. */ + + GST_DEBUG_OBJECT (qtdemux, "parsing in %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + version = QT_UINT32 (buffer + 16); + GST_DEBUG_OBJECT (qtdemux, "version %08x", version); + if (1 || version == 0x00000000) { + buf = buffer + 0x32; + + /* FIXME Quicktime uses PASCAL string while + * the iso format uses C strings. Check the file + * type before attempting to parse the string here. */ + tlen = QT_UINT8 (buf); + GST_DEBUG_OBJECT (qtdemux, "tlen = %d", tlen); + buf++; + GST_DEBUG_OBJECT (qtdemux, "string = %.*s", tlen, (char *) buf); + /* the string has a reserved space of 32 bytes so skip + * the remaining 31 */ + buf += 31; + buf += 4; /* and 4 bytes reserved */ + + GST_MEMDUMP_OBJECT (qtdemux, "mp4v", buf, end - buf); + + qtdemux_parse_container (qtdemux, node, buf, end); + } + break; + } + case FOURCC_H264: + { + GST_MEMDUMP_OBJECT (qtdemux, "H264", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } + case FOURCC_avc1: + { + GST_MEMDUMP_OBJECT (qtdemux, "avc1", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } + case FOURCC_avc3: + { + GST_MEMDUMP_OBJECT (qtdemux, "avc3", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvav: + { + GST_MEMDUMP_OBJECT (qtdemux, "dvav", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); + qtdemux->has_dolby_el_cand = TRUE; + break; + } +#endif + case FOURCC_H265: + { + GST_MEMDUMP_OBJECT (qtdemux, "H265", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } + case FOURCC_hvc1: + { + GST_MEMDUMP_OBJECT (qtdemux, "hvc1", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } + case FOURCC_hev1: + { + GST_MEMDUMP_OBJECT (qtdemux, "hev1", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); +#ifdef DOLBYHDR_SUPPORT + qtdemux->has_dolby_bl_cand = TRUE; +#endif + break; + } +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvhe: + { + GST_MEMDUMP_OBJECT (qtdemux, "dvhe", buffer, end - buffer); + qtdemux_parse_container (qtdemux, node, buffer + 0x56, end); + qtdemux->has_dolby_el_cand = TRUE; + break; + } + case FOURCC_dvcC: + { + GST_MEMDUMP_OBJECT (qtdemux, "dvcC", buffer, end - buffer); + if (node_length != 0x20) { + GST_LOG_OBJECT (qtdemux, "Invalid dvcC node length %d", node_length); + break; + } + qtdemux->dv_profile = (QT_UINT8 (buffer + 10) >> 1) & 0x7f; + qtdemux->rpu_present_flag = (QT_UINT8 (buffer + 11) >> 2) & 0x01; + qtdemux->el_present_flag = (QT_UINT8 (buffer + 11) >> 1) & 0x01; + qtdemux->bl_present_flag = QT_UINT8 (buffer + 11) & 0x01; + + GST_LOG_OBJECT (qtdemux, + "dvCC, dv_profile = %d, rpu_present_flag = %d, " + "el_present_flag = %d, bl_present_flag = %d", + qtdemux->dv_profile, + qtdemux->rpu_present_flag, qtdemux->el_present_flag, + qtdemux->bl_present_flag); + + qtdemux_parse_container (qtdemux, node, buffer + 0x20, end); + qtdemux->is_dolby_hdr = TRUE; + break; + } +#endif + case FOURCC_mjp2: + { + qtdemux_parse_container (qtdemux, node, buffer + 86, end); + break; + } + case FOURCC_meta: + { + GST_DEBUG_OBJECT (qtdemux, "parsing meta atom"); + qtdemux_parse_container (qtdemux, node, buffer + 12, end); + break; + } + case FOURCC_mp4s: + { + GST_MEMDUMP_OBJECT (qtdemux, "mp4s", buffer, end - buffer); + /* Skip 8 byte header, plus 8 byte version + flags + entry_count */ + qtdemux_parse_container (qtdemux, node, buffer + 16, end); + break; + } + case FOURCC_XiTh: + { + guint32 version; + guint32 offset; + + version = QT_UINT32 (buffer + 12); + GST_DEBUG_OBJECT (qtdemux, "parsing XiTh atom version 0x%08x", version); + + switch (version) { + case 0x00000001: + offset = 0x62; + break; + default: + GST_DEBUG_OBJECT (qtdemux, "unknown version 0x%08x", version); + offset = 0; + break; + } + if (offset) + qtdemux_parse_container (qtdemux, node, buffer + offset, end); + break; + } + case FOURCC_in24: + { + qtdemux_parse_container (qtdemux, node, buffer + 0x34, end); + break; + } + case FOURCC_uuid: + { + qtdemux_parse_uuid (qtdemux, buffer, end - buffer); + break; + } + case FOURCC_encv: + { + qtdemux_parse_container (qtdemux, node, buffer + 86, end); + break; + } + case FOURCC_enca: + { + qtdemux_parse_container (qtdemux, node, buffer + 36, end); + break; + } + case FOURCC_ec_3: + { + qtdemux_parse_container (qtdemux, node, buffer + 36, end); + break; + } + default: + if (!strcmp (type->name, "unknown")) + GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4); + break; + } + } + GST_LOG_OBJECT (qtdemux, "parsed '%" GST_FOURCC_FORMAT "'", + GST_FOURCC_ARGS (fourcc)); + return TRUE; + +/* ERRORS */ +not_enough_data: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), + ("Not enough data for an atom header, got only %u bytes", length)); + return FALSE; + } +broken_atom_size: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), + ("Atom '%" GST_FOURCC_FORMAT "' has size of %u bytes, but we have only " + "%u bytes available.", GST_FOURCC_ARGS (fourcc), node_length, + length)); + return FALSE; + } +} + +static GNode * +qtdemux_tree_get_child_by_type (GNode * node, guint32 fourcc) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc; + + for (child = g_node_first_child (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_fourcc = QT_FOURCC (buffer + 4); + + if (G_UNLIKELY (child_fourcc == fourcc)) { + return child; + } + } + return NULL; +} + +static GNode * +qtdemux_tree_get_child_by_type_full (GNode * node, guint32 fourcc, + GstByteReader * parser) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc, child_len; + + for (child = g_node_first_child (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_len = QT_UINT32 (buffer); + child_fourcc = QT_FOURCC (buffer + 4); + + if (G_UNLIKELY (child_fourcc == fourcc)) { + if (G_UNLIKELY (child_len < (4 + 4))) + return NULL; + /* FIXME: must verify if atom length < parent atom length */ + gst_byte_reader_init (parser, buffer + (4 + 4), child_len - (4 + 4)); + return child; + } + } + return NULL; +} + +static GNode * +qtdemux_tree_get_child_by_index (GNode * node, guint index) +{ + return g_node_nth_child (node, index); +} + +static GNode * +qtdemux_tree_get_sibling_by_type_full (GNode * node, guint32 fourcc, + GstByteReader * parser) +{ + GNode *child; + guint8 *buffer; + guint32 child_fourcc, child_len; + + for (child = g_node_next_sibling (node); child; + child = g_node_next_sibling (child)) { + buffer = (guint8 *) child->data; + + child_fourcc = QT_FOURCC (buffer + 4); + + if (child_fourcc == fourcc) { + if (parser) { + child_len = QT_UINT32 (buffer); + if (G_UNLIKELY (child_len < (4 + 4))) + return NULL; + /* FIXME: must verify if atom length < parent atom length */ + gst_byte_reader_init (parser, buffer + (4 + 4), child_len - (4 + 4)); + } + return child; + } + } + return NULL; +} + +static GNode * +qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc) +{ + return qtdemux_tree_get_sibling_by_type_full (node, fourcc, NULL); +} + +static void +qtdemux_do_allocation (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ +/* FIXME: This can only reliably work if demuxers have a + * separate streaming thread per srcpad. This should be + * done in a demuxer base class, which integrates parts + * of multiqueue + * + * https://bugzilla.gnome.org/show_bug.cgi?id=701856 + */ +#if 0 + GstQuery *query; + + query = gst_query_new_allocation (stream->caps, FALSE); + + if (!gst_pad_peer_query (stream->pad, query)) { + /* not a problem, just debug a little */ + GST_DEBUG_OBJECT (qtdemux, "peer ALLOCATION query failed"); + } + + if (stream->allocator) + gst_object_unref (stream->allocator); + + if (gst_query_get_n_allocation_params (query) > 0) { + /* try the allocator */ + gst_query_parse_nth_allocation_param (query, 0, &stream->allocator, + &stream->params); + stream->use_allocator = TRUE; + } else { + stream->allocator = NULL; + gst_allocation_params_init (&stream->params); + stream->use_allocator = FALSE; + } + gst_query_unref (query); +#endif +} + +static gboolean +gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux, + QtDemuxStream * stream) +{ + GstStructure *s; + const gchar *selected_system; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (gst_caps_get_size (stream->caps) == 1, FALSE); + + if (!is_common_enc_scheme_type (stream->protection_scheme_type)) { + GST_ERROR_OBJECT (qtdemux, "unsupported protection scheme"); + return FALSE; + } + if (qtdemux->protection_system_ids == NULL) { + GST_ERROR_OBJECT (qtdemux, "stream is protected using cenc, but no " + "cenc protection system information has been found"); + return FALSE; + } + g_ptr_array_add (qtdemux->protection_system_ids, NULL); + selected_system = gst_protection_select_system ((const gchar **) + qtdemux->protection_system_ids->pdata); + g_ptr_array_remove_index (qtdemux->protection_system_ids, + qtdemux->protection_system_ids->len - 1); + if (!selected_system) { + GST_ERROR_OBJECT (qtdemux, "stream is protected, but no " + "suitable decryptor element has been found"); + return FALSE; + } + + s = gst_caps_get_structure (stream->caps, 0); + if (!gst_structure_has_name (s, "application/x-cenc")) { + gst_structure_set (s, + "original-media-type", G_TYPE_STRING, gst_structure_get_name (s), + GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, G_TYPE_STRING, selected_system, + NULL); + gst_structure_set_name (s, "application/x-cenc"); + } + return TRUE; +} + +#ifdef ATSC3_MODE +static gboolean +gst_qtdemux_configure_dvr_caps (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + GstStructure *s; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (gst_caps_get_size (stream->caps) == 1, FALSE); + + if (!qtdemux->configure_dvr) { + GST_ERROR_OBJECT (qtdemux, "dvr uuid have not been detected yet"); + return FALSE; + } + + s = gst_caps_get_structure (stream->caps, 0); + if (!gst_structure_has_name (s, "application/x-dvr")) { + gst_structure_set (s, + "original-media-type", G_TYPE_STRING, gst_structure_get_name (s), NULL); + gst_structure_set_name (s, "application/x-dvr"); + } + return TRUE; +} +#endif + +static gboolean +gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + if (!qtdemux->fragmented && stream->duration < stream->stts_total_duration) { + check_update_duration (qtdemux, QTSTREAMTIME_TO_GSTTIME (stream, + stream->stts_total_duration)); + } + + if (stream->subtype == FOURCC_vide) { + /* fps is calculated base on the duration of the average framerate since + * qt does not have a fixed framerate. */ + gboolean fps_available = TRUE; + + if ((stream->n_samples == 1) && (stream->first_duration == 0)) { + /* still frame */ + stream->fps_n = 0; + stream->fps_d = 1; + } else { + guint64 duration; + guint32 n_samples; + + /* duration and n_samples can be updated for fragmented format + * so, framerate of fragmented format is calculated using data in a moof */ + if (qtdemux->fragmented && stream->n_samples_moof > 0 + && stream->duration_moof > 0) { + n_samples = stream->n_samples_moof; + duration = stream->duration_moof; + } else { + n_samples = stream->n_samples; + duration = stream->duration; + } + + if (duration == 0 || n_samples < 2) { + stream->fps_n = stream->timescale; + stream->fps_d = 1; + fps_available = FALSE; + } else { + GstClockTime avg_duration; + + /* Calculate a framerate, ignoring the first sample which is sometimes truncated */ + /* stream->duration is guint64, timescale, n_samples are guint32 */ + avg_duration = + gst_util_uint64_scale_round (duration - + stream->first_duration, GST_SECOND, + (guint64) (stream->timescale) * (n_samples - 1)); + + GST_LOG_OBJECT (qtdemux, + "Calculating avg sample duration based on stream (or moof) duration %" + G_GUINT64_FORMAT + " minus first sample %u, leaving %d samples gives %" + GST_TIME_FORMAT, duration, stream->first_duration, + n_samples - 1, GST_TIME_ARGS (avg_duration)); + + gst_video_guess_framerate (avg_duration, &stream->fps_n, + &stream->fps_d); + + GST_DEBUG_OBJECT (qtdemux, + "Calculating framerate, timescale %u gave fps_n %d fps_d %d", + stream->timescale, stream->fps_n, stream->fps_d); + } + } + + if (stream->caps) { + stream->caps = gst_caps_make_writable (stream->caps); + + gst_caps_set_simple (stream->caps, + "width", G_TYPE_INT, stream->width, + "height", G_TYPE_INT, stream->height, NULL); + + /* if we have valid framerate from previous moof, set using it */ + if (!fps_available && qtdemux->fragmented && stream->pad) { + GstCaps *prev_caps = gst_pad_get_current_caps (stream->pad); + if (prev_caps) { + GstStructure *s = gst_caps_get_structure (prev_caps, 0); + if (s && gst_structure_has_field (s, "framerate")) { + gst_structure_get_fraction (s, "framerate", &stream->fps_n, + &stream->fps_d); + fps_available = TRUE; + } + gst_caps_unref (prev_caps); + } + } + + /* set framerate if calculated framerate is reliable */ + if (fps_available) { + gst_caps_set_simple (stream->caps, + "framerate", GST_TYPE_FRACTION, stream->fps_n, stream->fps_d, NULL); + } + + /* calculate pixel-aspect-ratio using display width and height */ + GST_DEBUG_OBJECT (qtdemux, + "video size %dx%d, target display size %dx%d", stream->width, + stream->height, stream->display_width, stream->display_height); + /* qt file might have pasp atom */ + if (stream->par_w > 0 && stream->par_h > 0) { + GST_DEBUG_OBJECT (qtdemux, "par %d:%d", stream->par_w, stream->par_h); + gst_caps_set_simple (stream->caps, "pixel-aspect-ratio", + GST_TYPE_FRACTION, stream->par_w, stream->par_h, NULL); + } else if (stream->display_width > 0 && stream->display_height > 0 && + stream->width > 0 && stream->height > 0) { + gint n, d; + + /* calculate the pixel aspect ratio using the display and pixel w/h */ + n = stream->display_width * stream->height; + d = stream->display_height * stream->width; + if (n == d) + n = d = 1; + GST_DEBUG_OBJECT (qtdemux, "setting PAR to %d/%d", n, d); + stream->par_w = n; + stream->par_h = d; + gst_caps_set_simple (stream->caps, "pixel-aspect-ratio", + GST_TYPE_FRACTION, stream->par_w, stream->par_h, NULL); + } + + if (stream->multiview_mode != GST_VIDEO_MULTIVIEW_MODE_NONE) { + guint par_w = 1, par_h = 1; + + if (stream->par_w > 0 && stream->par_h > 0) { + par_w = stream->par_w; + par_h = stream->par_h; + } + + if (gst_video_multiview_guess_half_aspect (stream->multiview_mode, + stream->width, stream->height, par_w, par_h)) { + stream->multiview_flags |= GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT; + } + + gst_caps_set_simple (stream->caps, + "multiview-mode", G_TYPE_STRING, + gst_video_multiview_mode_to_caps_string (stream->multiview_mode), + "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, + stream->multiview_flags, GST_FLAG_SET_MASK_EXACT, NULL); + } +#ifdef DOLBYHDR_SUPPORT + /* Prepare Dolby HDR related src caps */ + if (qtdemux->is_dolby_hdr) + if (!qtdemux_prepare_dolby_track (qtdemux, stream)) + GST_DEBUG_OBJECT (qtdemux, + "Fail to define dolby HDR stream configure"); +#endif +#ifdef MPEGDASH_MODE + if (qtdemux->use_svp) { + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, + "secure_area", G_TYPE_STRING, "svp", + "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + } +#endif + } + } + + else if (stream->subtype == FOURCC_soun) { + if (stream->caps) { + stream->caps = gst_caps_make_writable (stream->caps); + if (stream->rate > 0) + gst_caps_set_simple (stream->caps, + "rate", G_TYPE_INT, (int) stream->rate, NULL); + if (stream->n_channels > 0) + gst_caps_set_simple (stream->caps, + "channels", G_TYPE_INT, stream->n_channels, NULL); + if (stream->n_channels > 2) { + /* FIXME: Need to parse the 'chan' atom to get channel layouts + * correctly; this is just the minimum we can do - assume + * we don't actually have any channel positions. */ + gst_caps_set_simple (stream->caps, + "channel-mask", GST_TYPE_BITMASK, G_GUINT64_CONSTANT (0), NULL); + } + } + } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text + || stream->subtype == FOURCC_sbtl) { + if (stream->caps) { + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, + "track-num", G_TYPE_INT, (int) qtdemux->n_sub_streams, NULL); + } + } + + if (stream->pad) { + GstCaps *prev_caps = NULL; + + GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream; + gst_pad_set_event_function (stream->pad, gst_qtdemux_handle_src_event); + gst_pad_set_query_function (stream->pad, gst_qtdemux_handle_src_query); + gst_pad_set_active (stream->pad, TRUE); + + gst_pad_use_fixed_caps (stream->pad); + + if (stream->protected) { + if (!gst_qtdemux_configure_protected_caps (qtdemux, stream)) { + /* Notify DRM Rights Error when failed to configure protection system. + * error-state: 0(No license) + * content-id: unknown + * drm-system-id: unknown + * rights-issuer-url: unknown */ + GstStructure *s = gst_structure_new ("drm-rights-error", "error-state", + G_TYPE_UINT, 0, "content-id", G_TYPE_STRING, "unknown", + "drm-system-id", G_TYPE_STRING, "unknown", "rights-issuer-url", + G_TYPE_STRING, "unknown", NULL); + + GST_DEBUG_OBJECT (qtdemux, + "Posting drm-rights-error msg to application: %" GST_PTR_FORMAT, s); + + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), + gst_message_new_element (GST_OBJECT_CAST (qtdemux), s)); + + GST_ERROR_OBJECT (qtdemux, + "Failed to configure protected stream caps."); + return FALSE; + } + } else if (qtdemux->configure_dvr) { + if (!gst_qtdemux_configure_dvr_caps (qtdemux, stream)) { + GST_ERROR_OBJECT (qtdemux, "Failed to configure dvr stream caps."); + return FALSE; + } + } + + if (stream->new_stream || qtdemux->new_collection) { + gchar *stream_id; + GstEvent *event; + GstStreamFlags stream_flags; + + event = + gst_pad_get_sticky_event (qtdemux->sinkpad, GST_EVENT_STREAM_START, + 0); + if (event) { + if (gst_event_parse_group_id (event, &qtdemux->group_id)) + qtdemux->have_group_id = TRUE; + else + qtdemux->have_group_id = FALSE; + gst_event_unref (event); + } else if (!qtdemux->have_group_id) { + qtdemux->have_group_id = TRUE; + qtdemux->group_id = gst_util_group_id_next (); + } + + stream->new_stream = FALSE; + if (qtdemux->collection) { + stream_id = + g_strdup_printf ("%s/%03u", + gst_stream_collection_get_upstream_id (qtdemux->collection), + stream->track_id); + } else { + stream_id = + gst_pad_create_stream_id_printf (stream->pad, + GST_ELEMENT_CAST (qtdemux), "%03u", stream->track_id); + } + + event = gst_event_new_stream_start (stream_id); + if (qtdemux->have_group_id) + gst_event_set_group_id (event, qtdemux->group_id); + stream_flags = GST_STREAM_FLAG_NONE; + if (stream->disabled) + stream_flags |= GST_STREAM_FLAG_UNSELECT; + if (stream->sparse) + stream_flags |= GST_STREAM_FLAG_SPARSE; + gst_event_set_stream_flags (event, stream_flags); + + /* FIXME: in case dolby-vision dual track, stream-collection may cause + * problem. To prevent regression, let's block this. + * Please let me fix when stream-collection for dolby-vision dual track is verified */ + if (qtdemux->collection && !qtdemux->is_dolby_hdr) { + GstStream *collection_stream; + GstStreamType stream_type; + + /* TODO: in case of dolby vision, let parsebin do stream-collection + * What happen if CENC case ?? */ + switch (stream->subtype) { + case FOURCC_vide: + stream_type = GST_STREAM_TYPE_VIDEO; + break; + case FOURCC_soun: + stream_type = GST_STREAM_TYPE_AUDIO; + break; + case FOURCC_subp: + case FOURCC_text: + case FOURCC_sbtl: + case FOURCC_subt: + stream_type = GST_STREAM_TYPE_TEXT; + break; + default: + { + gchar *padname = gst_pad_get_name (stream->pad); + if (g_str_has_prefix (padname, "video")) + stream_type = GST_STREAM_TYPE_VIDEO; + else + stream_type = GST_STREAM_TYPE_UNKNOWN; + g_free (padname); + } + break; + } + + collection_stream = + gst_stream_new (stream_id, stream->caps, stream_type, stream_flags); + + /* FIXME: please remove this protected condition if it is verified */ + if (!stream->protected) + gst_event_set_stream (event, collection_stream); + + gst_stream_set_caps (collection_stream, stream->caps); + if (stream->pending_tags) + gst_stream_set_tags (collection_stream, stream->pending_tags); + + gst_stream_collection_add_stream (qtdemux->collection, + collection_stream); + } + + gst_pad_push_event (stream->pad, event); + g_free (stream_id); + } + + if (qtdemux->collection && stream->caps) { + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, "upstream-id", G_TYPE_STRING, + gst_stream_collection_get_upstream_id (qtdemux->collection), NULL); + } + + if (qtdemux->highest_temporal_id != -1) { + stream->caps = gst_caps_make_writable (stream->caps); + gst_caps_set_simple (stream->caps, "highest-temporal-id", G_TYPE_INT, + qtdemux->highest_temporal_id, NULL); + qtdemux->highest_temporal_id = -1; + } + + prev_caps = gst_pad_get_current_caps (stream->pad); + + if (!prev_caps || !gst_caps_is_equal_fixed (prev_caps, stream->caps) + || qtdemux->new_collection) { + GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, stream->caps); + gst_pad_set_caps (stream->pad, stream->caps); + } else { + GST_DEBUG_OBJECT (qtdemux, "ignore duplicated caps"); + } + + if (prev_caps) + gst_caps_unref (prev_caps); + stream->new_caps = FALSE; + } + return TRUE; +} + +static void +gst_qtdemux_stream_check_and_change_stsd_index (GstQTDemux * demux, + QtDemuxStream * stream) +{ + if (stream->stsd_entries_length < 2 || + stream->cur_stsd_entry_index == stream->stsd_sample_description_id) + return; + + GST_DEBUG_OBJECT (stream->pad, "Changing stsd index from '%u' to '%u'", + stream->cur_stsd_entry_index, stream->stsd_sample_description_id); + stream->cur_stsd_entry_index = stream->stsd_sample_description_id; + stream->new_caps = TRUE; + + qtdemux_sample_description_copy_into_stream (demux, + &stream->stsd_entries[stream->cur_stsd_entry_index], stream); +} + +static gboolean +gst_qtdemux_add_stream (GstQTDemux * qtdemux, + QtDemuxStream * stream, GstTagList * list) +{ + gboolean ret = TRUE; + /* consistent default for push based mode */ + gst_segment_init (&stream->segment, GST_FORMAT_TIME); + + if (stream->pending_tags) + gst_tag_list_unref (stream->pending_tags); + stream->pending_tags = list; + list = NULL; + + if (stream->subtype == FOURCC_vide) { + gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams); + + stream->pad = + gst_pad_new_from_static_template (&gst_qtdemux_videosrc_template, name); + g_free (name); + + if (!gst_qtdemux_configure_stream (qtdemux, stream)) { + gst_object_unref (stream->pad); + stream->pad = NULL; + ret = FALSE; + goto done; + } + qtdemux->n_video_streams++; + } else if (stream->subtype == FOURCC_soun) { + gchar *name = g_strdup_printf ("audio_%u", qtdemux->n_audio_streams); + + stream->pad = + gst_pad_new_from_static_template (&gst_qtdemux_audiosrc_template, name); + g_free (name); + if (!gst_qtdemux_configure_stream (qtdemux, stream)) { + gst_object_unref (stream->pad); + stream->pad = NULL; + ret = FALSE; + goto done; + } + qtdemux->n_audio_streams++; + } else if (stream->subtype == FOURCC_strm) { + GST_DEBUG_OBJECT (qtdemux, "stream type, not creating pad"); + } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text + || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) { + gchar *name = g_strdup_printf ("subtitle_%u", qtdemux->n_sub_streams); + + stream->pad = + gst_pad_new_from_static_template (&gst_qtdemux_subsrc_template, name); + g_free (name); + if (!gst_qtdemux_configure_stream (qtdemux, stream)) { + gst_object_unref (stream->pad); + stream->pad = NULL; + ret = FALSE; + goto done; + } + qtdemux->n_sub_streams++; + } else if (stream->caps) { + gchar *name = g_strdup_printf ("video_%u", qtdemux->n_video_streams); + + stream->pad = + gst_pad_new_from_static_template (&gst_qtdemux_videosrc_template, name); + g_free (name); + if (!gst_qtdemux_configure_stream (qtdemux, stream)) { + gst_object_unref (stream->pad); + stream->pad = NULL; + ret = FALSE; + goto done; + } + qtdemux->n_video_streams++; + } else { + GST_DEBUG_OBJECT (qtdemux, "unknown stream type"); + goto done; + } + + if (stream->pad) { + GList *l; + + GST_DEBUG_OBJECT (qtdemux, "adding pad %s %p to qtdemux %p", + GST_OBJECT_NAME (stream->pad), stream->pad, qtdemux); + gst_element_add_pad (GST_ELEMENT_CAST (qtdemux), stream->pad); + gst_flow_combiner_add_pad (qtdemux->flowcombiner, stream->pad); + + /* global tags go on each pad anyway */ + stream->send_global_tags = TRUE; + /* send upstream GST_EVENT_PROTECTION events that were received before + this source pad was created */ + for (l = qtdemux->protection_event_queue.head; l != NULL; l = l->next) + gst_pad_push_event (stream->pad, gst_event_ref (l->data)); + } +done: + if (list) + gst_tag_list_unref (list); + return ret; +} + +/* find next atom with @fourcc starting at @offset */ +static GstFlowReturn +qtdemux_find_atom (GstQTDemux * qtdemux, guint64 * offset, + guint64 * length, guint32 fourcc) +{ + GstFlowReturn ret; + guint32 lfourcc; + GstBuffer *buf; + + GST_LOG_OBJECT (qtdemux, "finding fourcc %" GST_FOURCC_FORMAT " at offset %" + G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), *offset); + + while (TRUE) { + GstMapInfo map; + + buf = NULL; + ret = gst_pad_pull_range (qtdemux->sinkpad, *offset, 16, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto locate_failed; + if (G_UNLIKELY (gst_buffer_get_size (buf) != 16)) { + /* likely EOF */ + ret = GST_FLOW_EOS; + gst_buffer_unref (buf); + goto locate_failed; + } + gst_buffer_map (buf, &map, GST_MAP_READ); + extract_initial_length_and_fourcc (map.data, 16, length, &lfourcc); + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + + if (G_UNLIKELY (*length == 0)) { + GST_DEBUG_OBJECT (qtdemux, "invalid length 0"); + ret = GST_FLOW_ERROR; + goto locate_failed; + } + + if (lfourcc == fourcc) { + GST_DEBUG_OBJECT (qtdemux, "found fourcc at offset %" G_GUINT64_FORMAT, + *offset); + break; + } else { + GST_LOG_OBJECT (qtdemux, + "skipping atom '%" GST_FOURCC_FORMAT "' at %" G_GUINT64_FORMAT, + GST_FOURCC_ARGS (fourcc), *offset); + *offset += *length; + } + } + + return GST_FLOW_OK; + +locate_failed: + { + /* might simply have had last one */ + GST_DEBUG_OBJECT (qtdemux, "fourcc not found"); + return ret; + } +} + +/* should only do something in pull mode */ +/* call with OBJECT lock */ +static GstFlowReturn +qtdemux_add_fragmented_samples (GstQTDemux * qtdemux) +{ + guint64 length, offset; + GstBuffer *buf = NULL; + GstFlowReturn ret = GST_FLOW_OK; + GstFlowReturn res = GST_FLOW_OK; + GstMapInfo map; + + offset = qtdemux->moof_offset; + GST_DEBUG_OBJECT (qtdemux, "next moof at offset %" G_GUINT64_FORMAT, offset); + + if (!offset) { + GST_DEBUG_OBJECT (qtdemux, "no next moof"); + return GST_FLOW_EOS; + } + + /* best not do pull etc with lock held */ + GST_OBJECT_UNLOCK (qtdemux); + + ret = qtdemux_find_atom (qtdemux, &offset, &length, FOURCC_moof); + if (ret != GST_FLOW_OK) + goto flow_failed; + + ret = gst_qtdemux_pull_atom (qtdemux, offset, length, &buf); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto flow_failed; + gst_buffer_map (buf, &map, GST_MAP_READ); + if (!qtdemux_parse_moof (qtdemux, map.data, map.size, offset, NULL)) { + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + buf = NULL; + goto parse_failed; + } + + gst_buffer_unmap (buf, &map); + gst_buffer_unref (buf); + buf = NULL; + + offset += length; + /* look for next moof */ + ret = qtdemux_find_atom (qtdemux, &offset, &length, FOURCC_moof); + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto flow_failed; + +exit: + GST_OBJECT_LOCK (qtdemux); + + qtdemux->moof_offset = offset; + + return res; + +parse_failed: + { + GST_DEBUG_OBJECT (qtdemux, "failed to parse moof"); + offset = 0; + res = GST_FLOW_ERROR; + goto exit; + } +flow_failed: + { + /* maybe upstream temporarily flushing */ + if (ret != GST_FLOW_FLUSHING) { + GST_DEBUG_OBJECT (qtdemux, "no next moof"); + offset = 0; + } else { + GST_DEBUG_OBJECT (qtdemux, "upstream WRONG_STATE"); + /* resume at current position next time */ + } + res = ret; + goto exit; + } +} + +/* initialise bytereaders for stbl sub-atoms */ +static gboolean +qtdemux_stbl_init (GstQTDemux * qtdemux, QtDemuxStream * stream, GNode * stbl) +{ + stream->stbl_index = -1; /* no samples have yet been parsed */ + stream->sample_index = -1; + + /* time-to-sample atom */ + if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stts, &stream->stts)) + goto corrupt_file; + + /* copy atom data into a new buffer for later use */ + stream->stts.data = g_memdup (stream->stts.data, stream->stts.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stts, 1 + 3) || + !gst_byte_reader_get_uint32_be (&stream->stts, &stream->n_sample_times)) + goto corrupt_file; + GST_LOG_OBJECT (qtdemux, "%u timestamp blocks", stream->n_sample_times); + + /* make sure there's enough data */ + if (!qt_atom_parser_has_chunks (&stream->stts, stream->n_sample_times, 8)) { + stream->n_sample_times = gst_byte_reader_get_remaining (&stream->stts) / 8; + GST_LOG_OBJECT (qtdemux, "overriding to %u timestamp blocks", + stream->n_sample_times); + if (!stream->n_sample_times) + goto corrupt_file; + } + + /* Some corrupted files have incorrect stream duration info in mdhd box + * So, we need to ensure actual stream duration by using stts + */ + { + gint i; + guint pos = gst_byte_reader_get_pos (&stream->stts); + guint32 samples; + guint32 duration; + stream->stts_total_duration = 0; + for (i = 0; i < stream->n_sample_times; i++) { + samples = gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + duration = gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + + stream->stts_total_duration += (guint64) (samples * duration); + } + gst_byte_reader_set_pos (&stream->stts, pos); + } + + /* sync sample atom */ + stream->stps_present = FALSE; + if ((stream->stss_present = + !!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stss, + &stream->stss) ? TRUE : FALSE) == TRUE) { + /* copy atom data into a new buffer for later use */ + stream->stss.data = g_memdup (stream->stss.data, stream->stss.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stss, 1 + 3) || + !gst_byte_reader_get_uint32_be (&stream->stss, &stream->n_sample_syncs)) + goto corrupt_file; + + if (stream->n_sample_syncs) { + /* make sure there's enough data */ + if (!qt_atom_parser_has_chunks (&stream->stss, stream->n_sample_syncs, 4)) + goto corrupt_file; + } + + /* partial sync sample atom */ + if ((stream->stps_present = + !!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stps, + &stream->stps) ? TRUE : FALSE) == TRUE) { + /* copy atom data into a new buffer for later use */ + stream->stps.data = g_memdup (stream->stps.data, stream->stps.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stps, 1 + 3) || + !gst_byte_reader_get_uint32_be (&stream->stps, + &stream->n_sample_partial_syncs)) + goto corrupt_file; + + /* if there are no entries, the stss table contains the real + * sync samples */ + if (stream->n_sample_partial_syncs) { + /* make sure there's enough data */ + if (!qt_atom_parser_has_chunks (&stream->stps, + stream->n_sample_partial_syncs, 4)) + goto corrupt_file; + } + } + } + + /* sample size */ + if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stsz, &stream->stsz)) + goto no_samples; + + /* copy atom data into a new buffer for later use */ + stream->stsz.data = g_memdup (stream->stsz.data, stream->stsz.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stsz, 1 + 3) || + !gst_byte_reader_get_uint32_be (&stream->stsz, &stream->sample_size)) + goto corrupt_file; + + if (!gst_byte_reader_get_uint32_be (&stream->stsz, &stream->n_samples)) + goto corrupt_file; + + if (!stream->n_samples) + goto no_samples; + + /* sample-to-chunk atom */ + if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stsc, &stream->stsc)) + goto corrupt_file; + + /* copy atom data into a new buffer for later use */ + stream->stsc.data = g_memdup (stream->stsc.data, stream->stsc.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stsc, 1 + 3) || + !gst_byte_reader_get_uint32_be (&stream->stsc, + &stream->n_samples_per_chunk)) + goto corrupt_file; + + GST_DEBUG_OBJECT (qtdemux, "n_samples_per_chunk %u", + stream->n_samples_per_chunk); + + /* make sure there's enough data */ + if (!qt_atom_parser_has_chunks (&stream->stsc, stream->n_samples_per_chunk, + 12)) + goto corrupt_file; + + + /* chunk offset */ + if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stco, &stream->stco)) + stream->co_size = sizeof (guint32); + else if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_co64, + &stream->stco)) + stream->co_size = sizeof (guint64); + else + goto corrupt_file; + + /* copy atom data into a new buffer for later use */ + stream->stco.data = g_memdup (stream->stco.data, stream->stco.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->stco, 1 + 3)) + goto corrupt_file; + + /* chunks_are_samples == TRUE means treat chunks as samples */ + stream->chunks_are_samples = stream->sample_size && !stream->sampled; + if (stream->chunks_are_samples) { + /* treat chunks as samples */ + if (!gst_byte_reader_get_uint32_be (&stream->stco, &stream->n_samples)) + goto corrupt_file; + } else { + /* skip number of entries */ + if (!gst_byte_reader_skip (&stream->stco, 4)) + goto corrupt_file; + + /* make sure there are enough data in the stsz atom */ + if (!stream->sample_size) { + /* different sizes for each sample */ + if (!qt_atom_parser_has_chunks (&stream->stsz, stream->n_samples, 4)) + goto corrupt_file; + } + } + + GST_DEBUG_OBJECT (qtdemux, "allocating n_samples %u * %u (%.2f MB)", + stream->n_samples, (guint) sizeof (QtDemuxSample), + stream->n_samples * sizeof (QtDemuxSample) / (1024.0 * 1024.0)); + + if (stream->n_samples >= + QTDEMUX_MAX_SAMPLE_INDEX_SIZE / sizeof (QtDemuxSample)) { + GST_WARNING_OBJECT (qtdemux, "not allocating index of %d samples, would " + "be larger than %uMB (broken file?)", stream->n_samples, + QTDEMUX_MAX_SAMPLE_INDEX_SIZE >> 20); + return FALSE; + } + + g_assert (stream->samples == NULL); + stream->samples = g_try_new0 (QtDemuxSample, stream->n_samples); + if (!stream->samples) { + GST_WARNING_OBJECT (qtdemux, "failed to allocate %d samples", + stream->n_samples); + return FALSE; + } + + /* composition time-to-sample */ + if ((stream->ctts_present = + !!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_ctts, + &stream->ctts) ? TRUE : FALSE) == TRUE) { + GstByteReader cslg = GST_BYTE_READER_INIT (NULL, 0); + + /* copy atom data into a new buffer for later use */ + stream->ctts.data = g_memdup (stream->ctts.data, stream->ctts.size); + + /* skip version + flags */ + if (!gst_byte_reader_skip (&stream->ctts, 1 + 3) + || !gst_byte_reader_get_uint32_be (&stream->ctts, + &stream->n_composition_times)) + goto corrupt_file; + + /* make sure there's enough data */ + if (!qt_atom_parser_has_chunks (&stream->ctts, stream->n_composition_times, + 4 + 4)) + goto corrupt_file; + + /* This is optional, if missing we iterate the ctts */ + if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_cslg, &cslg)) { + if (!gst_byte_reader_skip (&cslg, 1 + 3) + || !gst_byte_reader_get_uint32_be (&cslg, &stream->cslg_shift)) { + g_free ((gpointer) cslg.data); + goto corrupt_file; + } + } else { + gint32 cslg_least = 0; + guint num_entries, pos; + gint i; + + pos = gst_byte_reader_get_pos (&stream->ctts); + num_entries = stream->n_composition_times; + + stream->cslg_shift = 0; + + for (i = 0; i < num_entries; i++) { + gint32 offset; + + gst_byte_reader_skip_unchecked (&stream->ctts, 4); + offset = gst_byte_reader_get_int32_be_unchecked (&stream->ctts); + + if (offset < cslg_least) + cslg_least = offset; + } + + if (cslg_least < 0) + stream->cslg_shift = ABS (cslg_least); + else + stream->cslg_shift = 0; + + /* reset the reader so we can generate sample table */ + gst_byte_reader_set_pos (&stream->ctts, pos); + } + } else { + /* Ensure the cslg_shift value is consistent so we can use it + * unconditionnally to produce TS and Segment */ + stream->cslg_shift = 0; + } + + return TRUE; + +corrupt_file: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + return FALSE; + } +no_samples: + { + gst_qtdemux_stbl_free (stream); + if (!qtdemux->fragmented) { + /* not quite good */ + GST_WARNING_OBJECT (qtdemux, "stream has no samples"); + return FALSE; + } else { + /* may pick up samples elsewhere */ + return TRUE; + } + } +} + +/* collect samples from the next sample to be parsed up to sample @n for @stream + * by reading the info from @stbl + * + * This code can be executed from both the streaming thread and the seeking + * thread so it takes the object lock to protect itself + */ +static gboolean +qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) +{ + gint i, j, k; + QtDemuxSample *samples, *first, *cur, *last; + guint32 n_samples_per_chunk; + guint32 n_samples; + + GST_LOG_OBJECT (qtdemux, "parsing samples for stream fourcc %" + GST_FOURCC_FORMAT ", pad %s", GST_FOURCC_ARGS (stream->fourcc), + stream->pad ? GST_PAD_NAME (stream->pad) : "(NULL)"); + + n_samples = stream->n_samples; + + if (n >= n_samples) + goto out_of_samples; + + GST_OBJECT_LOCK (qtdemux); + if (n <= stream->stbl_index) + goto already_parsed; + + GST_DEBUG_OBJECT (qtdemux, "parsing up to sample %u", n); + + if (!stream->stsz.data) { + /* so we already parsed and passed all the moov samples; + * onto fragmented ones */ + g_assert (qtdemux->fragmented); + goto done; + } + + /* pointer to the sample table */ + samples = stream->samples; + + /* starts from -1, moves to the next sample index to parse */ + stream->stbl_index++; + + /* keep track of the first and last sample to fill */ + first = &samples[stream->stbl_index]; + last = &samples[n]; + + if (!stream->chunks_are_samples) { + /* set the sample sizes */ + if (stream->sample_size == 0) { + /* different sizes for each sample */ + for (cur = first; cur <= last; cur++) { + cur->size = gst_byte_reader_get_uint32_be_unchecked (&stream->stsz); + GST_LOG_OBJECT (qtdemux, "sample %d has size %u", + (guint) (cur - samples), cur->size); + } + } else { + /* samples have the same size */ + GST_LOG_OBJECT (qtdemux, "all samples have size %u", stream->sample_size); + for (cur = first; cur <= last; cur++) + cur->size = stream->sample_size; + } + } + + n_samples_per_chunk = stream->n_samples_per_chunk; + cur = first; + + for (i = stream->stsc_index; i < n_samples_per_chunk; i++) { + guint32 last_chunk; + + if (stream->stsc_chunk_index >= stream->last_chunk + || stream->stsc_chunk_index < stream->first_chunk) { + stream->first_chunk = + gst_byte_reader_get_uint32_be_unchecked (&stream->stsc); + stream->samples_per_chunk = + gst_byte_reader_get_uint32_be_unchecked (&stream->stsc); + /* starts from 1 */ + stream->stsd_sample_description_id = + gst_byte_reader_get_uint32_be_unchecked (&stream->stsc) - 1; + + /* chunk numbers are counted from 1 it seems */ + if (G_UNLIKELY (stream->first_chunk == 0)) + goto corrupt_file; + + --stream->first_chunk; + + /* the last chunk of each entry is calculated by taking the first chunk + * of the next entry; except if there is no next, where we fake it with + * INT_MAX */ + if (G_UNLIKELY (i == (stream->n_samples_per_chunk - 1))) { + stream->last_chunk = G_MAXUINT32; + } else { + stream->last_chunk = + gst_byte_reader_peek_uint32_be_unchecked (&stream->stsc); + if (G_UNLIKELY (stream->last_chunk == 0)) + goto corrupt_file; + + --stream->last_chunk; + } + + GST_LOG_OBJECT (qtdemux, + "entry %d has first_chunk %d, last_chunk %d, samples_per_chunk %d" + "sample desc ID: %d", i, stream->first_chunk, stream->last_chunk, + stream->samples_per_chunk, stream->stsd_sample_description_id); + + if (G_UNLIKELY (stream->last_chunk < stream->first_chunk)) + goto corrupt_file; + + if (stream->last_chunk != G_MAXUINT32) { + if (!qt_atom_parser_peek_sub (&stream->stco, + stream->first_chunk * stream->co_size, + (stream->last_chunk - stream->first_chunk) * stream->co_size, + &stream->co_chunk)) + goto corrupt_file; + + } else { + stream->co_chunk = stream->stco; + if (!gst_byte_reader_skip (&stream->co_chunk, + stream->first_chunk * stream->co_size)) + goto corrupt_file; + } + + stream->stsc_chunk_index = stream->first_chunk; + } + + last_chunk = stream->last_chunk; + + if (stream->chunks_are_samples) { + cur = &samples[stream->stsc_chunk_index]; + + for (j = stream->stsc_chunk_index; j < last_chunk; j++) { + if (j > n) { + /* save state */ + stream->stsc_chunk_index = j; + goto done; + } + + cur->offset = + qt_atom_parser_get_offset_unchecked (&stream->co_chunk, + stream->co_size); + + GST_LOG_OBJECT (qtdemux, "Created entry %d with offset " + "%" G_GUINT64_FORMAT, j, cur->offset); + + if (stream->samples_per_frame > 0 && stream->bytes_per_frame > 0) { + cur->size = + (stream->samples_per_chunk * stream->n_channels) / + stream->samples_per_frame * stream->bytes_per_frame; + } else { + cur->size = stream->samples_per_chunk; + } + + GST_DEBUG_OBJECT (qtdemux, + "keyframe sample %d: timestamp %" GST_TIME_FORMAT ", size %u", + j, GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, + stream->stco_sample_index)), cur->size); + + cur->timestamp = stream->stco_sample_index; + cur->duration = stream->samples_per_chunk; + cur->keyframe = TRUE; + cur++; + + stream->stco_sample_index += stream->samples_per_chunk; + } + stream->stsc_chunk_index = j; + } else { + for (j = stream->stsc_chunk_index; j < last_chunk; j++) { + guint32 samples_per_chunk; + guint64 chunk_offset; + + if (!stream->stsc_sample_index + && !qt_atom_parser_get_offset (&stream->co_chunk, stream->co_size, + &stream->chunk_offset)) + goto corrupt_file; + + samples_per_chunk = stream->samples_per_chunk; + chunk_offset = stream->chunk_offset; + + for (k = stream->stsc_sample_index; k < samples_per_chunk; k++) { + GST_LOG_OBJECT (qtdemux, "creating entry %d with offset %" + G_GUINT64_FORMAT " and size %d", + (guint) (cur - samples), chunk_offset, cur->size); + + cur->offset = chunk_offset; + chunk_offset += cur->size; + cur++; + + if (G_UNLIKELY (cur > last)) { + /* save state */ + stream->stsc_sample_index = k + 1; + stream->chunk_offset = chunk_offset; + stream->stsc_chunk_index = j; + goto done2; + } + } + stream->stsc_sample_index = 0; + } + stream->stsc_chunk_index = j; + } + stream->stsc_index++; + } + + if (stream->chunks_are_samples) + goto ctts; +done2: + { + guint32 n_sample_times; + + n_sample_times = stream->n_sample_times; + cur = first; + + for (i = stream->stts_index; i < n_sample_times; i++) { + guint32 stts_samples; + gint32 stts_duration; + gint64 stts_time; + + if (stream->stts_sample_index >= stream->stts_samples + || !stream->stts_sample_index) { + + stream->stts_samples = + gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + stream->stts_duration = + gst_byte_reader_get_uint32_be_unchecked (&stream->stts); + + GST_LOG_OBJECT (qtdemux, "block %d, %u timestamps, duration %u", + i, stream->stts_samples, stream->stts_duration); + + stream->stts_sample_index = 0; + } + + stts_samples = stream->stts_samples; + stts_duration = stream->stts_duration; + stts_time = stream->stts_time; + + for (j = stream->stts_sample_index; j < stts_samples; j++) { + GST_DEBUG_OBJECT (qtdemux, + "sample %d: index %d, timestamp %" GST_TIME_FORMAT, + (guint) (cur - samples), j, + GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, stts_time))); + + cur->timestamp = stts_time; + cur->duration = stts_duration; + + /* avoid 32-bit wrap-around, + * but still mind possible 'negative' duration */ + stts_time += (gint64) stts_duration; + cur++; + + if (G_UNLIKELY (cur > last)) { + /* save values */ + stream->stts_time = stts_time; + stream->stts_sample_index = j + 1; + goto done3; + } + } + stream->stts_sample_index = 0; + stream->stts_time = stts_time; + stream->stts_index++; + } + /* fill up empty timestamps with the last timestamp, this can happen when + * the last samples do not decode and so we don't have timestamps for them. + * We however look at the last timestamp to estimate the track length so we + * need something in here. */ + for (; cur < last; cur++) { + GST_DEBUG_OBJECT (qtdemux, + "fill sample %d: timestamp %" GST_TIME_FORMAT, + (guint) (cur - samples), + GST_TIME_ARGS (QTSTREAMTIME_TO_GSTTIME (stream, stream->stts_time))); + cur->timestamp = stream->stts_time; + cur->duration = -1; + } + } +done3: + { + /* sample sync, can be NULL */ + if (stream->stss_present == TRUE) { + guint32 n_sample_syncs; + + n_sample_syncs = stream->n_sample_syncs; + + if (!n_sample_syncs || (n_sample_syncs == n_samples)) { + GST_DEBUG_OBJECT (qtdemux, "all samples are keyframes"); + stream->all_keyframe = TRUE; + } else { + for (i = stream->stss_index; i < n_sample_syncs; i++) { + /* note that the first sample is index 1, not 0 */ + guint32 index; + + index = gst_byte_reader_get_uint32_be_unchecked (&stream->stss); + + if (G_LIKELY (index > 0 && index <= n_samples)) { + index -= 1; + samples[index].keyframe = TRUE; + GST_DEBUG_OBJECT (qtdemux, "samples at %u is keyframe", index); + /* and exit if we have enough samples */ + if (G_UNLIKELY (index >= n)) { + i++; + break; + } + } + } + /* save state */ + stream->stss_index = i; + } + + /* stps marks partial sync frames like open GOP I-Frames */ + if (stream->stps_present == TRUE) { + guint32 n_sample_partial_syncs; + + n_sample_partial_syncs = stream->n_sample_partial_syncs; + + /* if there are no entries, the stss table contains the real + * sync samples */ + if (n_sample_partial_syncs) { + for (i = stream->stps_index; i < n_sample_partial_syncs; i++) { + /* note that the first sample is index 1, not 0 */ + guint32 index; + + index = gst_byte_reader_get_uint32_be_unchecked (&stream->stps); + + if (G_LIKELY (index > 0 && index <= n_samples)) { + index -= 1; + samples[index].keyframe = TRUE; + GST_DEBUG_OBJECT (qtdemux, "samples at %u is keyframe", index); + /* and exit if we have enough samples */ + if (G_UNLIKELY (index >= n)) { + i++; + break; + } + } + } + /* save state */ + stream->stps_index = i; + } + } + } else { + /* no stss, all samples are keyframes */ + stream->all_keyframe = TRUE; + stream->n_sample_syncs = stream->n_samples; + GST_DEBUG_OBJECT (qtdemux, "setting all keyframes"); + } + } + +ctts: + /* composition time to sample */ + if (stream->ctts_present == TRUE) { + guint32 n_composition_times; + guint32 ctts_count; + gint32 ctts_soffset; + + /* Fill in the pts_offsets */ + cur = first; + n_composition_times = stream->n_composition_times; + + for (i = stream->ctts_index; i < n_composition_times; i++) { + if (stream->ctts_sample_index >= stream->ctts_count + || !stream->ctts_sample_index) { + stream->ctts_count = + gst_byte_reader_get_uint32_be_unchecked (&stream->ctts); + stream->ctts_soffset = + gst_byte_reader_get_int32_be_unchecked (&stream->ctts); + stream->ctts_sample_index = 0; + } + + ctts_count = stream->ctts_count; + ctts_soffset = stream->ctts_soffset; + + for (j = stream->ctts_sample_index; j < ctts_count; j++) { + cur->pts_offset = ctts_soffset; + cur++; + + if (G_UNLIKELY (cur > last)) { + /* save state */ + stream->ctts_sample_index = j + 1; + goto done; + } + } + stream->ctts_sample_index = 0; + stream->ctts_index++; + } + } +done: + stream->stbl_index = n; + /* if index has been completely parsed, free data that is no-longer needed */ + if (n + 1 == stream->n_samples) { + gst_qtdemux_stbl_free (stream); + GST_DEBUG_OBJECT (qtdemux, "parsed all available samples;"); + if (qtdemux->pullbased) { + GST_DEBUG_OBJECT (qtdemux, "checking for more samples"); + while (n + 1 == stream->n_samples) + if (qtdemux_add_fragmented_samples (qtdemux) != GST_FLOW_OK) + break; + } + } + GST_OBJECT_UNLOCK (qtdemux); + + return TRUE; + + /* SUCCESS */ +already_parsed: + { + GST_LOG_OBJECT (qtdemux, + "Tried to parse up to sample %u but this sample has already been parsed", + n); + /* if fragmented, there may be more */ + if (qtdemux->fragmented && n == stream->stbl_index) + goto done; + GST_OBJECT_UNLOCK (qtdemux); + return TRUE; + } + /* ERRORS */ +out_of_samples: + { + GST_LOG_OBJECT (qtdemux, + "Tried to parse up to sample %u but there are only %u samples", n + 1, + stream->n_samples); + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + return FALSE; + } +corrupt_file: + { + GST_OBJECT_UNLOCK (qtdemux); + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + return FALSE; + } +} + +/* collect all segment info for @stream. + */ +static gboolean +qtdemux_parse_segments (GstQTDemux * qtdemux, QtDemuxStream * stream, + GNode * trak) +{ + GNode *edts; + /* accept edts if they contain gaps at start and there is only + * one media segment */ + gboolean allow_pushbased_edts = TRUE; + gint media_segments_count = 0; + + /* parse and prepare segment info from the edit list */ + GST_DEBUG_OBJECT (qtdemux, "looking for edit list container"); + stream->n_segments = 0; + stream->segments = NULL; + if ((edts = qtdemux_tree_get_child_by_type (trak, FOURCC_edts))) { + GNode *elst; + gint n_segments; + gint i, count, entry_size; + guint64 time; + GstClockTime stime; + guint8 *buffer; + guint8 version; + + GST_DEBUG_OBJECT (qtdemux, "looking for edit list"); + if (!(elst = qtdemux_tree_get_child_by_type (edts, FOURCC_elst))) + goto done; + + buffer = elst->data; + + version = QT_UINT8 (buffer + 8); + entry_size = (version == 1) ? 20 : 12; + + n_segments = QT_UINT32 (buffer + 12); + + /* we might allocate a bit too much, at least allocate 1 segment */ + stream->segments = g_new (QtDemuxSegment, MAX (n_segments, 1)); + + /* segments always start from 0 */ + time = 0; + stime = 0; + count = 0; + for (i = 0; i < n_segments; i++) { + guint64 duration; + guint64 media_time; + gboolean time_valid = TRUE; + QtDemuxSegment *segment; + guint32 rate_int; + GstClockTime media_start = GST_CLOCK_TIME_NONE; + + if (version == 1) { + media_time = QT_UINT64 (buffer + 24 + i * entry_size); + duration = QT_UINT64 (buffer + 16 + i * entry_size); + if (media_time == G_MAXUINT64) + time_valid = FALSE; + } else { + media_time = QT_UINT32 (buffer + 20 + i * entry_size); + duration = QT_UINT32 (buffer + 16 + i * entry_size); + if (media_time == G_MAXUINT32) + time_valid = FALSE; + } + + if (time_valid) + media_start = QTSTREAMTIME_TO_GSTTIME (stream, media_time); + + segment = &stream->segments[count++]; + + /* time and duration expressed in global timescale */ + segment->time = stime; + /* add non scaled values so we don't cause roundoff errors */ + if (duration || media_start == GST_CLOCK_TIME_NONE) { + time += duration; + stime = QTTIME_TO_GSTTIME (qtdemux, time); + segment->duration = stime - segment->time; + } else { + /* zero duration does not imply media_start == media_stop + * but, only specify media_start.*/ + stime = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration); + if (GST_CLOCK_TIME_IS_VALID (stime) && time_valid + && stime >= media_start) { + segment->duration = stime - media_start; + } else { + segment->duration = GST_CLOCK_TIME_NONE; + } + } + segment->stop_time = stime; + + segment->trak_media_start = media_time; + /* media_time expressed in stream timescale */ + if (time_valid) { + segment->media_start = media_start; + segment->media_stop = segment->media_start + segment->duration; + media_segments_count++; + } else { + segment->media_start = GST_CLOCK_TIME_NONE; + segment->media_stop = GST_CLOCK_TIME_NONE; + } + rate_int = + QT_UINT32 (buffer + ((version == 1) ? 32 : 24) + i * entry_size); + + if (rate_int <= 1) { + /* 0 is not allowed, some programs write 1 instead of the floating point + * value */ + GST_WARNING_OBJECT (qtdemux, "found suspicious rate %" G_GUINT32_FORMAT, + rate_int); + segment->rate = 1; + } else { + /* some specific files have a problem with rate_int parsing if edts header exists. + * so we set the segment rate to 1 */ + segment->rate = 1; + } + + GST_DEBUG_OBJECT (qtdemux, "created segment %d time %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT ", media_start %" GST_TIME_FORMAT + " (%" G_GUINT64_FORMAT ") , media_stop %" GST_TIME_FORMAT + " stop_time %" GST_TIME_FORMAT " rate %g, (%d) timescale %u", + i, GST_TIME_ARGS (segment->time), + GST_TIME_ARGS (segment->duration), + GST_TIME_ARGS (segment->media_start), media_time, + GST_TIME_ARGS (segment->media_stop), + GST_TIME_ARGS (segment->stop_time), segment->rate, rate_int, + stream->timescale); + if (segment->stop_time > qtdemux->segment.stop) { + GST_WARNING_OBJECT (qtdemux, "Segment %d " + " extends to %" GST_TIME_FORMAT + " past the end of the file duration %" GST_TIME_FORMAT + " it will be truncated", i, GST_TIME_ARGS (segment->stop_time), + GST_TIME_ARGS (qtdemux->segment.stop)); + qtdemux->segment.stop = segment->stop_time; + } + } + GST_DEBUG_OBJECT (qtdemux, "found %d segments", count); + stream->n_segments = count; + if (media_segments_count != 1) + allow_pushbased_edts = FALSE; + } +done: + + /* push based does not handle segments, so act accordingly here, + * and warn if applicable */ + if (!qtdemux->pullbased && !allow_pushbased_edts) { + GST_WARNING_OBJECT (qtdemux, "streaming; discarding edit list segments"); + /* remove and use default one below, we stream like it anyway */ + g_free (stream->segments); + stream->segments = NULL; + stream->n_segments = 0; + } + + /* no segments, create one to play the complete trak */ + if (stream->n_segments == 0) { + GstClockTime stream_duration = + QTSTREAMTIME_TO_GSTTIME (stream, stream->duration); + + if (stream->segments == NULL) + stream->segments = g_new (QtDemuxSegment, 1); + + /* represent unknown our way */ + if (stream_duration == 0) + stream_duration = GST_CLOCK_TIME_NONE; + + stream->segments[0].time = 0; + stream->segments[0].stop_time = stream_duration; + stream->segments[0].duration = stream_duration; + stream->segments[0].media_start = 0; + stream->segments[0].media_stop = stream_duration; + stream->segments[0].rate = 1.0; + stream->segments[0].trak_media_start = 0; + + GST_DEBUG_OBJECT (qtdemux, "created dummy segment %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream_duration)); + stream->n_segments = 1; + stream->dummy_segment = TRUE; + } + GST_DEBUG_OBJECT (qtdemux, "using %d segments", stream->n_segments); + + return TRUE; +} + +/* + * Parses the stsd atom of a svq3 trak looking for + * the SMI and gama atoms. + */ +static void +qtdemux_parse_svq3_stsd_data (GstQTDemux * qtdemux, + const guint8 * stsd_entry_data, const guint8 ** gamma, GstBuffer ** seqh) +{ + const guint8 *_gamma = NULL; + GstBuffer *_seqh = NULL; + const guint8 *stsd_data = stsd_entry_data; + guint32 length = QT_UINT32 (stsd_data); + guint16 version; + + if (length < 32) { + GST_WARNING_OBJECT (qtdemux, "stsd too short"); + goto end; + } + + stsd_data += 16; + length -= 16; + version = QT_UINT16 (stsd_data); + if (version == 3) { + if (length >= 70) { + length -= 70; + stsd_data += 70; + while (length > 8) { + guint32 fourcc, size; + const guint8 *data; + size = QT_UINT32 (stsd_data); + fourcc = QT_FOURCC (stsd_data + 4); + data = stsd_data + 8; + + if (size == 0) { + GST_WARNING_OBJECT (qtdemux, "Atom of size 0 found, aborting " + "svq3 atom parsing"); + goto end; + } + + switch (fourcc) { + case FOURCC_gama:{ + if (size == 12) { + _gamma = data; + } else { + GST_WARNING_OBJECT (qtdemux, "Unexpected size %" G_GUINT32_FORMAT + " for gama atom, expected 12", size); + } + break; + } + case FOURCC_SMI_:{ + if (size > 16 && QT_FOURCC (data) == FOURCC_SEQH) { + guint32 seqh_size; + if (_seqh != NULL) { + GST_WARNING_OBJECT (qtdemux, "Unexpected second SEQH SMI atom " + " found, ignoring"); + } else { + seqh_size = QT_UINT32 (data + 4); + if (seqh_size > 0) { + _seqh = gst_buffer_new_and_alloc (seqh_size); + gst_buffer_fill (_seqh, 0, data + 8, seqh_size); + } + } + } + break; + } + default:{ + GST_WARNING_OBJECT (qtdemux, "Unhandled atom %" GST_FOURCC_FORMAT + " in SVQ3 entry in stsd atom", GST_FOURCC_ARGS (fourcc)); + } + } + + if (size <= length) { + length -= size; + stsd_data += size; + } + } + } else { + GST_WARNING_OBJECT (qtdemux, "SVQ3 entry too short in stsd atom"); + } + } else { + GST_WARNING_OBJECT (qtdemux, "Unexpected version for SVQ3 entry %" + G_GUINT16_FORMAT, version); + goto end; + } + +end: + if (gamma) { + *gamma = _gamma; + } + if (seqh) { + *seqh = _seqh; + } else if (_seqh) { + gst_buffer_unref (_seqh); + } +} + +static gchar * +qtdemux_get_rtsp_uri_from_hndl (GstQTDemux * qtdemux, GNode * minf) +{ + GNode *dinf; + GstByteReader dref; + gchar *uri = NULL; + + /* + * Get 'dinf', to get its child 'dref', that might contain a 'hndl' + * atom that might contain a 'data' atom with the rtsp uri. + * This case was reported in bug #597497, some info about + * the hndl atom can be found in TN1195 + */ + dinf = qtdemux_tree_get_child_by_type (minf, FOURCC_dinf); + GST_DEBUG_OBJECT (qtdemux, "Trying to obtain rtsp URI for stream trak"); + + if (dinf) { + guint32 dref_num_entries = 0; + if (qtdemux_tree_get_child_by_type_full (dinf, FOURCC_dref, &dref) && + gst_byte_reader_skip (&dref, 4) && + gst_byte_reader_get_uint32_be (&dref, &dref_num_entries)) { + gint i; + + /* search dref entries for hndl atom */ + for (i = 0; i < dref_num_entries; i++) { + guint32 size = 0, type; + guint8 string_len = 0; + if (gst_byte_reader_get_uint32_be (&dref, &size) && + qt_atom_parser_get_fourcc (&dref, &type)) { + if (type == FOURCC_hndl) { + GST_DEBUG_OBJECT (qtdemux, "Found hndl atom"); + + /* skip data reference handle bytes and the + * following pascal string and some extra 4 + * bytes I have no idea what are */ + if (!gst_byte_reader_skip (&dref, 4) || + !gst_byte_reader_get_uint8 (&dref, &string_len) || + !gst_byte_reader_skip (&dref, string_len + 4)) { + GST_WARNING_OBJECT (qtdemux, "Failed to parse hndl atom"); + break; + } + + /* iterate over the atoms to find the data atom */ + while (gst_byte_reader_get_remaining (&dref) >= 8) { + guint32 atom_size; + guint32 atom_type; + + if (gst_byte_reader_get_uint32_be (&dref, &atom_size) && + qt_atom_parser_get_fourcc (&dref, &atom_type)) { + if (atom_type == FOURCC_data) { + const guint8 *uri_aux = NULL; + + /* found the data atom that might contain the rtsp uri */ + GST_DEBUG_OBJECT (qtdemux, "Found data atom inside " + "hndl atom, interpreting it as an URI"); + if (gst_byte_reader_peek_data (&dref, atom_size - 8, + &uri_aux)) { + if (g_strstr_len ((gchar *) uri_aux, 7, "rtsp://") != NULL) + uri = g_strndup ((gchar *) uri_aux, atom_size - 8); + else + GST_WARNING_OBJECT (qtdemux, "Data atom in hndl atom " + "didn't contain a rtsp address"); + } else { + GST_WARNING_OBJECT (qtdemux, "Failed to get the data " + "atom contents"); + } + break; + } + /* skipping to the next entry */ + if (!gst_byte_reader_skip (&dref, atom_size - 8)) + break; + } else { + GST_WARNING_OBJECT (qtdemux, "Failed to parse hndl child " + "atom header"); + break; + } + } + break; + } + /* skip to the next entry */ + if (!gst_byte_reader_skip (&dref, size - 8)) + break; + } else { + GST_WARNING_OBJECT (qtdemux, "Error parsing dref atom"); + } + } + GST_DEBUG_OBJECT (qtdemux, "Finished parsing dref atom"); + } + } + return uri; +} + +#define AMR_NB_ALL_MODES 0x81ff +#define AMR_WB_ALL_MODES 0x83ff +static guint +qtdemux_parse_amr_bitrate (GstBuffer * buf, gboolean wb) +{ + /* The 'damr' atom is of the form: + * + * | vendor | decoder_ver | mode_set | mode_change_period | frames/sample | + * 32 b 8 b 16 b 8 b 8 b + * + * The highest set bit of the first 7 (AMR-NB) or 8 (AMR-WB) bits of mode_set + * represents the highest mode used in the stream (and thus the maximum + * bitrate), with a couple of special cases as seen below. + */ + + /* Map of frame type ID -> bitrate */ + static const guint nb_bitrates[] = { + 4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200 + }; + static const guint wb_bitrates[] = { + 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 + }; + GstMapInfo map; + gsize max_mode; + guint16 mode_set; + + gst_buffer_map (buf, &map, GST_MAP_READ); + + if (map.size != 0x11) { + GST_DEBUG ("Atom should have size 0x11, not %" G_GSIZE_FORMAT, map.size); + goto bad_data; + } + + if (QT_FOURCC (map.data + 4) != FOURCC_damr) { + GST_DEBUG ("Unknown atom in %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_UINT32 (map.data + 4))); + goto bad_data; + } + + mode_set = QT_UINT16 (map.data + 13); + + if (mode_set == (wb ? AMR_WB_ALL_MODES : AMR_NB_ALL_MODES)) + max_mode = 7 + (wb ? 1 : 0); + else + /* AMR-NB modes fo from 0-7, and AMR-WB modes go from 0-8 */ + max_mode = g_bit_nth_msf ((gulong) mode_set & (wb ? 0x1ff : 0xff), -1); + + if (max_mode == -1) { + GST_DEBUG ("No mode indication was found (mode set) = %x", + (guint) mode_set); + goto bad_data; + } + + gst_buffer_unmap (buf, &map); + return wb ? wb_bitrates[max_mode] : nb_bitrates[max_mode]; + +bad_data: + gst_buffer_unmap (buf, &map); + return 0; +} + +static gboolean +qtdemux_parse_transformation_matrix (GstQTDemux * qtdemux, + GstByteReader * reader, guint32 * matrix, const gchar * atom) +{ + /* + * 9 values of 32 bits (fixed point 16.16, except 2 5 and 8 that are 2.30) + * [0 1 2] + * [3 4 5] + * [6 7 8] + */ + + if (gst_byte_reader_get_remaining (reader) < 36) + return FALSE; + + matrix[0] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[1] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[2] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[3] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[4] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[5] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[6] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[7] = gst_byte_reader_get_uint32_be_unchecked (reader); + matrix[8] = gst_byte_reader_get_uint32_be_unchecked (reader); + + GST_DEBUG_OBJECT (qtdemux, "Transformation matrix from atom %s", atom); + GST_DEBUG_OBJECT (qtdemux, "%u.%u %u.%u %u.%u", matrix[0] >> 16, + matrix[0] & 0xFFFF, matrix[1] >> 16, matrix[1] & 0xFF, matrix[2] >> 16, + matrix[2] & 0xFF); + GST_DEBUG_OBJECT (qtdemux, "%u.%u %u.%u %u.%u", matrix[3] >> 16, + matrix[3] & 0xFFFF, matrix[4] >> 16, matrix[4] & 0xFF, matrix[5] >> 16, + matrix[5] & 0xFF); + GST_DEBUG_OBJECT (qtdemux, "%u.%u %u.%u %u.%u", matrix[6] >> 16, + matrix[6] & 0xFFFF, matrix[7] >> 16, matrix[7] & 0xFF, matrix[8] >> 16, + matrix[8] & 0xFF); + + return TRUE; +} + +static void +qtdemux_inspect_transformation_matrix (GstQTDemux * qtdemux, + QtDemuxStream * stream, guint32 * matrix, GstTagList ** taglist) +{ + +/* [a b c] + * [d e f] + * [g h i] + * + * This macro will only compare value abdegh, it expects cfi to have already + * been checked + */ +#define QTCHECK_MATRIX(m,a,b,d,e) ((m)[0] == (a << 16) && (m)[1] == (b << 16) && \ + (m)[3] == (d << 16) && (m)[4] == (e << 16)) + + /* only handle the cases where the last column has standard values */ + if (matrix[2] == 0 && matrix[5] == 0 && matrix[8] == 1 << 30) { + const gchar *rotation_tag = NULL; + + /* no rotation needed */ + if (QTCHECK_MATRIX (matrix, 1, 0, 0, 1)) { + /* NOP */ + } else if (QTCHECK_MATRIX (matrix, 0, 1, G_MAXUINT16, 0)) { + rotation_tag = "rotate-90"; + } else if (QTCHECK_MATRIX (matrix, G_MAXUINT16, 0, 0, G_MAXUINT16)) { + rotation_tag = "rotate-180"; + } else if (QTCHECK_MATRIX (matrix, 0, G_MAXUINT16, 1, 0)) { + rotation_tag = "rotate-270"; + } else { + GST_FIXME_OBJECT (qtdemux, "Unhandled transformation matrix values"); + } + + GST_DEBUG_OBJECT (qtdemux, "Transformation matrix rotation %s", + rotation_tag); + if (rotation_tag != NULL) { + if (*taglist == NULL) + *taglist = gst_tag_list_new_empty (); + gst_tag_list_add (*taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_IMAGE_ORIENTATION, rotation_tag, NULL); + } + } else { + GST_FIXME_OBJECT (qtdemux, "Unhandled transformation matrix values"); + } +} + +/* Parses the boxes defined in ISO/IEC 14496-12 that enable support for + * protected streams (sinf, frma, schm and schi); if the protection scheme is + * Common Encryption (cenc), the function will also parse the tenc box (defined + * in ISO/IEC 23001-7). @container points to the node that contains these boxes + * (typically an enc[v|a|t|s] sample entry); the function will set + * @original_fmt to the fourcc of the original unencrypted stream format. + * Returns TRUE if successful; FALSE otherwise. */ +static gboolean +qtdemux_parse_protection_scheme_info (GstQTDemux * qtdemux, + QtDemuxStream * stream, GNode * container, guint32 * original_fmt) +{ + GNode *sinf; + GNode *frma; + GNode *schm; + GNode *schi; + + g_return_val_if_fail (qtdemux != NULL, FALSE); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (container != NULL, FALSE); + g_return_val_if_fail (original_fmt != NULL, FALSE); + + sinf = qtdemux_tree_get_child_by_type (container, FOURCC_sinf); + if (G_UNLIKELY (!sinf)) { + if (stream->protection_scheme_type == FOURCC_cenc) { + GST_ERROR_OBJECT (qtdemux, "sinf box does not contain schi box, which is " + "mandatory for Common Encryption"); + return FALSE; + } + return TRUE; + } + + frma = qtdemux_tree_get_child_by_type (sinf, FOURCC_frma); + if (G_UNLIKELY (!frma)) { + GST_ERROR_OBJECT (qtdemux, "sinf box does not contain mandatory frma box"); + return FALSE; + } + + *original_fmt = QT_FOURCC ((const guint8 *) frma->data + 8); + GST_DEBUG_OBJECT (qtdemux, "original stream format: '%" GST_FOURCC_FORMAT "'", + GST_FOURCC_ARGS (*original_fmt)); + + schm = qtdemux_tree_get_child_by_type (sinf, FOURCC_schm); + if (!schm) { + GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schm box"); + return FALSE; + } + stream->protection_scheme_type = QT_FOURCC ((const guint8 *) schm->data + 12); + stream->protection_scheme_version = + QT_UINT32 ((const guint8 *) schm->data + 16); + + GST_DEBUG_OBJECT (qtdemux, + "protection_scheme_type: %" GST_FOURCC_FORMAT ", " + "protection_scheme_version: %#010x", + GST_FOURCC_ARGS (stream->protection_scheme_type), + stream->protection_scheme_version); + + schi = qtdemux_tree_get_child_by_type (sinf, FOURCC_schi); + if (!schi) { + GST_DEBUG_OBJECT (qtdemux, "sinf box does not contain schi box"); + return FALSE; + } + if (is_common_enc_scheme_type (stream->protection_scheme_type)) { + QtDemuxCencSampleSetInfo *info; + GNode *tenc; + const guint8 *tenc_data; + guint8 version = 0; + guint8 crypt_byte_block = 0; + guint8 skip_byte_block = 0; + guint32 isEncrypted; + guint8 iv_size; + const guint8 *default_kid; + GstBuffer *kid_buf; + guint8 constant_iv_size = 0; + const guint8 *default_constant_iv = NULL; + GstBuffer *constant_iv_buf = NULL; + + if (G_UNLIKELY (!stream->protection_scheme_info)) + stream->protection_scheme_info = + g_malloc0 (sizeof (QtDemuxCencSampleSetInfo)); + + info = (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; + + tenc = qtdemux_tree_get_child_by_type (schi, FOURCC_tenc); + if (!tenc) { + GST_ERROR_OBJECT (qtdemux, "schi box does not contain tenc box, " + "which is mandatory for Common Encryption"); + return FALSE; + } + tenc_data = (const guint8 *) tenc->data + 12; + + if (stream->protection_scheme_type == FOURCC_cens || + stream->protection_scheme_type == FOURCC_cbcs) { + version = 1; + crypt_byte_block = QT_UINT8 (tenc_data + 1) >> 4; + skip_byte_block = QT_UINT8 (tenc_data + 1) & 0x0F; + } + isEncrypted = QT_UINT8 (tenc_data + 2); + iv_size = QT_UINT8 (tenc_data + 3); + default_kid = (tenc_data + 4); + kid_buf = gst_buffer_new_allocate (NULL, 16, NULL); + gst_buffer_fill (kid_buf, 0, default_kid, 16); + + if (iv_size == 0) { + constant_iv_size = QT_UINT8 (tenc_data + 20); + default_constant_iv = (tenc_data + 21); + constant_iv_buf = gst_buffer_new_allocate (NULL, constant_iv_size, NULL); + gst_buffer_fill (constant_iv_buf, 0, default_constant_iv, + constant_iv_size); + } + + if (info->default_properties) + gst_structure_free (info->default_properties); + info->default_properties = + gst_structure_new ("application/x-cenc", + "scheme_type", G_TYPE_UINT, stream->protection_scheme_type, + "version", G_TYPE_UINT, version, + "crypt_byte_block", G_TYPE_UINT, crypt_byte_block, + "skip_byte_block", G_TYPE_UINT, skip_byte_block, + "iv_size", G_TYPE_UINT, iv_size, + "encrypted", G_TYPE_BOOLEAN, (isEncrypted == 1), + "kid", GST_TYPE_BUFFER, kid_buf, NULL); + + if (constant_iv_size != 0 && constant_iv_buf != NULL) { + gst_structure_set (info->default_properties, + "constant_iv_size", G_TYPE_UINT, constant_iv_size, + "constant_iv", GST_TYPE_BUFFER, constant_iv_buf, NULL); + gst_buffer_unref (constant_iv_buf); + } + + GST_DEBUG_OBJECT (qtdemux, "default sample properties: " + "is_encrypted=%u, iv_size=%u", isEncrypted, iv_size); + gst_buffer_unref (kid_buf); + } + return TRUE; +} + +static gboolean +qtdemux_sample_description_copy_into_stream (GstQTDemux * qtdemux, + QtDemuxStreamStsdEntry * stsd_entry, QtDemuxStream * stream) +{ + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stsd_entry != NULL, FALSE); + + /* free allocated memory on stsd_entry if exist */ + if (stream->caps) { + gst_caps_unref (stream->caps); + stream->caps = NULL; + } + if (stream->rgb8_palette) { + gst_memory_unref (stream->rgb8_palette); + stream->rgb8_palette = NULL; + } + + if (stsd_entry->caps) + stream->caps = gst_caps_copy (stsd_entry->caps); + + stream->fourcc = stsd_entry->fourcc; + + stream->sampled = stsd_entry->sampled; + stream->padding = stsd_entry->padding; + + stream->width = stsd_entry->width; + stream->height = stsd_entry->height; + + stream->display_width = stsd_entry->display_width; + stream->display_height = stsd_entry->display_height; + + stream->bits_per_sample = stsd_entry->bits_per_sample; + stream->color_table_id = stsd_entry->color_table_id; + if (stsd_entry->rgb8_palette) + stream->rgb8_palette = gst_memory_ref (stsd_entry->rgb8_palette); + + stream->rate = stsd_entry->rate; + stream->n_channels = stsd_entry->n_channels; + stream->samples_per_packet = stsd_entry->samples_per_packet; + stream->samples_per_frame = stsd_entry->samples_per_frame; + stream->bytes_per_packet = stsd_entry->bytes_per_packet; + stream->bytes_per_sample = stsd_entry->bytes_per_sample; + stream->bytes_per_frame = stsd_entry->bytes_per_frame; + stream->compression = stsd_entry->compression; + + return TRUE; +} + +static gboolean +qtdemux_stream_copy_into_sample_description (GstQTDemux * qtdemux, + QtDemuxStream * stream, gint stsd_index) +{ + QtDemuxStreamStsdEntry *stsd_entry; + + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stsd_index >= 0, FALSE); + g_return_val_if_fail (stsd_index < stream->stsd_entries_length, FALSE); + + stsd_entry = &stream->stsd_entries[stsd_index]; + + /* free allocated memory on stsd_entry if exist */ + if (stsd_entry->caps) { + gst_caps_unref (stsd_entry->caps); + stsd_entry->caps = NULL; + } + if (stsd_entry->rgb8_palette) { + gst_memory_unref (stsd_entry->rgb8_palette); + stsd_entry->rgb8_palette = NULL; + } + + if (stream->caps) + stsd_entry->caps = gst_caps_copy (stream->caps); + + stsd_entry->fourcc = stream->fourcc; + + stsd_entry->sampled = stream->sampled; + stsd_entry->padding = stream->padding; + + stsd_entry->width = stream->width; + stsd_entry->height = stream->height; + + stsd_entry->display_width = stream->display_width; + stsd_entry->display_height = stream->display_height; + + stsd_entry->bits_per_sample = stream->bits_per_sample; + stsd_entry->color_table_id = stream->color_table_id; + if (stream->rgb8_palette) + stsd_entry->rgb8_palette = gst_memory_ref (stream->rgb8_palette); + + stsd_entry->rate = stream->rate; + stsd_entry->n_channels = stream->n_channels; + stsd_entry->samples_per_packet = stream->samples_per_packet; + stsd_entry->samples_per_frame = stream->samples_per_frame; + stsd_entry->bytes_per_packet = stream->bytes_per_packet; + stsd_entry->bytes_per_sample = stream->bytes_per_sample; + stsd_entry->bytes_per_frame = stream->bytes_per_frame; + stsd_entry->compression = stream->compression; + + return TRUE; +} + +/* parse the traks. + * With each track we associate a new QtDemuxStream that contains all the info + * about the trak. + * traks that do not decode to something (like strm traks) will not have a pad. + */ +static gboolean +qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) +{ + GstByteReader tkhd; + int offset; + GNode *mdia; + GNode *mdhd; + GNode *hdlr; + GNode *minf; + GNode *stbl; + GNode *stsd; + GNode *mp4a; + GNode *mp4v; + GNode *wave; + GNode *esds; + GNode *pasp; + GNode *tref; + GNode *udta; + GNode *svmi; + + QtDemuxStream *stream = NULL; + gboolean new_stream = FALSE; + gchar *codec = NULL; + const guint8 *stsd_data; + const guint8 *stsd_entry_data; + guint remaining_stsd_len; + guint stsd_entry_count; + guint stsd_index = 0; + guint16 lang_code; /* quicktime lang code or packed iso code */ + guint32 version; + guint32 tkhd_flags = 0; + guint8 tkhd_version = 0; + guint32 w = 0, h = 0; + guint32 fourcc; + guint value_size, stsd_len, len; + guint32 track_id; + guint32 dummy; + + GST_DEBUG_OBJECT (qtdemux, "parse_trak"); + + if (!qtdemux_tree_get_child_by_type_full (trak, FOURCC_tkhd, &tkhd) + || !gst_byte_reader_get_uint8 (&tkhd, &tkhd_version) + || !gst_byte_reader_get_uint24_be (&tkhd, &tkhd_flags)) + goto corrupt_file; + + /* pick between 64 or 32 bits */ + value_size = tkhd_version == 1 ? 8 : 4; + if (!gst_byte_reader_skip (&tkhd, value_size * 2) || + !gst_byte_reader_get_uint32_be (&tkhd, &track_id)) + goto corrupt_file; + + if (!qtdemux->got_moov) { + if (qtdemux_find_stream (qtdemux, track_id)) + goto existing_stream; + stream = _create_stream (); + stream->track_id = track_id; + new_stream = TRUE; + } else { + stream = qtdemux_find_stream (qtdemux, track_id); + if (!stream) { + /* Update changed track_id due to switched represent in DASH. + * This assumes that DASH segment has only one track. */ + /* FIXME: We may be able to handle this using DSC in the future */ + if (qtdemux->upstream_format_is_time && qtdemux->n_streams == 1 && + qtdemux->streams[0]) { + stream = qtdemux->streams[0]; + + if (!(mdia = qtdemux_tree_get_child_by_type (trak, FOURCC_mdia))) + goto corrupt_file; + if (!(hdlr = qtdemux_tree_get_child_by_type (mdia, FOURCC_hdlr))) + goto corrupt_file; + + if (QT_FOURCC ((guint8 *) hdlr->data + 16) == stream->subtype) { + GST_DEBUG_OBJECT (qtdemux, "Update track_id from stream %u to %" + G_GUINT32_FORMAT, stream->track_id, track_id); + + stream->track_id = track_id; + } else { + GST_WARNING_OBJECT (qtdemux, "Stream not found, going to ignore it"); + goto skip_track; + } + } else { + GST_WARNING_OBJECT (qtdemux, "Stream not found, going to ignore it"); + goto skip_track; + } + } + + if (qtdemux->upstream_format_is_time && qtdemux->fragmented) { + /* unset parsed_trex flag in order to apply new trex box's data */ + stream->parsed_trex = FALSE; + } + + /* flush samples data from this track from previous moov */ + gst_qtdemux_stream_flush_segments_data (qtdemux, stream); + gst_qtdemux_stream_flush_samples_data (qtdemux, stream); + gst_qtdemux_stream_flush_sample_descriptions (qtdemux, stream); + } + /* need defaults for fragments */ + qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy); + + if (stream->pending_tags == NULL) + stream->pending_tags = gst_tag_list_new_empty (); + + if ((tkhd_flags & 1) == 0) + stream->disabled = TRUE; + + GST_LOG_OBJECT (qtdemux, "track[tkhd] version/flags/id: 0x%02x/%06x/%u", + tkhd_version, tkhd_flags, stream->track_id); + + if (!(mdia = qtdemux_tree_get_child_by_type (trak, FOURCC_mdia))) + goto corrupt_file; + + if (!(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mdhd))) { + /* be nice for some crooked mjp2 files that use mhdr for mdhd */ + if (qtdemux->major_brand != FOURCC_mjp2 || + !(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mhdr))) + goto corrupt_file; + } + + len = QT_UINT32 ((guint8 *) mdhd->data); + version = QT_UINT32 ((guint8 *) mdhd->data + 8); + GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version); + if (version == 0x01000000) { + if (len < 38) + goto corrupt_file; + stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28); + stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32); + lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36); + } else { + if (len < 30) + goto corrupt_file; + stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 20); + stream->duration = QT_UINT32 ((guint8 *) mdhd->data + 24); + lang_code = QT_UINT16 ((guint8 *) mdhd->data + 28); + } + + if (lang_code < 0x400) { + qtdemux_lang_map_qt_code_to_iso (stream->lang_id, lang_code); + } else if (lang_code == 0x7fff) { + stream->lang_id[0] = 0; /* unspecified */ + } else { + stream->lang_id[0] = 0x60 + ((lang_code >> 10) & 0x1F); + stream->lang_id[1] = 0x60 + ((lang_code >> 5) & 0x1F); + stream->lang_id[2] = 0x60 + (lang_code & 0x1F); + stream->lang_id[3] = 0; + } + + GST_LOG_OBJECT (qtdemux, "track timescale: %" G_GUINT32_FORMAT, + stream->timescale); + GST_LOG_OBJECT (qtdemux, "track duration: %" G_GUINT64_FORMAT, + stream->duration); + GST_LOG_OBJECT (qtdemux, "track language code/id: 0x%04x/%s", + lang_code, stream->lang_id); + + if (G_UNLIKELY (stream->timescale == 0 || qtdemux->timescale == 0)) + goto corrupt_file; + + if ((tref = qtdemux_tree_get_child_by_type (trak, FOURCC_tref))) { + /* chapters track reference */ + GNode *chap = qtdemux_tree_get_child_by_type (tref, FOURCC_chap); + if (chap) { + gsize length = GST_READ_UINT32_BE (chap->data); + if (qtdemux->chapters_track_id) + GST_FIXME_OBJECT (qtdemux, "Multiple CHAP tracks"); + + if (length >= 12) { + qtdemux->chapters_track_id = + GST_READ_UINT32_BE ((gint8 *) chap->data + 8); + } + } + } + + /* fragmented files may have bogus duration in moov */ + if (!qtdemux->fragmented && + qtdemux->duration != G_MAXINT64 && stream->duration != G_MAXINT32) { + guint64 tdur1, tdur2; + + /* don't overflow */ + tdur1 = stream->timescale * (guint64) qtdemux->duration; + tdur2 = qtdemux->timescale * (guint64) stream->duration; + + /* HACK: + * some of those trailers, nowadays, have prologue images that are + * themselves vide tracks as well. I haven't really found a way to + * identify those yet, except for just looking at their duration. */ + if (tdur1 != 0 && (tdur2 * 10 / tdur1) < 2) { + GST_WARNING_OBJECT (qtdemux, + "Track shorter than 20%% (%" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT + " vs. %" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT ") of the stream " + "found, assuming preview image or something; skipping track", + stream->duration, stream->timescale, qtdemux->duration, + qtdemux->timescale); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return TRUE; + } + } + + if (!(hdlr = qtdemux_tree_get_child_by_type (mdia, FOURCC_hdlr))) + goto corrupt_file; + + GST_LOG_OBJECT (qtdemux, "track type: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_FOURCC ((guint8 *) hdlr->data + 12))); + + len = QT_UINT32 ((guint8 *) hdlr->data); + if (len >= 20) + stream->subtype = QT_FOURCC ((guint8 *) hdlr->data + 16); + GST_LOG_OBJECT (qtdemux, "track subtype: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->subtype)); + + if (qtdemux->thumbnail_mode && (stream->subtype == FOURCC_soun)) { + GST_DEBUG_OBJECT (qtdemux, + "This is the Thumbnail-mode and don't have to parse audio track."); + g_free (stream); + return TRUE; + } + + if (!(minf = qtdemux_tree_get_child_by_type (mdia, FOURCC_minf))) + goto corrupt_file; + + if (!(stbl = qtdemux_tree_get_child_by_type (minf, FOURCC_stbl))) + goto corrupt_file; + + /*parse svmi header if existing */ + svmi = qtdemux_tree_get_child_by_type (stbl, FOURCC_svmi); + if (svmi) { + len = QT_UINT32 ((guint8 *) svmi->data); + version = QT_UINT32 ((guint8 *) svmi->data + 8); + if (!version) { + GstVideoMultiviewMode mode = GST_VIDEO_MULTIVIEW_MODE_NONE; + GstVideoMultiviewFlags flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE; + guint8 frame_type, frame_layout; + + /* MPEG-A stereo video */ + if (qtdemux->major_brand == FOURCC_ss02) + flags |= GST_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO; + + frame_type = QT_UINT8 ((guint8 *) svmi->data + 12); + frame_layout = QT_UINT8 ((guint8 *) svmi->data + 13) & 0x01; + switch (frame_type) { + case 0: + mode = GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE; + break; + case 1: + mode = GST_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED; + break; + case 2: + mode = GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME; + break; + case 3: + /* mode 3 is primary/secondary view sequence, ie + * left/right views in separate tracks. See section 7.2 + * of ISO/IEC 23000-11:2009 */ + GST_FIXME_OBJECT (qtdemux, + "Implement stereo video in separate streams"); + } + + if ((frame_layout & 0x1) == 0) + flags |= GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST; + + GST_LOG_OBJECT (qtdemux, + "StereoVideo: composition type: %u, is_left_first: %u", + frame_type, frame_layout); + stream->multiview_mode = mode; + stream->multiview_flags = flags; + } + } + + /* parse rest of tkhd */ + if (stream->subtype == FOURCC_vide) { + guint32 matrix[9]; + + /* version 1 uses some 64-bit ints */ + if (!gst_byte_reader_skip (&tkhd, 20 + value_size)) + goto corrupt_file; + + if (!qtdemux_parse_transformation_matrix (qtdemux, &tkhd, matrix, "tkhd")) + goto corrupt_file; + + if (!gst_byte_reader_get_uint32_be (&tkhd, &w) + || !gst_byte_reader_get_uint32_be (&tkhd, &h)) + goto corrupt_file; + + qtdemux_inspect_transformation_matrix (qtdemux, stream, matrix, + &stream->pending_tags); + } + + /* parse stsd */ + if (!(stsd = qtdemux_tree_get_child_by_type (stbl, FOURCC_stsd))) + goto corrupt_file; + stsd_data = (const guint8 *) stsd->data; + + /* stsd should at least have one entry */ + stsd_len = QT_UINT32 (stsd_data); + if (stsd_len < 24) { + /* .. but skip stream with empty stsd produced by some Vivotek cameras */ + if (stream->subtype == FOURCC_vivo) { + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return TRUE; + } else { + goto corrupt_file; + } + } + + stream->stsd_entries_length = stsd_entry_count = QT_UINT32 (stsd_data + 12); + stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, stsd_entry_count); + GST_LOG_OBJECT (qtdemux, "stsd len: %d", stsd_len); + GST_LOG_OBJECT (qtdemux, "stsd entry count: %u", stsd_entry_count); + stsd_entry_data = stsd_data + 16; + remaining_stsd_len = stsd_len - 16; + +/* FIXME: it may be better to use loop, but it may cause conflict with upstream + * code due to indent difference, so let's use goto syntax until this approach + * will be accepted in upstream */ +stsd_entry_loop: + GST_LOG_OBJECT (qtdemux, "stsd entry loop %d", stsd_index); + + /* and that entry should fit within stsd */ + len = QT_UINT32 (stsd_entry_data); + if (len > remaining_stsd_len) + goto corrupt_file; + + stream->fourcc = fourcc = QT_FOURCC (stsd_entry_data + 4); + GST_LOG_OBJECT (qtdemux, "stsd type: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->fourcc)); + GST_LOG_OBJECT (qtdemux, "stsd type len: %d", len); + + if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi)) + goto error_encrypted; + + if (fourcc == FOURCC_encv || fourcc == FOURCC_enca) { + /* FIXME this looks wrong, there might be multiple children + * with the same type */ + GNode *enc = qtdemux_tree_get_child_by_type (stsd, fourcc); + + /* FIXME: This must be work around in my opinion... + * Upstream should notify stream change with new stream-start event */ + if (qtdemux->upstream_format_is_time && !new_stream && !stream->protected) { + GST_DEBUG_OBJECT (qtdemux, "stream changed from clear to encrypted"); + qtdemux->new_collection = TRUE; + } + stream->protected = TRUE; + if (!qtdemux_parse_protection_scheme_info (qtdemux, stream, enc, &fourcc)) + GST_ERROR_OBJECT (qtdemux, "Failed to parse protection scheme info"); + } else if (qtdemux->upstream_format_is_time && !new_stream + && stream->protected) { + /* FIXME: This must be work around in my opinion... + * Upstream should notify stream change with new stream-start event */ + + GST_DEBUG_OBJECT (qtdemux, "stream changed from encrypted to clear"); + qtdemux->new_collection = TRUE; + stream->protected = FALSE; + } + + if (stream->subtype == FOURCC_vide) { + gboolean gray; + gint depth, palette_size, palette_count; + guint32 *palette_data = NULL; + + stream->sampled = TRUE; + + stream->display_width = w >> 16; + stream->display_height = h >> 16; + + offset = 16; + if (len < 86) /* TODO verify */ + goto corrupt_file; + + stream->width = QT_UINT16 (stsd_entry_data + offset + 16); + stream->height = QT_UINT16 (stsd_entry_data + offset + 18); + stream->fps_n = 0; /* this is filled in later */ + stream->fps_d = 0; /* this is filled in later */ + stream->bits_per_sample = QT_UINT16 (stsd_entry_data + offset + 66); + stream->color_table_id = QT_UINT16 (stsd_entry_data + offset + 68); + + /* if color_table_id is 0, ctab atom must follow; however some files + * produced by TMPEGEnc have color_table_id = 0 and no ctab atom, so + * if color table is not present we'll correct the value */ + if (stream->color_table_id == 0 && + (len < 90 + || QT_FOURCC (stsd_entry_data + offset + 70) != FOURCC_ctab)) { + stream->color_table_id = -1; + } + + GST_LOG_OBJECT (qtdemux, "width %d, height %d, bps %d, color table id %d", + stream->width, stream->height, stream->bits_per_sample, + stream->color_table_id); + + if (ceil (((double) stream->width / (double) 16)) * + ceil ((double) stream->height / (double) 16) >= 8704) { + qtdemux->isBigData = TRUE; + GST_ERROR_OBJECT (qtdemux, "ceil(width/16) %4f, ceil(height/16) %4f", + ceil ((double) (stream->width / 16)), + ceil ((double) (stream->height / 16))); + } + + depth = stream->bits_per_sample; + + /* more than 32 bits means grayscale */ + gray = (depth > 32); + /* low 32 bits specify the depth */ + depth &= 0x1F; + + /* different number of palette entries is determined by depth. */ + palette_count = 0; + if ((depth == 1) || (depth == 2) || (depth == 4) || (depth == 8)) + palette_count = (1 << depth); + palette_size = palette_count * 4; + + if (stream->color_table_id) { + switch (palette_count) { + case 0: + break; + case 2: + palette_data = g_memdup (ff_qt_default_palette_2, palette_size); + break; + case 4: + palette_data = g_memdup (ff_qt_default_palette_4, palette_size); + break; + case 16: + if (gray) + palette_data = g_memdup (ff_qt_grayscale_palette_16, palette_size); + else + palette_data = g_memdup (ff_qt_default_palette_16, palette_size); + break; + case 256: + if (gray) + palette_data = g_memdup (ff_qt_grayscale_palette_256, palette_size); + else + palette_data = g_memdup (ff_qt_default_palette_256, palette_size); + break; + default: + GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX, + (_("The video in this file might not play correctly.")), + ("unsupported palette depth %d", depth)); + break; + } + } else { + gint i, j, start, end; + + if (len < 94) + goto corrupt_file; + + /* read table */ + start = QT_UINT32 (stsd_entry_data + offset + 70); + palette_count = QT_UINT16 (stsd_entry_data + offset + 74); + end = QT_UINT16 (stsd_entry_data + offset + 76); + + GST_LOG_OBJECT (qtdemux, "start %d, end %d, palette_count %d", + start, end, palette_count); + + if (end > 255) + end = 255; + if (start > end) + start = end; + + if (len < 94 + (end - start) * 8) + goto corrupt_file; + + /* palette is always the same size */ + palette_data = g_malloc0 (256 * 4); + palette_size = 256 * 4; + + for (j = 0, i = start; i <= end; j++, i++) { + guint32 a, r, g, b; + + a = QT_UINT16 (stsd_entry_data + offset + 78 + (j * 8)); + r = QT_UINT16 (stsd_entry_data + offset + 80 + (j * 8)); + g = QT_UINT16 (stsd_entry_data + offset + 82 + (j * 8)); + b = QT_UINT16 (stsd_entry_data + offset + 84 + (j * 8)); + + palette_data[i] = ((a & 0xff00) << 16) | ((r & 0xff00) << 8) | + (g & 0xff00) | (b >> 8); + } + } + + if (stream->caps) + gst_caps_unref (stream->caps); + + stream->caps = + qtdemux_video_caps (qtdemux, stream, fourcc, stsd_entry_data, &codec); + if (G_UNLIKELY (!stream->caps)) { + g_free (palette_data); + goto unknown_stream; + } + + if (codec) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_VIDEO_CODEC, codec, NULL); + g_free (codec); + codec = NULL; + } + + + if (palette_data) { + GstStructure *s; + + if (stream->rgb8_palette) + gst_memory_unref (stream->rgb8_palette); + stream->rgb8_palette = gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY, + palette_data, palette_size, 0, palette_size, palette_data, g_free); + + s = gst_caps_get_structure (stream->caps, 0); + + /* non-raw video has a palette_data property. raw video has the palette as + * an extra plane that we append to the output buffers before we push + * them*/ + if (!gst_structure_has_name (s, "video/x-raw")) { + GstBuffer *palette; + + palette = gst_buffer_new (); + gst_buffer_append_memory (palette, stream->rgb8_palette); + stream->rgb8_palette = NULL; + + gst_caps_set_simple (stream->caps, "palette_data", + GST_TYPE_BUFFER, palette, NULL); + gst_buffer_unref (palette); + } + } else if (palette_count != 0) { + GST_ELEMENT_WARNING (qtdemux, STREAM, NOT_IMPLEMENTED, + (NULL), ("Unsupported palette depth %d", depth)); + } + + GST_LOG_OBJECT (qtdemux, "frame count: %u", + QT_UINT16 (stsd_entry_data + offset + 32)); + + esds = NULL; + pasp = NULL; + /* pick 'the' stsd child */ + mp4v = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!stream->protected) { + if (mp4v == NULL || QTDEMUX_TREE_NODE_FOURCC (mp4v) != fourcc) { + mp4v = NULL; + } + } else { + if (mp4v == NULL || QTDEMUX_TREE_NODE_FOURCC (mp4v) != FOURCC_encv) { + mp4v = NULL; + } + } + + if (mp4v) { + esds = qtdemux_tree_get_child_by_type (mp4v, FOURCC_esds); + pasp = qtdemux_tree_get_child_by_type (mp4v, FOURCC_pasp); + } + + if (pasp) { + const guint8 *pasp_data = (const guint8 *) pasp->data; + + stream->par_w = QT_UINT32 (pasp_data + 8); + stream->par_h = QT_UINT32 (pasp_data + 12); + } else { + stream->par_w = 0; + stream->par_h = 0; + } + + if (esds) { + gst_qtdemux_handle_esds (qtdemux, stream, esds, stream->pending_tags); + } else { + switch (fourcc) { + case FOURCC_H264: + case FOURCC_avc1: + case FOURCC_avc3: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvav: +#endif + { + gint len = QT_UINT32 (stsd_entry_data) - 0x56; + const guint8 *avc_data = stsd_entry_data + 0x56; + +#ifdef DOLBYHDR_SUPPORT + /* if following conditions are met, let's skip this track + * - Dolby Vision cannot be supported by this platform + * - Dolby Vision with dual-track + * - This track is for EL + */ + if (!qtdemux->dolby_vision_support && qtdemux->has_dolby_bl_cand + && fourcc == FOURCC_dvav && !qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore non SDR dolby-vision track"); + qtdemux->has_dolby_el_cand = FALSE; + goto skip_track; + } +#endif + + /* find avcC */ + while (len >= 0x8) { + gint size; + + if (QT_UINT32 (avc_data) <= len) + size = QT_UINT32 (avc_data) - 0x8; + else + size = len - 0x8; + + if (size < 1) + /* No real data, so break out */ + break; + + switch (QT_FOURCC (avc_data + 0x4)) { + case FOURCC_avcC: + { + /* parse, if found */ + GstBuffer *buf; + const guint8 *codec_data_avcC; + + codec_data_avcC = avc_data + 0x8; + codec_data_avcC += 4; + + switch (*codec_data_avcC & 0x03) { + case 0: + stream->length_size_avcC = 1; + break; + case 1: + stream->length_size_avcC = 2; + break; + case 3: + stream->length_size_avcC = 4; + break; + default: + GST_WARNING_OBJECT (qtdemux, "wrong length_size = %d", + *codec_data_avcC & 0x03); + break; + } + + GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd"); + + /* First 4 bytes are the length of the atom, the next 4 bytes + * are the fourcc, the next 1 byte is the version, and the + * subsequent bytes are profile_tier_level structure like data. */ + gst_codec_utils_h264_caps_set_level_and_profile (stream->caps, + avc_data + 8 + 1, size - 1); + buf = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buf, 0, avc_data + 0x8, size); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + + break; + } + case FOURCC_strf: + { + GstBuffer *buf; + + GST_DEBUG_OBJECT (qtdemux, "found strf codec_data in stsd"); + + /* First 4 bytes are the length of the atom, the next 4 bytes + * are the fourcc, next 40 bytes are BITMAPINFOHEADER, + * next 1 byte is the version, and the + * subsequent bytes are sequence parameter set like data. */ + + size -= 40; /* we'll be skipping BITMAPINFOHEADER */ + if (size > 1) { + gst_codec_utils_h264_caps_set_level_and_profile (stream->caps, + avc_data + 8 + 40 + 1, size - 1); + + buf = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buf, 0, avc_data + 8 + 40, size); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + break; + } + case FOURCC_btrt: + { + guint avg_bitrate, max_bitrate; + + /* bufferSizeDB, maxBitrate and avgBitrate - 4 bytes each */ + if (size < 12) + break; + + max_bitrate = QT_UINT32 (avc_data + 0xc); + avg_bitrate = QT_UINT32 (avc_data + 0x10); + + if (!max_bitrate && !avg_bitrate) + break; + + /* Some muxers seem to swap the average and maximum bitrates + * (I'm looking at you, YouTube), so we swap for sanity. */ + if (max_bitrate > 0 && max_bitrate < avg_bitrate) { + guint temp = avg_bitrate; + + avg_bitrate = max_bitrate; + max_bitrate = temp; + } + + if (max_bitrate > 0 && max_bitrate < G_MAXUINT32) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, max_bitrate, NULL); + } + if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_BITRATE, avg_bitrate, NULL); + } + + break; + } + + default: + break; + } + + len -= size + 8; + avc_data += size + 8; + } + + break; + } + case FOURCC_H265: + case FOURCC_hvc1: + case FOURCC_hev1: +#ifdef DOLBYHDR_SUPPORT + case FOURCC_dvhe: +#endif + { + gint len = QT_UINT32 (stsd_entry_data) - 0x56; + const guint8 *hevc_data = stsd_entry_data + 0x56; + +#ifdef DOLBYHDR_SUPPORT + /* If following conditions are met, let's skip this track + * - Dolby Vision cannot be supported by this platform + * - Dolby Vision with dual-track + * - This track is for EL + */ + if (!qtdemux->dolby_vision_support && qtdemux->has_dolby_bl_cand + && fourcc == FOURCC_dvhe && !qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore non SDR dolby-vision track"); + qtdemux->has_dolby_el_cand = FALSE; + goto skip_track; + } +#endif + + /* find hevc */ + while (len >= 0x8) { + gint size; + + if (QT_UINT32 (hevc_data) <= len) + size = QT_UINT32 (hevc_data) - 0x8; + else + size = len - 0x8; + + if (size < 1) + /* No real data, so break out */ + break; + + switch (QT_FOURCC (hevc_data + 0x4)) { + case FOURCC_hvcC: + { + /* parse, if found */ + GstBuffer *buf; + + GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd"); + + /* First 4 bytes are the length of the atom, the next 4 bytes + * are the fourcc, the next 1 byte is the version, and the + * subsequent bytes are sequence parameter set like data. */ + gst_codec_utils_h265_caps_set_level_tier_and_profile + (stream->caps, hevc_data + 8 + 1, size - 1); + + buf = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buf, 0, hevc_data + 0x8, size); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + break; + } + default: + break; + } + len -= size + 8; + hevc_data += size + 8; + } + break; + } + case FOURCC_mp4v: + case FOURCC_MP4V: + case FOURCC_fmp4: + case FOURCC_FMP4: + { + GNode *glbl; + + GST_DEBUG_OBJECT (qtdemux, "found %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + + /* codec data might be in glbl extension atom */ + glbl = mp4v ? + qtdemux_tree_get_child_by_type (mp4v, FOURCC_glbl) : NULL; + if (glbl) { + guint8 *data; + GstBuffer *buf; + gint len; + + GST_DEBUG_OBJECT (qtdemux, "found glbl data in stsd"); + data = glbl->data; + len = QT_UINT32 (data); + if (len > 0x8) { + len -= 0x8; + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, data + 8, len); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + } + break; + } + case FOURCC_mjp2: + { + /* see annex I of the jpeg2000 spec */ + GNode *jp2h, *ihdr, *colr, *mjp2, *field, *prefix, *cmap, *cdef; + const guint8 *data; + const gchar *colorspace = NULL; + gint ncomp = 0; + guint32 ncomp_map = 0; + gint32 *comp_map = NULL; + guint32 nchan_def = 0; + gint32 *chan_def = NULL; + + GST_DEBUG_OBJECT (qtdemux, "found mjp2"); + /* some required atoms */ + mjp2 = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!mjp2) + break; + jp2h = qtdemux_tree_get_child_by_type (mjp2, FOURCC_jp2h); + if (!jp2h) + break; + + /* number of components; redundant with info in codestream, but useful + to a muxer */ + ihdr = qtdemux_tree_get_child_by_type (jp2h, FOURCC_ihdr); + if (!ihdr || QT_UINT32 (ihdr->data) != 22) + break; + ncomp = QT_UINT16 (((guint8 *) ihdr->data) + 16); + + colr = qtdemux_tree_get_child_by_type (jp2h, FOURCC_colr); + if (!colr) + break; + GST_DEBUG_OBJECT (qtdemux, "found colr"); + /* extract colour space info */ + if (QT_UINT8 ((guint8 *) colr->data + 8) == 1) { + switch (QT_UINT32 ((guint8 *) colr->data + 11)) { + case 16: + colorspace = "sRGB"; + break; + case 17: + colorspace = "GRAY"; + break; + case 18: + colorspace = "sYUV"; + break; + default: + colorspace = NULL; + break; + } + } + if (!colorspace) + /* colr is required, and only values 16, 17, and 18 are specified, + so error if we have no colorspace */ + break; + + /* extract component mapping */ + cmap = qtdemux_tree_get_child_by_type (jp2h, FOURCC_cmap); + if (cmap) { + guint32 cmap_len = 0; + int i; + cmap_len = QT_UINT32 (cmap->data); + if (cmap_len >= 8) { + /* normal box, subtract off header */ + cmap_len -= 8; + /* cmap: { u16 cmp; u8 mtyp; u8 pcol; }* */ + if (cmap_len % 4 == 0) { + ncomp_map = (cmap_len / 4); + comp_map = g_new0 (gint32, ncomp_map); + for (i = 0; i < ncomp_map; i++) { + guint16 cmp; + guint8 mtyp, pcol; + cmp = QT_UINT16 (((guint8 *) cmap->data) + 8 + i * 4); + mtyp = QT_UINT8 (((guint8 *) cmap->data) + 8 + i * 4 + 2); + pcol = QT_UINT8 (((guint8 *) cmap->data) + 8 + i * 4 + 3); + comp_map[i] = (mtyp << 24) | (pcol << 16) | cmp; + } + } + } + } + /* extract channel definitions */ + cdef = qtdemux_tree_get_child_by_type (jp2h, FOURCC_cdef); + if (cdef) { + guint32 cdef_len = 0; + int i; + cdef_len = QT_UINT32 (cdef->data); + if (cdef_len >= 10) { + /* normal box, subtract off header and len */ + cdef_len -= 10; + /* cdef: u16 n; { u16 cn; u16 typ; u16 asoc; }* */ + if (cdef_len % 6 == 0) { + nchan_def = (cdef_len / 6); + chan_def = g_new0 (gint32, nchan_def); + for (i = 0; i < nchan_def; i++) + chan_def[i] = -1; + for (i = 0; i < nchan_def; i++) { + guint16 cn, typ, asoc; + cn = QT_UINT16 (((guint8 *) cdef->data) + 10 + i * 6); + typ = QT_UINT16 (((guint8 *) cdef->data) + 10 + i * 6 + 2); + asoc = QT_UINT16 (((guint8 *) cdef->data) + 10 + i * 6 + 4); + if (cn < nchan_def) { + switch (typ) { + case 0: + chan_def[cn] = asoc; + break; + case 1: + chan_def[cn] = 0; /* alpha */ + break; + default: + chan_def[cn] = -typ; + } + } + } + } + } + } + + gst_caps_set_simple (stream->caps, + "num-components", G_TYPE_INT, ncomp, NULL); + gst_caps_set_simple (stream->caps, + "colorspace", G_TYPE_STRING, colorspace, NULL); + + if (comp_map) { + GValue arr = { 0, }; + GValue elt = { 0, }; + int i; + g_value_init (&arr, GST_TYPE_ARRAY); + g_value_init (&elt, G_TYPE_INT); + for (i = 0; i < ncomp_map; i++) { + g_value_set_int (&elt, comp_map[i]); + gst_value_array_append_value (&arr, &elt); + } + gst_structure_set_value (gst_caps_get_structure (stream->caps, 0), + "component-map", &arr); + g_value_unset (&elt); + g_value_unset (&arr); + g_free (comp_map); + } + + if (chan_def) { + GValue arr = { 0, }; + GValue elt = { 0, }; + int i; + g_value_init (&arr, GST_TYPE_ARRAY); + g_value_init (&elt, G_TYPE_INT); + for (i = 0; i < nchan_def; i++) { + g_value_set_int (&elt, chan_def[i]); + gst_value_array_append_value (&arr, &elt); + } + gst_structure_set_value (gst_caps_get_structure (stream->caps, 0), + "channel-definitions", &arr); + g_value_unset (&elt); + g_value_unset (&arr); + g_free (chan_def); + } + + /* some optional atoms */ + field = qtdemux_tree_get_child_by_type (mjp2, FOURCC_fiel); + prefix = qtdemux_tree_get_child_by_type (mjp2, FOURCC_jp2x); + + /* indicate possible fields in caps */ + if (field) { + data = (guint8 *) field->data + 8; + if (*data != 1) + gst_caps_set_simple (stream->caps, "fields", G_TYPE_INT, + (gint) * data, NULL); + } + /* add codec_data if provided */ + if (prefix) { + GstBuffer *buf; + gint len; + + GST_DEBUG_OBJECT (qtdemux, "found prefix data in stsd"); + data = prefix->data; + len = QT_UINT32 (data); + if (len > 0x8) { + len -= 0x8; + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, data + 8, len); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + } + break; + } + case FOURCC_SVQ3: + case FOURCC_VP31: + { + GstBuffer *buf; + GstBuffer *seqh = NULL; + const guint8 *gamma_data = NULL; + gint len = QT_UINT32 (stsd_data); /* FIXME review - why put the whole stsd in codec data? */ + + qtdemux_parse_svq3_stsd_data (qtdemux, stsd_entry_data, &gamma_data, + &seqh); + if (gamma_data) { + gst_caps_set_simple (stream->caps, "applied-gamma", G_TYPE_DOUBLE, + QT_FP32 (gamma_data), NULL); + } + if (seqh) { + /* sorry for the bad name, but we don't know what this is, other + * than its own fourcc */ + gst_caps_set_simple (stream->caps, "seqh", GST_TYPE_BUFFER, seqh, + NULL); + } + + GST_DEBUG_OBJECT (qtdemux, "found codec_data in stsd"); + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, stsd_data, len); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + break; + } + case FOURCC_SVQ1: + case FOURCC_rak3: + { + /* Decoder does not support video codec of SVQ1 */ + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file contains no playable stream.")), + ("Because of unsupported codec")); + gst_qtdemux_stream_free (qtdemux, stream); + return FALSE; + } + case FOURCC_rle_: + case FOURCC_WRLE: + { + gst_caps_set_simple (stream->caps, + "depth", G_TYPE_INT, QT_UINT16 (stsd_entry_data + offset + 66), + NULL); + break; + } + case FOURCC_XiTh: + { + GNode *xith, *xdxt; + + GST_DEBUG_OBJECT (qtdemux, "found XiTh"); + xith = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!xith) + break; + + xdxt = qtdemux_tree_get_child_by_type (xith, FOURCC_XdxT); + if (!xdxt) + break; + + GST_DEBUG_OBJECT (qtdemux, "found XdxT node"); + /* collect the headers and store them in a stream list so that we can + * send them out first */ + qtdemux_parse_theora_extension (qtdemux, stream, xdxt); + break; + } + case FOURCC_ovc1: + { + GNode *ovc1; + guint8 *ovc1_data; + guint ovc1_len; + GstBuffer *buf; + + GST_DEBUG_OBJECT (qtdemux, "parse ovc1 header"); + ovc1 = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!ovc1) + break; + ovc1_data = ovc1->data; + ovc1_len = QT_UINT32 (ovc1_data); + if (ovc1_len <= 198) { + GST_WARNING_OBJECT (qtdemux, "Too small ovc1 header, skipping"); + break; + } + buf = gst_buffer_new_and_alloc (ovc1_len - 198); + gst_buffer_fill (buf, 0, ovc1_data + 198, ovc1_len - 198); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + break; + } + case FOURCC_vc_1: + { + gint len = QT_UINT32 (stsd_entry_data) - 0x56; + const guint8 *vc1_data = stsd_entry_data + 0x56; + + /* find dvc1 */ + while (len >= 8) { + gint size; + + if (QT_UINT32 (vc1_data) <= len) + size = QT_UINT32 (vc1_data) - 8; + else + size = len - 8; + + if (size < 1) + /* No real data, so break out */ + break; + + switch (QT_FOURCC (vc1_data + 0x4)) { + case GST_MAKE_FOURCC ('d', 'v', 'c', '1'): + { + GstBuffer *buf; + + GST_DEBUG_OBJECT (qtdemux, "found dvc1 codec_data in stsd"); + buf = gst_buffer_new_and_alloc (size); + gst_buffer_fill (buf, 0, vc1_data + 8, size); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + break; + } + default: + break; + } + len -= size + 8; + vc1_data += size + 8; + } + break; + } + default: + break; + } + } + + GST_INFO_OBJECT (qtdemux, + "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT, + GST_FOURCC_ARGS (fourcc), stream->caps); + + } else if (stream->subtype == FOURCC_soun) { + int version, samplesize; + guint16 compression_id; + gboolean amrwb = FALSE; + + offset = 16; + /* sample description entry (16) + sound sample description v0 (20) */ + if (len < 36) + goto corrupt_file; + + version = QT_UINT32 (stsd_entry_data + offset); + stream->n_channels = QT_UINT16 (stsd_entry_data + offset + 8); + samplesize = QT_UINT16 (stsd_entry_data + offset + 10); + compression_id = QT_UINT16 (stsd_entry_data + offset + 12); + stream->rate = QT_FP32 (stsd_entry_data + offset + 16); + + GST_LOG_OBJECT (qtdemux, "version/rev: %08x", version); + GST_LOG_OBJECT (qtdemux, "vendor: %08x", + QT_UINT32 (stsd_entry_data + offset + 4)); + GST_LOG_OBJECT (qtdemux, "n_channels: %d", stream->n_channels); + GST_LOG_OBJECT (qtdemux, "sample_size: %d", samplesize); + GST_LOG_OBJECT (qtdemux, "compression_id: %d", compression_id); + GST_LOG_OBJECT (qtdemux, "packet size: %d", + QT_UINT16 (stsd_entry_data + offset + 14)); + GST_LOG_OBJECT (qtdemux, "sample rate: %g", stream->rate); + + if (compression_id == 0xfffe) + stream->sampled = TRUE; + + /* first assume uncompressed audio */ + stream->bytes_per_sample = samplesize / 8; + stream->samples_per_frame = stream->n_channels; + stream->bytes_per_frame = stream->n_channels * stream->bytes_per_sample; + stream->samples_per_packet = stream->samples_per_frame; + stream->bytes_per_packet = stream->bytes_per_sample; + + offset = 36; + switch (fourcc) { + /* Yes, these have to be hard-coded */ + case FOURCC_MAC6: + { + stream->samples_per_packet = 6; + stream->bytes_per_packet = 1; + stream->bytes_per_frame = 1 * stream->n_channels; + stream->bytes_per_sample = 1; + stream->samples_per_frame = 6 * stream->n_channels; + break; + } + case FOURCC_MAC3: + { + stream->samples_per_packet = 3; + stream->bytes_per_packet = 1; + stream->bytes_per_frame = 1 * stream->n_channels; + stream->bytes_per_sample = 1; + stream->samples_per_frame = 3 * stream->n_channels; + break; + } + case FOURCC_ima4: + { + stream->samples_per_packet = 64; + stream->bytes_per_packet = 34; + stream->bytes_per_frame = 34 * stream->n_channels; + stream->bytes_per_sample = 2; + stream->samples_per_frame = 64 * stream->n_channels; + break; + } + case FOURCC_ulaw: + case FOURCC_alaw: + { + stream->samples_per_packet = 1; + stream->bytes_per_packet = 1; + stream->bytes_per_frame = 1 * stream->n_channels; + stream->bytes_per_sample = 1; + stream->samples_per_frame = 1 * stream->n_channels; + break; + } + case FOURCC_agsm: + { + stream->samples_per_packet = 160; + stream->bytes_per_packet = 33; + stream->bytes_per_frame = 33 * stream->n_channels; + stream->bytes_per_sample = 2; + stream->samples_per_frame = 160 * stream->n_channels; + break; + } + default: + break; + } + + if (version == 0x00010000) { + /* sample description entry (16) + sound sample description v1 (20+16) */ + if (len < 52) + goto corrupt_file; + + switch (fourcc) { + case FOURCC_twos: + case FOURCC_sowt: + case FOURCC_raw_: + break; + default: + { + /* only parse extra decoding config for non-pcm audio */ + stream->samples_per_packet = QT_UINT32 (stsd_entry_data + offset); + stream->bytes_per_packet = QT_UINT32 (stsd_entry_data + offset + 4); + stream->bytes_per_frame = QT_UINT32 (stsd_entry_data + offset + 8); + stream->bytes_per_sample = QT_UINT32 (stsd_entry_data + offset + 12); + + GST_LOG_OBJECT (qtdemux, "samples/packet: %d", + stream->samples_per_packet); + GST_LOG_OBJECT (qtdemux, "bytes/packet: %d", + stream->bytes_per_packet); + GST_LOG_OBJECT (qtdemux, "bytes/frame: %d", + stream->bytes_per_frame); + GST_LOG_OBJECT (qtdemux, "bytes/sample: %d", + stream->bytes_per_sample); + + if (!stream->sampled && stream->bytes_per_packet) { + stream->samples_per_frame = (stream->bytes_per_frame / + stream->bytes_per_packet) * stream->samples_per_packet; + GST_LOG_OBJECT (qtdemux, "samples/frame: %d", + stream->samples_per_frame); + } + break; + } + } + } else if (version == 0x00020000) { + union + { + gdouble fp; + guint64 val; + } qtfp; + + /* sample description entry (16) + sound sample description v2 (56) */ + if (len < 72) + goto corrupt_file; + + qtfp.val = QT_UINT64 (stsd_entry_data + offset + 4); + stream->rate = qtfp.fp; + stream->n_channels = QT_UINT32 (stsd_entry_data + offset + 12); + + GST_LOG_OBJECT (qtdemux, "Sound sample description Version 2"); + GST_LOG_OBJECT (qtdemux, "sample rate: %g", stream->rate); + GST_LOG_OBJECT (qtdemux, "n_channels: %d", stream->n_channels); + GST_LOG_OBJECT (qtdemux, "bits/channel: %d", + QT_UINT32 (stsd_entry_data + offset + 20)); + GST_LOG_OBJECT (qtdemux, "format flags: %X", + QT_UINT32 (stsd_entry_data + offset + 24)); + GST_LOG_OBJECT (qtdemux, "bytes/packet: %d", + QT_UINT32 (stsd_entry_data + offset + 28)); + GST_LOG_OBJECT (qtdemux, "LPCM frames/packet: %d", + QT_UINT32 (stsd_entry_data + offset + 32)); + } else if (version != 0x00000) { + GST_WARNING_OBJECT (qtdemux, "unknown audio STSD version %08x", version); + } + + if (stream->caps) + gst_caps_unref (stream->caps); + + stream->caps = qtdemux_audio_caps (qtdemux, stream, fourcc, + stsd_entry_data + 32, len - 16, &codec); + + switch (fourcc) { + case FOURCC_in24: + { + GNode *enda; + GNode *in24; + + in24 = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!in24) + break; + enda = qtdemux_tree_get_child_by_type (in24, FOURCC_enda); + if (!enda) { + wave = qtdemux_tree_get_child_by_type (in24, FOURCC_wave); + if (wave) + enda = qtdemux_tree_get_child_by_type (wave, FOURCC_enda); + } + if (enda) { + int enda_value = QT_UINT16 ((guint8 *) enda->data + 8); + gst_caps_set_simple (stream->caps, + "format", G_TYPE_STRING, (enda_value) ? "S24LE" : "S24BE", NULL); + } + break; + } + case FOURCC_owma: + { + GNode *owma; + const guint8 *owma_data; + const gchar *codec_name = NULL; + guint owma_len; + GstBuffer *buf; + gint version = 1; + /* from http://msdn.microsoft.com/en-us/library/dd757720(VS.85).aspx */ + /* FIXME this should also be gst_riff_strf_auds, + * but the latter one is actually missing bits-per-sample :( */ + typedef struct + { + gint16 wFormatTag; + gint16 nChannels; + gint32 nSamplesPerSec; + gint32 nAvgBytesPerSec; + gint16 nBlockAlign; + gint16 wBitsPerSample; + gint16 cbSize; + } WAVEFORMATEX; + WAVEFORMATEX *wfex; + + GST_DEBUG_OBJECT (qtdemux, "parse owma"); + owma = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!owma) + break; + owma_data = owma->data; + owma_len = QT_UINT32 (owma_data); + if (owma_len <= 54) { + GST_WARNING_OBJECT (qtdemux, "Too small owma header, skipping"); + break; + } + wfex = (WAVEFORMATEX *) (owma_data + 36); + buf = gst_buffer_new_and_alloc (owma_len - 54); + gst_buffer_fill (buf, 0, owma_data + 54, owma_len - 54); + if (wfex->wFormatTag == 0x0161) { + codec_name = "Windows Media Audio"; + version = 2; + } else if (wfex->wFormatTag == 0x0162) { + codec_name = "Windows Media Audio 9 Pro"; + version = 3; + } else if (wfex->wFormatTag == 0x0163) { + codec_name = "Windows Media Audio 9 Lossless"; + /* is that correct? gstffmpegcodecmap.c is missing it, but + * fluendo codec seems to support it */ + version = 4; + } + + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, + "wmaversion", G_TYPE_INT, version, + "block_align", G_TYPE_INT, GST_READ_UINT16_LE (&wfex->nBlockAlign), + "bitrate", G_TYPE_INT, GST_READ_UINT32_LE (&wfex->nAvgBytesPerSec), + "width", G_TYPE_INT, GST_READ_UINT16_LE (&wfex->wBitsPerSample), + "depth", G_TYPE_INT, GST_READ_UINT16_LE (&wfex->wBitsPerSample), + NULL); + gst_buffer_unref (buf); + + if (codec_name) { + g_free (codec); + codec = g_strdup (codec_name); + } + break; + } + case FOURCC_wma_: + { + gint len = QT_UINT32 (stsd_entry_data) - offset; + const guint8 *wfex_data = stsd_entry_data + offset; + const gchar *codec_name = NULL; + gint version = 1; + /* from http://msdn.microsoft.com/en-us/library/dd757720(VS.85).aspx */ + /* FIXME this should also be gst_riff_strf_auds, + * but the latter one is actually missing bits-per-sample :( */ + typedef struct + { + gint16 wFormatTag; + gint16 nChannels; + gint32 nSamplesPerSec; + gint32 nAvgBytesPerSec; + gint16 nBlockAlign; + gint16 wBitsPerSample; + gint16 cbSize; + } WAVEFORMATEX; + WAVEFORMATEX wfex; + + /* FIXME: unify with similar wavformatex parsing code above */ + GST_DEBUG_OBJECT (qtdemux, "parse wma, looking for wfex"); + + /* find wfex */ + while (len >= 8) { + gint size; + + if (QT_UINT32 (wfex_data) <= len) + size = QT_UINT32 (wfex_data) - 8; + else + size = len - 8; + + if (size < 1) + /* No real data, so break out */ + break; + + switch (QT_FOURCC (wfex_data + 4)) { + case GST_MAKE_FOURCC ('w', 'f', 'e', 'x'): + { + GST_DEBUG_OBJECT (qtdemux, "found wfex in stsd"); + + if (size < 8 + 18) + break; + + wfex.wFormatTag = GST_READ_UINT16_LE (wfex_data + 8 + 0); + wfex.nChannels = GST_READ_UINT16_LE (wfex_data + 8 + 2); + wfex.nSamplesPerSec = GST_READ_UINT32_LE (wfex_data + 8 + 4); + wfex.nAvgBytesPerSec = GST_READ_UINT32_LE (wfex_data + 8 + 8); + wfex.nBlockAlign = GST_READ_UINT16_LE (wfex_data + 8 + 12); + wfex.wBitsPerSample = GST_READ_UINT16_LE (wfex_data + 8 + 14); + wfex.cbSize = GST_READ_UINT16_LE (wfex_data + 8 + 16); + + GST_LOG_OBJECT (qtdemux, "Found wfex box in stsd:"); + GST_LOG_OBJECT (qtdemux, "FormatTag = 0x%04x, Channels = %u, " + "SamplesPerSec = %u, AvgBytesPerSec = %u, BlockAlign = %u, " + "BitsPerSample = %u, Size = %u", wfex.wFormatTag, + wfex.nChannels, wfex.nSamplesPerSec, wfex.nAvgBytesPerSec, + wfex.nBlockAlign, wfex.wBitsPerSample, wfex.cbSize); + + if (wfex.wFormatTag == 0x0161) { + codec_name = "Windows Media Audio"; + version = 2; + } else if (wfex.wFormatTag == 0x0162) { + codec_name = "Windows Media Audio 9 Pro"; + version = 3; + } else if (wfex.wFormatTag == 0x0163) { + codec_name = "Windows Media Audio 9 Lossless"; + /* is that correct? gstffmpegcodecmap.c is missing it, but + * fluendo codec seems to support it */ + version = 4; + } + + gst_caps_set_simple (stream->caps, + "wmaversion", G_TYPE_INT, version, + "block_align", G_TYPE_INT, wfex.nBlockAlign, + "bitrate", G_TYPE_INT, wfex.nAvgBytesPerSec, + "width", G_TYPE_INT, wfex.wBitsPerSample, + "depth", G_TYPE_INT, wfex.wBitsPerSample, NULL); + + if (size > wfex.cbSize) { + GstBuffer *buf; + + buf = gst_buffer_new_and_alloc (size - wfex.cbSize); + gst_buffer_fill (buf, 0, wfex_data + 8 + wfex.cbSize, + size - wfex.cbSize); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } else { + GST_WARNING_OBJECT (qtdemux, "no codec data"); + } + + if (codec_name) { + g_free (codec); + codec = g_strdup (codec_name); + } + break; + } + default: + break; + } + len -= size + 8; + wfex_data += size + 8; + } + break; + } + case FOURCC_opus: + { + GNode *opus; + const guint8 *opus_data; + guint8 *channel_mapping = NULL; + guint32 rate; + guint8 channels; + guint8 channel_mapping_family; + guint8 stream_count; + guint8 coupled_count; + guint8 i; + + opus = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (!opus) + break; + opus_data = opus->data; + + channels = GST_READ_UINT8 (opus_data + 45); + rate = GST_READ_UINT32_LE (opus_data + 48); + channel_mapping_family = GST_READ_UINT8 (opus_data + 54); + stream_count = GST_READ_UINT8 (opus_data + 55); + coupled_count = GST_READ_UINT8 (opus_data + 56); + + if (channels > 0) { + channel_mapping = g_malloc (channels * sizeof (guint8)); + for (i = 0; i < channels; i++) + channel_mapping[i] = GST_READ_UINT8 (opus_data + i + 57); + } + + stream->caps = gst_codec_utils_opus_create_caps (rate, channels, + channel_mapping_family, stream_count, coupled_count, + channel_mapping); + break; + } + default: + break; + } + + if (codec) { + GstStructure *s; + gint bitrate = 0; + + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_AUDIO_CODEC, codec, NULL); + g_free (codec); + codec = NULL; + + /* some bitrate info may have ended up in caps */ + s = gst_caps_get_structure (stream->caps, 0); + gst_structure_get_int (s, "bitrate", &bitrate); + if (bitrate > 0) + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_BITRATE, bitrate, NULL); + } + + mp4a = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (stream->protected && fourcc == FOURCC_mp4a) { + if (mp4a == NULL || QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca) { + mp4a = NULL; + } + } else { + if (mp4a == NULL || QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_mp4a) { + mp4a = NULL; + } + } + + wave = NULL; + esds = NULL; + if (mp4a) { + wave = qtdemux_tree_get_child_by_type (mp4a, FOURCC_wave); + if (wave) + esds = qtdemux_tree_get_child_by_type (wave, FOURCC_esds); + if (!esds) + esds = qtdemux_tree_get_child_by_type (mp4a, FOURCC_esds); + } + + + /* If the fourcc's bottom 16 bits gives 'sm', then the top + 16 bits is a byte-swapped wave-style codec identifier, + and we can find a WAVE header internally to a 'wave' atom here. + This can more clearly be thought of as 'ms' as the top 16 bits, and a + codec id as the bottom 16 bits - but byte-swapped to store in QT (which + is big-endian). + */ + if ((fourcc & 0xffff) == (('s' << 8) | 'm')) { + if (len < offset + 20) { + GST_WARNING_OBJECT (qtdemux, "No wave atom in MS-style audio"); + } else { + guint32 datalen = QT_UINT32 (stsd_entry_data + offset + 16); + const guint8 *data = stsd_entry_data + offset + 16; + GNode *wavenode; + GNode *waveheadernode; + + wavenode = g_node_new ((guint8 *) data); + if (qtdemux_parse_node (qtdemux, wavenode, data, datalen)) { + const guint8 *waveheader; + guint32 headerlen; + + waveheadernode = qtdemux_tree_get_child_by_type (wavenode, fourcc); + if (waveheadernode) { + waveheader = (const guint8 *) waveheadernode->data; + headerlen = QT_UINT32 (waveheader); + + if (headerlen > 8) { + gst_riff_strf_auds *header = NULL; + GstBuffer *headerbuf; + GstBuffer *extra; + + waveheader += 8; + headerlen -= 8; + + headerbuf = gst_buffer_new_and_alloc (headerlen); + gst_buffer_fill (headerbuf, 0, waveheader, headerlen); + + if (gst_riff_parse_strf_auds (GST_ELEMENT_CAST (qtdemux), + headerbuf, &header, &extra)) { + gst_caps_unref (stream->caps); + /* FIXME: Need to do something with the channel reorder map */ + stream->caps = gst_riff_create_audio_caps (header->format, NULL, + header, extra, NULL, NULL, NULL); + + if (extra) + gst_buffer_unref (extra); + g_free (header); + } + } + } else + GST_DEBUG ("Didn't find waveheadernode for this codec"); + } + g_node_destroy (wavenode); + } + } else if (esds) { + gst_qtdemux_handle_esds (qtdemux, stream, esds, stream->pending_tags); + } else { + switch (fourcc) { +#if 0 + /* FIXME: what is in the chunk? */ + case FOURCC_QDMC: + { + gint len = QT_UINT32 (stsd_data); + + /* seems to be always = 116 = 0x74 */ + break; + } +#endif + case FOURCC_QDM2: + { + gint len = QT_UINT32 (stsd_entry_data); + + if (len > 0x3C) { + GstBuffer *buf = gst_buffer_new_and_alloc (len - 0x3C); + + gst_buffer_fill (buf, 0, stsd_entry_data + 0x3C, len - 0x3C); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + gst_caps_set_simple (stream->caps, + "samplesize", G_TYPE_INT, samplesize, NULL); + break; + } + case FOURCC_alac: + { + GNode *alac, *wave = NULL; + + /* apparently, m4a has this atom appended directly in the stsd entry, + * while mov has it in a wave atom */ + alac = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (alac) { + /* alac now refers to stsd entry atom */ + wave = qtdemux_tree_get_child_by_type (alac, FOURCC_wave); + if (wave) + alac = qtdemux_tree_get_child_by_type (wave, FOURCC_alac); + else + alac = qtdemux_tree_get_child_by_type (alac, FOURCC_alac); + } + if (alac) { + const guint8 *alac_data = alac->data; + gint len = QT_UINT32 (alac->data); + GstBuffer *buf; + + if (len < 36) { + GST_DEBUG_OBJECT (qtdemux, + "discarding alac atom with unexpected len %d", len); + } else { + /* codec-data contains alac atom size and prefix, + * ffmpeg likes it that way, not quite gst-ish though ...*/ + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, alac->data, len); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + + stream->bytes_per_frame = QT_UINT32 (alac_data + 12); + stream->n_channels = QT_UINT8 (alac_data + 21); + stream->rate = QT_UINT32 (alac_data + 32); + } + } + gst_caps_set_simple (stream->caps, + "samplesize", G_TYPE_INT, samplesize, NULL); + break; + } + case FOURCC_sawb: + /* Fallthrough! */ + amrwb = TRUE; + case FOURCC_samr: + { + gint len = QT_UINT32 (stsd_entry_data); + + if (len > 0x24) { + GstBuffer *buf = gst_buffer_new_and_alloc (len - 0x24); + guint bitrate; + + gst_buffer_fill (buf, 0, stsd_entry_data + 0x24, len - 0x24); + + /* If we have enough data, let's try to get the 'damr' atom. See + * the 3GPP container spec (26.244) for more details. */ + if ((len - 0x34) > 8 && + (bitrate = qtdemux_parse_amr_bitrate (buf, amrwb))) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, bitrate, NULL); + } + + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + break; + } + case FOURCC_mp4a: + { + /* mp4a atom withtout ESDS; Attempt to build codec data from atom */ + gint len = QT_UINT32 (stsd_entry_data); + + if (len >= 34) { + guint16 sound_version = QT_UINT16 (stsd_entry_data + 16); + + if (sound_version == 1) { + guint16 channels = QT_UINT16 (stsd_entry_data + 24); + guint32 time_scale = QT_UINT32 (stsd_entry_data + 30); + guint8 codec_data[2]; + GstBuffer *buf; + gint profile = 2; /* FIXME: Can this be determined somehow? There doesn't seem to be anything in mp4a atom that specifis compression */ + + gint sample_rate_index = + gst_codec_utils_aac_get_index_from_sample_rate (time_scale); + + /* build AAC codec data */ + codec_data[0] = profile << 3; + codec_data[0] |= ((sample_rate_index >> 1) & 0x7); + codec_data[1] = (sample_rate_index & 0x01) << 7; + codec_data[1] |= (channels & 0xF) << 3; + + buf = gst_buffer_new_and_alloc (2); + gst_buffer_fill (buf, 0, codec_data, 2); + gst_caps_set_simple (stream->caps, + "codec_data", GST_TYPE_BUFFER, buf, NULL); + gst_buffer_unref (buf); + } + } + break; + } + case FOURCC_ec_3: + { + GNode *ec3, *dec3; + ec3 = qtdemux_tree_get_child_by_type (stsd, FOURCC_ec_3); + if (ec3) { + dec3 = qtdemux_tree_get_child_by_type (ec3, + GST_MAKE_FOURCC ('d', 'e', 'c', '3')); + + if (dec3) { + const guint8 *dec3_data = dec3->data; + gint len = QT_UINT32 (dec3_data); + + if (len < 12) { /* minimum required bit for ATMOS */ + GST_DEBUG_OBJECT (qtdemux, + "dec3 length is too small to define ATMOS"); + } else { + /* Refer to ETSI TS 102.366 v1.3.1 Annex F */ + guint8 num_ind_sub = QT_UINT8 (dec3_data + 9) & 0x7; + guint offset = 10; /* start from the next bit of num_ind_sub */ + gint i; + + for (i = 0; i < num_ind_sub + 1; i++) { + /* skip bytes for fields followed by num_dep_sub */ + offset += 2; + /* skip byte(s) based on num_dep_sub */ + offset += (QT_UINT8 (dec3_data + offset) & 0x30) ? 2 : 1; + } + + if (len - offset > 1) { /* need at least 2 bytes to be ATMOS */ + if (QT_UINT8 (dec3_data + offset) & 0x1) { + /* flag_ec3_extension_type_a == 1 */ + GST_DEBUG_OBJECT (qtdemux, "set ATMOS in caps"); + gst_caps_set_simple (stream->caps, + "immersive", G_TYPE_STRING, "ATMOS", NULL); + } else { + GST_DEBUG_OBJECT (qtdemux, + "ec3_ext_type_a flag was not detected, not an ATMOS"); + } + } else { + GST_DEBUG_OBJECT (qtdemux, + "no room for complexity_index_type_a, not an ATMOS"); + } + } + } + } + } + break; + case GST_MAKE_FOURCC ('s', 'q', 'c', 'p'): + { + /* FIXME:SQCP audio codec is not supported as of now. Maybe this + * codec can be supported in the future. + * Fix for http://hlm.lge.com/issue/browse/TMFTASK-8017 + */ + if (!qtdemux->fragmented) { + GST_DEBUG_OBJECT (qtdemux, "ignore track with codec SQCP "); + goto skip_track; + } + break; + } + default: + GST_INFO_OBJECT (qtdemux, + "unhandled type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + break; + } + } + GST_INFO_OBJECT (qtdemux, + "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT, + GST_FOURCC_ARGS (fourcc), stream->caps); + + } else if (stream->subtype == FOURCC_strm) { + if (fourcc == FOURCC_rtsp) { + stream->redirect_uri = qtdemux_get_rtsp_uri_from_hndl (qtdemux, minf); + } else { + GST_INFO_OBJECT (qtdemux, "unhandled stream type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + goto unknown_stream; + } + stream->sampled = TRUE; + } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text + || stream->subtype == FOURCC_sbtl || stream->subtype == FOURCC_subt) { + + stream->sampled = TRUE; + stream->sparse = TRUE; + + if (stream->caps) + gst_caps_unref (stream->caps); + + stream->caps = + qtdemux_sub_caps (qtdemux, stream, fourcc, stsd_entry_data, &codec); + if (codec) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_SUBTITLE_CODEC, codec, NULL); + g_free (codec); + codec = NULL; + } + + /* hunt for sort-of codec data */ + switch (fourcc) { + case FOURCC_mp4s: + { + GNode *mp4s = NULL; + GNode *esds = NULL; + + /* look for palette in a stsd->mp4s->esds sub-atom */ + mp4s = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (mp4s) + esds = qtdemux_tree_get_child_by_type (mp4s, FOURCC_esds); + if (esds == NULL) { + /* Invalid STSD */ + GST_LOG_OBJECT (qtdemux, "Skipping invalid stsd: no esds child"); + break; + } + + gst_qtdemux_handle_esds (qtdemux, stream, esds, stream->pending_tags); + break; + } + default: + GST_INFO_OBJECT (qtdemux, + "unhandled type %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + break; + } + GST_INFO_OBJECT (qtdemux, + "type %" GST_FOURCC_FORMAT " caps %" GST_PTR_FORMAT, + GST_FOURCC_ARGS (fourcc), stream->caps); + } else if (stream->subtype == FOURCC_hint) { + switch (fourcc) { + case FOURCC_mmth: + { + GNode *mmth = NULL; + const guint8 *mmth_data; + guint32 len; + + mmth = qtdemux_tree_get_child_by_index (stsd, stsd_index); + if (mmth) { + guint16 hinttrackversion; /* should be 1 */ + guint16 highestcompatibleversion; /* should be 1 */ + guint16 packet_id; + guint8 tmp; + gboolean has_mfus_flag; + gboolean is_timed; + + GST_DEBUG_OBJECT (qtdemux, "Found mmth"); + + mmth_data = mmth->data; + + len = QT_UINT32 (mmth_data); + + if (len != 23) { + GST_DEBUG_OBJECT (qtdemux, "mmth box length is too small"); + break; + } + + hinttrackversion = QT_UINT16 (mmth_data + 16); + highestcompatibleversion = QT_UINT16 (mmth_data + 16 + 2); + packet_id = QT_UINT16 (mmth_data + 16 + 4); + tmp = QT_UINT8 (mmth_data + 16 + 6); + has_mfus_flag = (tmp >> 7) & 0x1; + is_timed = (tmp >> 6) & 0x1; + + GST_DEBUG_OBJECT (qtdemux, "hint track version = %" G_GUINT16_FORMAT, + hinttrackversion); + GST_DEBUG_OBJECT (qtdemux, "highest compatible version = %" + G_GUINT16_FORMAT, highestcompatibleversion); + GST_DEBUG_OBJECT (qtdemux, "packet_id = %" G_GUINT16_FORMAT, + packet_id); + GST_DEBUG_OBJECT (qtdemux, "has_mfus_flag = %d, is_timed = %d", + has_mfus_flag, is_timed); + + qtdemux->has_mmth = TRUE; + qtdemux->is_mmth_timed = is_timed; + } + break; + } + default: + goto unknown_stream; + } + } else { + /* everything in 1 sample */ + stream->sampled = TRUE; + + stream->caps = + qtdemux_generic_caps (qtdemux, stream, fourcc, stsd_entry_data, &codec); + + if (stream->caps == NULL) + goto unknown_stream; + + if (codec) { + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_SUBTITLE_CODEC, codec, NULL); + g_free (codec); + codec = NULL; + } + } + + /* promote to sampled format */ + if (stream->fourcc == FOURCC_samr) { + /* force mono 8000 Hz for AMR */ + stream->sampled = TRUE; + stream->n_channels = 1; + stream->rate = 8000; + } else if (stream->fourcc == FOURCC_sawb) { + /* force mono 16000 Hz for AMR-WB */ + stream->sampled = TRUE; + stream->n_channels = 1; + stream->rate = 16000; + } else if (stream->fourcc == FOURCC_mp4a) { + stream->sampled = TRUE; + } + + stsd_entry_data += len; + remaining_stsd_len -= len; + qtdemux_stream_copy_into_sample_description (qtdemux, stream, stsd_index); + stsd_index++; + if (stsd_index < stsd_entry_count) + goto stsd_entry_loop; + + /* reset stream's sample description data to the first stsd_entry data */ + if (stsd_entry_count > 1) { + qtdemux_sample_description_copy_into_stream (qtdemux, + &stream->stsd_entries[0], stream); + } + stream->cur_stsd_entry_index = 0; + + /* collect sample information */ + if (!qtdemux_stbl_init (qtdemux, stream, stbl)) + goto samples_failed; + + if (stream->stco.data != NULL) { + guint32 entry_num; + + entry_num = QT_UINT32 (stream->stco.data + stream->co_size); + + if (entry_num > 1 && entry_num < G_MAXUINT32) { + stream->first_chunk_offset = + QT_UINT32 (stream->stco.data + stream->co_size * 2); + stream->last_chunk_offset = + QT_UINT32 (stream->stco.data + stream->co_size * (entry_num + 1)); + + GST_DEBUG_OBJECT (qtdemux, + "(stream %" GST_FOURCC_FORMAT ") first offset %" G_GINT64_FORMAT + ", last offset %" G_GINT64_FORMAT, GST_FOURCC_ARGS (stream->subtype), + stream->first_chunk_offset, stream->last_chunk_offset); + } else { + stream->first_chunk_offset = 0; + stream->last_chunk_offset = 0; + } + } + + if (qtdemux->fragmented) { + guint64 offset; + + /* need all moov samples as basis; probably not many if any at all */ + /* prevent moof parsing taking of at this time */ + offset = qtdemux->moof_offset; + qtdemux->moof_offset = 0; + if (stream->n_samples && + !qtdemux_parse_samples (qtdemux, stream, stream->n_samples - 1)) { + qtdemux->moof_offset = offset; + goto samples_failed; + } + qtdemux->moof_offset = 0; + /* movie duration more reliable in this case (e.g. mehd) */ + if (qtdemux->segment.duration && + GST_CLOCK_TIME_IS_VALID (qtdemux->segment.duration)) + stream->duration = + GSTTIME_TO_QTSTREAMTIME (stream, qtdemux->segment.duration); + } + + /* configure segments */ + if (!qtdemux_parse_segments (qtdemux, stream, trak)) + goto segments_failed; + + /* add some language tag, if useful */ + if (stream->lang_id[0] != '\0' && strcmp (stream->lang_id, "unk") && + strcmp (stream->lang_id, "und")) { + const gchar *lang_code; + + /* convert ISO 639-2 code to ISO 639-1 */ + lang_code = gst_tag_get_language_code (stream->lang_id); + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_LANGUAGE_CODE, (lang_code) ? lang_code : stream->lang_id, NULL); + } + + /* Check for UDTA tags */ + if ((udta = qtdemux_tree_get_child_by_type (trak, FOURCC_udta))) { + qtdemux_parse_udta (qtdemux, stream->pending_tags, udta); + } + + /* add track_id in tag */ + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_ID, stream->track_id, NULL); + + /* now we are ready to add the stream */ + if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS) + goto too_many_streams; + + if (!qtdemux->got_moov) { + qtdemux->streams[qtdemux->n_streams] = stream; + qtdemux->n_streams++; + GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams); + } + + return TRUE; + +/* ERRORS */ +skip_track: + { + GST_INFO_OBJECT (qtdemux, "skip disabled track"); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return TRUE; + } +corrupt_file: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return FALSE; + } +error_encrypted: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL)); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return FALSE; + } +samples_failed: +segments_failed: + { + /* we posted an error already */ + /* free stbl sub-atoms */ + gst_qtdemux_stbl_free (stream); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return FALSE; + } +existing_stream: + { + GST_INFO_OBJECT (qtdemux, "stream with track id %i already exists", + track_id); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + return TRUE; + } +unknown_stream: + { + GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->subtype)); + if (new_stream) + gst_qtdemux_stream_free (qtdemux, stream); + if (codec) { + g_free (codec); + codec = NULL; + } + return TRUE; + } +too_many_streams: + { + GST_ELEMENT_WARNING (qtdemux, STREAM, DEMUX, + (_("This file contains too many streams. Only playing first %d"), + GST_QTDEMUX_MAX_STREAMS), (NULL)); + return TRUE; + } +} + +/* If we can estimate the overall bitrate, and don't have information about the + * stream bitrate for exactly one stream, this guesses the stream bitrate as + * the overall bitrate minus the sum of the bitrates of all other streams. This + * should be useful for the common case where we have one audio and one video + * stream and can estimate the bitrate of one, but not the other. */ +static void +gst_qtdemux_guess_bitrate (GstQTDemux * qtdemux) +{ + QtDemuxStream *stream = NULL; + gint64 size, sys_bitrate, sum_bitrate = 0; + GstClockTime duration; + gint i; + guint bitrate; + + if (qtdemux->fragmented) + return; + + GST_DEBUG_OBJECT (qtdemux, "Looking for streams with unknown bitrate"); + + if (!gst_pad_peer_query_duration (qtdemux->sinkpad, GST_FORMAT_BYTES, &size) + || size <= 0) { + GST_DEBUG_OBJECT (qtdemux, + "Size in bytes of the stream not known - bailing"); + return; + } + + /* Subtract the header size */ + GST_DEBUG_OBJECT (qtdemux, "Total size %" G_GINT64_FORMAT ", header size %u", + size, qtdemux->header_size); + + if (size < qtdemux->header_size) + return; + + size = size - qtdemux->header_size; + + if (!gst_qtdemux_get_duration (qtdemux, &duration)) { + GST_DEBUG_OBJECT (qtdemux, "Stream duration not known - bailing"); + return; + } + + for (i = 0; i < qtdemux->n_streams; i++) { + switch (qtdemux->streams[i]->subtype) { + case FOURCC_soun: + case FOURCC_vide: + GST_DEBUG_OBJECT (qtdemux, "checking bitrate for %" GST_PTR_FORMAT, + qtdemux->streams[i]->caps); + /* retrieve bitrate, prefer avg then max */ + bitrate = 0; + if (qtdemux->streams[i]->pending_tags) { + gst_tag_list_get_uint (qtdemux->streams[i]->pending_tags, + GST_TAG_MAXIMUM_BITRATE, &bitrate); + GST_DEBUG_OBJECT (qtdemux, "max-bitrate: %u", bitrate); + gst_tag_list_get_uint (qtdemux->streams[i]->pending_tags, + GST_TAG_NOMINAL_BITRATE, &bitrate); + GST_DEBUG_OBJECT (qtdemux, "nominal-bitrate: %u", bitrate); + gst_tag_list_get_uint (qtdemux->streams[i]->pending_tags, + GST_TAG_BITRATE, &bitrate); + GST_DEBUG_OBJECT (qtdemux, "bitrate: %u", bitrate); + } + if (bitrate) + sum_bitrate += bitrate; + else { + if (stream) { + GST_DEBUG_OBJECT (qtdemux, + ">1 stream with unknown bitrate - bailing"); + return; + } else + stream = qtdemux->streams[i]; + } + + default: + /* For other subtypes, we assume no significant impact on bitrate */ + break; + } + } + + if (!stream) { + GST_DEBUG_OBJECT (qtdemux, "All stream bitrates are known"); + return; + } + + sys_bitrate = gst_util_uint64_scale (size, GST_SECOND * 8, duration); + + if (sys_bitrate < sum_bitrate) { + /* This can happen, since sum_bitrate might be derived from maximum + * bitrates and not average bitrates */ + GST_DEBUG_OBJECT (qtdemux, + "System bitrate less than sum bitrate - bailing"); + return; + } + + bitrate = sys_bitrate - sum_bitrate; + GST_DEBUG_OBJECT (qtdemux, "System bitrate = %" G_GINT64_FORMAT + ", Stream bitrate = %u", sys_bitrate, bitrate); + + if (!stream->pending_tags) + stream->pending_tags = gst_tag_list_new_empty (); + + gst_tag_list_add (stream->pending_tags, GST_TAG_MERGE_REPLACE, + GST_TAG_BITRATE, bitrate, NULL); +} + +static GstFlowReturn +qtdemux_prepare_streams (GstQTDemux * qtdemux) +{ + gint i; + guint64 last_offset = 0; + GstFlowReturn ret = GST_FLOW_OK; + + GST_DEBUG_OBJECT (qtdemux, "prepare streams"); + + for (i = 0; ret == GST_FLOW_OK && i < qtdemux->n_streams; i++) { + QtDemuxStream *stream = qtdemux->streams[i]; + guint32 sample_num = 0; + + GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT, + i, stream->track_id, GST_FOURCC_ARGS (stream->fourcc)); + + if (i == 0) + last_offset = stream->last_chunk_offset; + else if (last_offset != 0 && stream->first_chunk_offset > last_offset + && ((stream->subtype == FOURCC_vide) + || stream->subtype == FOURCC_soun)) { + qtdemux->isInterleaved = FALSE; + GST_WARNING_OBJECT (qtdemux, "stream not interleaved file"); + } + + if (qtdemux->fragmented) { + /* need all moov samples first */ + GST_OBJECT_LOCK (qtdemux); + while (stream->n_samples == 0) + if ((ret = qtdemux_add_fragmented_samples (qtdemux)) != GST_FLOW_OK) + break; + GST_OBJECT_UNLOCK (qtdemux); + } else { + /* discard any stray moof */ + qtdemux->moof_offset = 0; + } + + /* prepare braking */ + if (ret != GST_FLOW_ERROR) + ret = GST_FLOW_OK; + + /* in pull mode, we should have parsed some sample info by now; + * and quite some code will not handle no samples. + * in push mode, we'll just have to deal with it */ + if (G_UNLIKELY (qtdemux->pullbased && !stream->n_samples)) { + GST_DEBUG_OBJECT (qtdemux, "no samples for stream; discarding"); + gst_qtdemux_remove_stream (qtdemux, i); + i--; + continue; + } + + /* parse the initial sample for use in setting the frame rate cap */ + while (sample_num == 0 && sample_num < stream->n_samples) { + if (!qtdemux_parse_samples (qtdemux, stream, sample_num)) + break; + ++sample_num; + } + if (stream->n_samples > 0 && stream->stbl_index >= 0) { + stream->first_duration = stream->samples[0].duration; + GST_LOG_OBJECT (qtdemux, "stream %d first duration %u", + stream->track_id, stream->first_duration); + } + } + + return ret; +} + +/* Must be called with expose lock */ +static GstFlowReturn +qtdemux_expose_streams (GstQTDemux * qtdemux) +{ + gint i; + GstFlowReturn ret = GST_FLOW_OK; + GSList *oldpads = NULL; + GSList *iter; + + GST_DEBUG_OBJECT (qtdemux, "exposing streams"); + + for (i = 0; ret == GST_FLOW_OK && i < qtdemux->n_streams; i++) { + QtDemuxStream *stream = qtdemux->streams[i]; + GstPad *oldpad = stream->pad; + GstTagList *list; + + GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT, + i, stream->track_id, GST_FOURCC_ARGS (stream->fourcc)); + + if ((stream->subtype == FOURCC_text || stream->subtype == FOURCC_sbtl) && + stream->track_id == qtdemux->chapters_track_id) { + /* TODO - parse chapters track and expose it as GstToc; For now just ignore it + so that it doesn't look like a subtitle track */ + gst_qtdemux_remove_stream (qtdemux, i); + i--; + continue; + } + + /* now we have all info and can expose */ + list = stream->pending_tags; + stream->pending_tags = NULL; + if (qtdemux->new_collection) + stream->discont = TRUE; + if (oldpad) + oldpads = g_slist_prepend (oldpads, oldpad); + if (!gst_qtdemux_add_stream (qtdemux, stream, list)) + return GST_FLOW_ERROR; + } + + gst_qtdemux_guess_bitrate (qtdemux); + + /* FIXME: in case dolby-vision dual track, stream-collection may cause + * problem. To prevent regression, let's block this. + * Please let me fix when stream-collection for dolby-vision dual track is verified */ + if (qtdemux->collection && !qtdemux->is_dolby_hdr) { + /* Emit collection message */ + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), + gst_message_new_stream_collection (GST_OBJECT_CAST (qtdemux), + qtdemux->collection)); + + /* Send collection event */ + gst_qtdemux_push_event (qtdemux, + gst_event_new_stream_collection (qtdemux->collection)); + } + + qtdemux->new_collection = FALSE; + + gst_element_no_more_pads (GST_ELEMENT_CAST (qtdemux)); + + for (iter = oldpads; iter; iter = g_slist_next (iter)) { + GstPad *oldpad = iter->data; + GstEvent *event; + + event = gst_event_new_eos (); + if (qtdemux->segment_seqnum) + gst_event_set_seqnum (event, qtdemux->segment_seqnum); + + gst_pad_push_event (oldpad, event); + gst_pad_set_active (oldpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (qtdemux), oldpad); + gst_flow_combiner_remove_pad (qtdemux->flowcombiner, oldpad); + /* FIXME: if refcount of this pad were controlled by demux, followng unref + * is correct!! But, refcount does not matched what we expected. maybe + * parsebin is doing it. Until fix it, temporarily disable this, because + * this causes segfault in desktop environment */ + //gst_object_unref (oldpad); + } + + /* check if we should post a redirect in case there is a single trak + * and it is a redirecting trak */ + if (qtdemux->n_streams == 1 && qtdemux->streams[0]->redirect_uri != NULL) { + GstMessage *m; + + GST_INFO_OBJECT (qtdemux, "Issuing a redirect due to a single track with " + "an external content"); + m = gst_message_new_element (GST_OBJECT_CAST (qtdemux), + gst_structure_new ("redirect", + "new-location", G_TYPE_STRING, qtdemux->streams[0]->redirect_uri, + NULL)); + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), m); + qtdemux->posted_redirect = TRUE; + } + + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream = qtdemux->streams[i]; + + qtdemux_do_allocation (qtdemux, stream); + } + + qtdemux->exposed = TRUE; + return ret; +} + +/* check if major or compatible brand is 3GP */ +static inline gboolean +qtdemux_is_brand_3gp (GstQTDemux * qtdemux, gboolean major) +{ + if (major) { + return ((qtdemux->major_brand & GST_MAKE_FOURCC (255, 255, 0, 0)) == + FOURCC_3g__); + } else if (qtdemux->comp_brands != NULL) { + GstMapInfo map; + guint8 *data; + gsize size; + gboolean res = FALSE; + + gst_buffer_map (qtdemux->comp_brands, &map, GST_MAP_READ); + data = map.data; + size = map.size; + while (size >= 4) { + res = res || ((QT_FOURCC (data) & GST_MAKE_FOURCC (255, 255, 0, 0)) == + FOURCC_3g__); + data += 4; + size -= 4; + } + gst_buffer_unmap (qtdemux->comp_brands, &map); + return res; + } else { + return FALSE; + } +} + +static gboolean +qtdemux_is_keyframe_trick_condition (GstQTDemux * qtdemux) +{ + gboolean condition = FALSE; + gdouble keyframe_trickrate = 4.0; + if (qtdemux->isBigData) + keyframe_trickrate = 2.0; + + if (qtdemux->segment.rate < 0.0 + || qtdemux->segment.rate >= keyframe_trickrate) + condition = TRUE; + return condition; +} + +/* check if tag is a spec'ed 3GP tag keyword storing a string */ +static inline gboolean +qtdemux_is_string_tag_3gp (GstQTDemux * qtdemux, guint32 fourcc) +{ + return fourcc == FOURCC_cprt || fourcc == FOURCC_gnre || fourcc == FOURCC_titl + || fourcc == FOURCC_dscp || fourcc == FOURCC_perf || fourcc == FOURCC_auth + || fourcc == FOURCC_albm; +} + +static void +qtdemux_tag_add_location (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + int offset; + char *name; + gchar *data; + gdouble longitude, latitude, altitude; + gint len; + + len = QT_UINT32 (node->data); + if (len <= 14) + goto short_read; + + data = node->data; + offset = 14; + + /* TODO: language code skipped */ + + name = gst_tag_freeform_string_to_utf8 (data + offset, -1, env_vars); + + if (!name) { + /* do not alarm in trivial case, but bail out otherwise */ + if (*(data + offset) != 0) { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8, " + "giving up", tag); + } + } else { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_GEO_LOCATION_NAME, name, NULL); + offset += strlen (name); + g_free (name); + } + + if (len < offset + 2 + 4 + 4 + 4) + goto short_read; + + /* +1 +1 = skip null-terminator and location role byte */ + offset += 1 + 1; + /* table in spec says unsigned, semantics say negative has meaning ... */ + longitude = QT_SFP32 (data + offset); + + offset += 4; + latitude = QT_SFP32 (data + offset); + + offset += 4; + altitude = QT_SFP32 (data + offset); + + /* one invalid means all are invalid */ + if (longitude >= -180.0 && longitude <= 180.0 && + latitude >= -90.0 && latitude <= 90.0) { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_GEO_LOCATION_LATITUDE, latitude, + GST_TAG_GEO_LOCATION_LONGITUDE, longitude, + GST_TAG_GEO_LOCATION_ELEVATION, altitude, NULL); + } + + /* TODO: no GST_TAG_, so astronomical body and additional notes skipped */ + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP location"); + return; + } +} + + +static void +qtdemux_tag_add_year (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + guint16 y; + GDate *date; + gint len; + + len = QT_UINT32 (node->data); + if (len < 14) + return; + + y = QT_UINT16 ((guint8 *) node->data + 12); + if (y == 0) { + GST_DEBUG_OBJECT (qtdemux, "year: %u is not a valid year", y); + return; + } + GST_DEBUG_OBJECT (qtdemux, "year: %u", y); + + date = g_date_new_dmy (1, 1, y); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); + g_date_free (date); +} + +static void +qtdemux_tag_add_classification (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + int offset; + char *tag_str = NULL; + guint8 *entity; + guint16 table; + gint len; + + len = QT_UINT32 (node->data); + if (len <= 20) + goto short_read; + + offset = 12; + entity = (guint8 *) node->data + offset; + if (entity[0] == 0 || entity[1] == 0 || entity[2] == 0 || entity[3] == 0) { + GST_DEBUG_OBJECT (qtdemux, + "classification info: %c%c%c%c invalid classification entity", + entity[0], entity[1], entity[2], entity[3]); + return; + } + + offset += 4; + table = QT_UINT16 ((guint8 *) node->data + offset); + + /* Language code skipped */ + + offset += 4; + + /* Tag format: "XXXX://Y[YYYY]/classification info string" + * XXXX: classification entity, fixed length 4 chars. + * Y[YYYY]: classification table, max 5 chars. + */ + tag_str = g_strdup_printf ("----://%u/%s", + table, (char *) node->data + offset); + + /* memcpy To be sure we're preserving byte order */ + memcpy (tag_str, entity, 4); + GST_DEBUG_OBJECT (qtdemux, "classification info: %s", tag_str); + + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, tag, tag_str, NULL); + + g_free (tag_str); + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP classification"); + return; + } +} + +static gboolean +qtdemux_tag_add_str_full (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + GNode *data; + char *s; + int len; + guint32 type; + int offset; + gboolean ret = TRUE; + const gchar *charset = NULL; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000001 && len > 16) { + s = gst_tag_freeform_string_to_utf8 ((char *) data->data + 16, len - 16, + env_vars); + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); + g_free (s); + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); + } + } + } else { + len = QT_UINT32 (node->data); + type = QT_UINT32 ((guint8 *) node->data + 4); + if ((type >> 24) == 0xa9) { + gint str_len; + gint lang_code; + + /* Type starts with the (C) symbol, so the next data is a list + * of (string size(16), language code(16), string) */ + + str_len = QT_UINT16 ((guint8 *) node->data + 8); + lang_code = QT_UINT16 ((guint8 *) node->data + 10); + + /* the string + fourcc + size + 2 16bit fields, + * means that there are more tags in this atom */ + if (len > str_len + 8 + 4) { + /* TODO how to represent the same tag in different languages? */ + GST_WARNING_OBJECT (qtdemux, "Ignoring metadata entry with multiple " + "text alternatives, reading only first one"); + } + + offset = 12; + len = str_len + 8 + 4; /* remove trailing strings that we don't use */ + GST_DEBUG_OBJECT (qtdemux, "found international text tag"); + + if (lang_code < 0x800) { /* MAC encoded string */ + charset = "mac"; + } + } else if (len > 14 && qtdemux_is_string_tag_3gp (qtdemux, + QT_FOURCC ((guint8 *) node->data + 4))) { + guint32 type = QT_UINT32 ((guint8 *) node->data + 8); + + /* we go for 3GP style encoding if major brands claims so, + * or if no hope for data be ok UTF-8, and compatible 3GP brand present */ + if (qtdemux_is_brand_3gp (qtdemux, TRUE) || + (qtdemux_is_brand_3gp (qtdemux, FALSE) && + ((type & 0x00FFFFFF) == 0x0) && (type >> 24 <= 0xF))) { + offset = 14; + /* 16-bit Language code is ignored here as well */ + GST_DEBUG_OBJECT (qtdemux, "found 3gpp text tag"); + } else { + goto normal; + } + } else { + normal: + offset = 8; + GST_DEBUG_OBJECT (qtdemux, "found normal text tag"); + ret = FALSE; /* may have to fallback */ + } + if (charset) { + GError *err = NULL; + + s = g_convert ((gchar *) node->data + offset, len - offset, "utf8", + charset, NULL, NULL, &err); + if (err) { + GST_DEBUG_OBJECT (qtdemux, "Failed to convert string from charset %s:" + " %s(%d): %s", charset, g_quark_to_string (err->domain), err->code, + err->message); + g_error_free (err); + } + } else { + s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, + len - offset, env_vars); + } + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); + g_free (s); + ret = TRUE; + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); + } + } + return ret; +} + +static void +qtdemux_tag_add_str (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node); +} + +static void +qtdemux_tag_add_keywords (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + guint8 *data; + char *s, *t, *k = NULL; + int len; + int offset; + int count; + + /* first try normal string tag if major brand not 3GP */ + if (!qtdemux_is_brand_3gp (qtdemux, TRUE)) { + if (!qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node)) { + /* hm, that did not work, maybe 3gpp storage in non-3gpp major brand; + * let's try it 3gpp way after minor safety check */ + data = node->data; + if (QT_UINT32 (data) < 15 || !qtdemux_is_brand_3gp (qtdemux, FALSE)) + return; + } else + return; + } + + GST_DEBUG_OBJECT (qtdemux, "found 3gpp keyword tag"); + + data = node->data; + + len = QT_UINT32 (data); + if (len < 15) + goto short_read; + + count = QT_UINT8 (data + 14); + offset = 15; + for (; count; count--) { + gint slen; + + if (offset + 1 > len) + goto short_read; + slen = QT_UINT8 (data + offset); + offset += 1; + if (offset + slen > len) + goto short_read; + s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, + slen, env_vars); + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding keyword %s", GST_STR_NULL (s)); + if (k) { + t = g_strjoin (",", k, s, NULL); + g_free (s); + g_free (k); + k = t; + } else { + k = s; + } + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert keyword to UTF-8"); + } + offset += slen; + } + +done: + if (k) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (k)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, k, NULL); + } + g_free (k); + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP keywords"); + goto done; + } +} + +static void +qtdemux_tag_add_num (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *tag2, GNode * node) +{ + GNode *data; + int len; + int type; + int n1, n2; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000000 && len >= 22) { + n1 = QT_UINT16 ((guint8 *) data->data + 18); + n2 = QT_UINT16 ((guint8 *) data->data + 20); + if (n1 > 0) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag1, n1); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, n1, NULL); + } + if (n2 > 0) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag2, n2); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag2, n2, NULL); + } + } + } +} + +static void +qtdemux_tag_add_tmpo (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + int n1; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have tempo tag, type=%d,len=%d", type, len); + /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ + if ((type == 0x00000015 || type == 0x0000000f) && len >= 18) { + n1 = QT_UINT16 ((guint8 *) data->data + 16); + if (n1) { + /* do not add bpm=0 */ + GST_DEBUG_OBJECT (qtdemux, "adding tag %d", n1); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, (gdouble) n1, + NULL); + } + } + } +} + +static void +qtdemux_tag_add_uint32 (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + guint32 num; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have %s tag, type=%d,len=%d", tag1, type, len); + /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ + if ((type == 0x00000015 || type == 0x0000000f) && len >= 20) { + num = QT_UINT32 ((guint8 *) data->data + 16); + if (num) { + /* do not add num=0 */ + GST_DEBUG_OBJECT (qtdemux, "adding tag %d", num); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, num, NULL); + } + } + } +} + +static void +qtdemux_tag_add_covr (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + GstSample *sample; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have covr tag, type=%d,len=%d", type, len); + if ((type == 0x0000000d || type == 0x0000000e) && len > 16) { + if ((sample = + gst_tag_image_data_to_image_sample ((guint8 *) data->data + 16, + len - 16, GST_TAG_IMAGE_TYPE_NONE))) { + GST_DEBUG_OBJECT (qtdemux, "adding tag size %d", len - 16); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, sample, NULL); + gst_sample_unref (sample); + } + } + } +} + +static void +qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + GNode *data; + char *s; + int len; + int type; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000001 && len > 16) { + guint y, m = 1, d = 1; + gint ret; + + s = g_strndup ((char *) data->data + 16, len - 16); + GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s); + ret = sscanf (s, "%u-%u-%u", &y, &m, &d); + if (ret >= 1 && y > 1500 && y < 3000) { + GDate *date; + + date = g_date_new_dmy (d, m, y); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); + g_date_free (date); + } else { + GST_DEBUG_OBJECT (qtdemux, "could not parse date string '%s'", s); + } + g_free (s); + } + } +} + +static void +qtdemux_tag_add_gnre (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + GNode *data; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + + /* re-route to normal string tag if major brand says so + * or no data atom and compatible brand suggests so */ + if (qtdemux_is_brand_3gp (qtdemux, TRUE) || + (qtdemux_is_brand_3gp (qtdemux, FALSE) && !data)) { + qtdemux_tag_add_str (qtdemux, taglist, tag, dummy, node); + return; + } + + if (data) { + guint len, type, n; + + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000000 && len >= 18) { + n = QT_UINT16 ((guint8 *) data->data + 16); + if (n > 0) { + const gchar *genre; + + genre = gst_tag_id3_genre_get (n - 1); + if (genre != NULL) { + GST_DEBUG_OBJECT (qtdemux, "adding %d [%s]", n, genre); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, genre, NULL); + } + } + } + } +} + +static void +qtdemux_add_double_tag_from_str (GstQTDemux * demux, GstTagList * taglist, + const gchar * tag, guint8 * data, guint32 datasize) +{ + gdouble value; + gchar *datacopy; + + /* make a copy to have \0 at the end */ + datacopy = g_strndup ((gchar *) data, datasize); + + /* convert the str to double */ + if (sscanf (datacopy, "%lf", &value) == 1) { + GST_DEBUG_OBJECT (demux, "adding tag: %s [%s]", tag, datacopy); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, value, NULL); + } else { + GST_WARNING_OBJECT (demux, "Failed to parse double from string: %s", + datacopy); + } + g_free (datacopy); +} + + +static void +qtdemux_tag_add_revdns (GstQTDemux * demux, GstTagList * taglist, + const char *tag, const char *tag_bis, GNode * node) +{ + GNode *mean; + GNode *name; + GNode *data; + guint32 meansize; + guint32 namesize; + guint32 datatype; + guint32 datasize; + const gchar *meanstr; + const gchar *namestr; + + /* checking the whole ---- atom size for consistency */ + if (QT_UINT32 (node->data) <= 4 + 12 + 12 + 16) { + GST_WARNING_OBJECT (demux, "Tag ---- atom is too small, ignoring"); + return; + } + + mean = qtdemux_tree_get_child_by_type (node, FOURCC_mean); + if (!mean) { + GST_WARNING_OBJECT (demux, "No 'mean' atom found"); + return; + } + + meansize = QT_UINT32 (mean->data); + if (meansize <= 12) { + GST_WARNING_OBJECT (demux, "Small mean atom, ignoring the whole tag"); + return; + } + meanstr = ((gchar *) mean->data) + 12; + meansize -= 12; + + name = qtdemux_tree_get_child_by_type (node, FOURCC_name); + if (!name) { + GST_WARNING_OBJECT (demux, "'name' atom not found, ignoring tag"); + return; + } + + namesize = QT_UINT32 (name->data); + if (namesize <= 12) { + GST_WARNING_OBJECT (demux, "'name' atom is too small, ignoring tag"); + return; + } + namestr = ((gchar *) name->data) + 12; + namesize -= 12; + + /* + * Data atom is: + * uint32 - size + * uint32 - name + * uint8 - version + * uint24 - data type + * uint32 - all 0 + * rest - the data + */ + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (!data) { + GST_WARNING_OBJECT (demux, "No data atom in this tag"); + return; + } + datasize = QT_UINT32 (data->data); + if (datasize <= 16) { + GST_WARNING_OBJECT (demux, "Data atom too small"); + return; + } + datatype = QT_UINT32 (((gchar *) data->data) + 8) & 0xFFFFFF; + + if ((strncmp (meanstr, "com.apple.iTunes", meansize) == 0) || + (strncmp (meanstr, "org.hydrogenaudio.replaygain", meansize) == 0)) { + static const struct + { + const gchar name[28]; + const gchar tag[28]; + } tags[] = { + { + "replaygain_track_gain", GST_TAG_TRACK_GAIN}, { + "replaygain_track_peak", GST_TAG_TRACK_PEAK}, { + "replaygain_album_gain", GST_TAG_ALBUM_GAIN}, { + "replaygain_album_peak", GST_TAG_ALBUM_PEAK}, { + "MusicBrainz Track Id", GST_TAG_MUSICBRAINZ_TRACKID}, { + "MusicBrainz Artist Id", GST_TAG_MUSICBRAINZ_ARTISTID}, { + "MusicBrainz Album Id", GST_TAG_MUSICBRAINZ_ALBUMID}, { + "MusicBrainz Album Artist Id", GST_TAG_MUSICBRAINZ_ALBUMARTISTID} + }; + int i; + + for (i = 0; i < G_N_ELEMENTS (tags); ++i) { + if (!g_ascii_strncasecmp (tags[i].name, namestr, namesize)) { + switch (gst_tag_get_type (tags[i].tag)) { + case G_TYPE_DOUBLE: + qtdemux_add_double_tag_from_str (demux, taglist, tags[i].tag, + ((guint8 *) data->data) + 16, datasize - 16); + break; + case G_TYPE_STRING: + qtdemux_tag_add_str (demux, taglist, tags[i].tag, NULL, node); + break; + default: + /* not reached */ + break; + } + break; + } + } + if (i == G_N_ELEMENTS (tags)) + goto unknown_tag; + } else { + goto unknown_tag; + } + + return; + +/* errors */ +unknown_tag: +#ifndef GST_DISABLE_GST_DEBUG + { + gchar *namestr_dbg; + gchar *meanstr_dbg; + + meanstr_dbg = g_strndup (meanstr, meansize); + namestr_dbg = g_strndup (namestr, namesize); + + GST_WARNING_OBJECT (demux, "This tag %s:%s type:%u is not mapped, " + "file a bug at bugzilla.gnome.org", meanstr_dbg, namestr_dbg, datatype); + + g_free (namestr_dbg); + g_free (meanstr_dbg); + } +#endif + return; +} + +static void +qtdemux_tag_add_id32 (GstQTDemux * demux, GstTagList * taglist, const char *tag, + const char *tag_bis, GNode * node) +{ + guint8 *data; + GstBuffer *buf; + guint len; + GstTagList *id32_taglist = NULL; + + GST_LOG_OBJECT (demux, "parsing ID32"); + + data = node->data; + len = GST_READ_UINT32_BE (data); + + /* need at least full box and language tag */ + if (len < 12 + 2) + return; + + buf = gst_buffer_new_allocate (NULL, len - 14, NULL); + gst_buffer_fill (buf, 0, data + 14, len - 14); + + id32_taglist = gst_tag_list_from_id3v2_tag (buf); + if (id32_taglist) { + GST_LOG_OBJECT (demux, "parsing ok"); + gst_tag_list_insert (taglist, id32_taglist, GST_TAG_MERGE_KEEP); + gst_tag_list_unref (id32_taglist); + } else { + GST_LOG_OBJECT (demux, "parsing failed"); + } + + gst_buffer_unref (buf); +} + +typedef void (*GstQTDemuxAddTagFunc) (GstQTDemux * demux, GstTagList * taglist, + const char *tag, const char *tag_bis, GNode * node); + +/* unmapped tags +FOURCC_pcst -> if media is a podcast -> bool +FOURCC_cpil -> if media is part of a compilation -> bool +FOURCC_pgap -> if media is part of a gapless context -> bool +FOURCC_tven -> the tv episode id e.g. S01E23 -> str +*/ + +static const struct +{ + guint32 fourcc; + const gchar *gst_tag; + const gchar *gst_tag_bis; + const GstQTDemuxAddTagFunc func; +} add_funcs[] = { + { + FOURCC__nam, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { + FOURCC_titl, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { + FOURCC__grp, GST_TAG_GROUPING, NULL, qtdemux_tag_add_str}, { + FOURCC__wrt, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { + FOURCC__ART, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_perf, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_auth, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { + FOURCC__alb, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { + FOURCC_albm, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { + FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { + FOURCC__cpy, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { + FOURCC__cmt, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { + FOURCC__des, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC_desc, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC__lyr, GST_TAG_LYRICS, NULL, qtdemux_tag_add_str}, { + FOURCC__day, GST_TAG_DATE, NULL, qtdemux_tag_add_date}, { + FOURCC_yrrc, GST_TAG_DATE, NULL, qtdemux_tag_add_year}, { + FOURCC__too, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { + FOURCC__inf, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { + FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, qtdemux_tag_add_num}, { + FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, + qtdemux_tag_add_num}, { + FOURCC_disc, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, + qtdemux_tag_add_num}, { + FOURCC__gen, GST_TAG_GENRE, NULL, qtdemux_tag_add_str}, { + FOURCC_gnre, GST_TAG_GENRE, NULL, qtdemux_tag_add_gnre}, { + FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, qtdemux_tag_add_tmpo}, { + FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, qtdemux_tag_add_covr}, { + FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, qtdemux_tag_add_str}, { + FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, qtdemux_tag_add_uint32}, { + FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, qtdemux_tag_add_uint32}, { + FOURCC_kywd, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_keywords}, { + FOURCC_keyw, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_str}, { + FOURCC__enc, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { + FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, qtdemux_tag_add_location}, { + FOURCC_clsf, GST_QT_DEMUX_CLASSIFICATION_TAG, NULL, + qtdemux_tag_add_classification}, { + FOURCC__mak, GST_TAG_DEVICE_MANUFACTURER, NULL, qtdemux_tag_add_str}, { + FOURCC__mod, GST_TAG_DEVICE_MODEL, NULL, qtdemux_tag_add_str}, { + FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, qtdemux_tag_add_str}, { + + /* This is a special case, some tags are stored in this + * 'reverse dns naming', according to: + * http://atomicparsley.sourceforge.net/mpeg-4files.html and + * bug #614471 + */ + FOURCC_____, "", NULL, qtdemux_tag_add_revdns}, { + /* see http://www.mp4ra.org/specs.html for ID32 in meta box */ + FOURCC_ID32, "", NULL, qtdemux_tag_add_id32} +}; + +struct _GstQtDemuxTagList +{ + GstQTDemux *demux; + GstTagList *taglist; +}; +typedef struct _GstQtDemuxTagList GstQtDemuxTagList; + +static void +qtdemux_tag_add_blob (GNode * node, GstQtDemuxTagList * qtdemuxtaglist) +{ + gint len; + guint8 *data; + GstBuffer *buf; + gchar *media_type; + const gchar *style; + GstSample *sample; + GstStructure *s; + guint i; + guint8 ndata[4]; + GstQTDemux *demux = qtdemuxtaglist->demux; + GstTagList *taglist = qtdemuxtaglist->taglist; + + data = node->data; + len = QT_UINT32 (data); + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, data, len); + + /* heuristic to determine style of tag */ + if (QT_FOURCC (data + 4) == FOURCC_____ || + (len > 8 + 12 && QT_FOURCC (data + 12) == FOURCC_data)) + style = "itunes"; + else if (demux->major_brand == FOURCC_qt__) + style = "quicktime"; + /* fall back to assuming iso/3gp tag style */ + else + style = "iso"; + + /* santize the name for the caps. */ + for (i = 0; i < 4; i++) { + guint8 d = data[4 + i]; + if (g_ascii_isalnum (d)) + ndata[i] = g_ascii_tolower (d); + else + ndata[i] = '_'; + } + + media_type = g_strdup_printf ("application/x-gst-qt-%c%c%c%c-tag", + ndata[0], ndata[1], ndata[2], ndata[3]); + GST_DEBUG_OBJECT (demux, "media type %s", media_type); + + s = gst_structure_new (media_type, "style", G_TYPE_STRING, style, NULL); + sample = gst_sample_new (buf, NULL, NULL, s); + gst_buffer_unref (buf); + g_free (media_type); + + GST_DEBUG_OBJECT (demux, "adding private tag; size %d, info %" GST_PTR_FORMAT, + len, s); + + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, + GST_QT_DEMUX_PRIVATE_TAG, sample, NULL); + + gst_sample_unref (sample); +} + +static void +qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, GNode * udta) +{ + GNode *meta; + GNode *ilst; + GNode *xmp_; + GNode *node; + gint i; + GstQtDemuxTagList demuxtaglist; + + demuxtaglist.demux = qtdemux; + demuxtaglist.taglist = taglist; + + meta = qtdemux_tree_get_child_by_type (udta, FOURCC_meta); + if (meta != NULL) { + ilst = qtdemux_tree_get_child_by_type (meta, FOURCC_ilst); + if (ilst == NULL) { + GST_LOG_OBJECT (qtdemux, "no ilst"); + return; + } + } else { + ilst = udta; + GST_LOG_OBJECT (qtdemux, "no meta so using udta itself"); + } + + i = 0; + while (i < G_N_ELEMENTS (add_funcs)) { + node = qtdemux_tree_get_child_by_type (ilst, add_funcs[i].fourcc); + if (node) { + gint len; + + len = QT_UINT32 (node->data); + if (len < 12) { + GST_DEBUG_OBJECT (qtdemux, "too small tag atom %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (add_funcs[i].fourcc)); + } else { + add_funcs[i].func (qtdemux, taglist, add_funcs[i].gst_tag, + add_funcs[i].gst_tag_bis, node); + } + g_node_destroy (node); + } else { + i++; + } + } + + /* parsed nodes have been removed, pass along remainder as blob */ + g_node_children_foreach (ilst, G_TRAVERSE_ALL, + (GNodeForeachFunc) qtdemux_tag_add_blob, &demuxtaglist); + + /* parse up XMP_ node if existing */ + xmp_ = qtdemux_tree_get_child_by_type (udta, FOURCC_XMP_); + if (xmp_ != NULL) { + GstBuffer *buf; + GstTagList *xmptaglist; + + buf = _gst_buffer_new_wrapped (((guint8 *) xmp_->data) + 8, + QT_UINT32 ((guint8 *) xmp_->data) - 8, NULL); + xmptaglist = gst_tag_list_from_xmp_buffer (buf); + gst_buffer_unref (buf); + + qtdemux_handle_xmp_taglist (qtdemux, taglist, xmptaglist); + } else { + GST_DEBUG_OBJECT (qtdemux, "No XMP_ node found"); + } +} + +typedef struct +{ + GstStructure *structure; /* helper for sort function */ + gchar *location; + guint min_req_bitrate; + guint min_req_qt_version; +} GstQtReference; + +static gint +qtdemux_redirects_sort_func (gconstpointer a, gconstpointer b) +{ + GstQtReference *ref_a = (GstQtReference *) a; + GstQtReference *ref_b = (GstQtReference *) b; + + if (ref_b->min_req_qt_version != ref_a->min_req_qt_version) + return ref_b->min_req_qt_version - ref_a->min_req_qt_version; + + /* known bitrates go before unknown; higher bitrates go first */ + return ref_b->min_req_bitrate - ref_a->min_req_bitrate; +} + +/* sort the redirects and post a message for the application. + */ +static void +qtdemux_process_redirects (GstQTDemux * qtdemux, GList * references) +{ + GstQtReference *best; + GstStructure *s; + GstMessage *msg; + GValue list_val = { 0, }; + GList *l; + + g_assert (references != NULL); + + references = g_list_sort (references, qtdemux_redirects_sort_func); + + best = (GstQtReference *) references->data; + + g_value_init (&list_val, GST_TYPE_LIST); + + for (l = references; l != NULL; l = l->next) { + GstQtReference *ref = (GstQtReference *) l->data; + GValue struct_val = { 0, }; + + ref->structure = gst_structure_new ("redirect", + "new-location", G_TYPE_STRING, ref->location, NULL); + + if (ref->min_req_bitrate > 0) { + gst_structure_set (ref->structure, "minimum-bitrate", G_TYPE_INT, + ref->min_req_bitrate, NULL); + } + + g_value_init (&struct_val, GST_TYPE_STRUCTURE); + g_value_set_boxed (&struct_val, ref->structure); + gst_value_list_append_value (&list_val, &struct_val); + g_value_unset (&struct_val); + /* don't free anything here yet, since we need best->structure below */ + } + + g_assert (best != NULL); + s = gst_structure_copy (best->structure); + + if (g_list_length (references) > 1) { + gst_structure_set_value (s, "locations", &list_val); + } + + g_value_unset (&list_val); + + for (l = references; l != NULL; l = l->next) { + GstQtReference *ref = (GstQtReference *) l->data; + + gst_structure_free (ref->structure); + g_free (ref->location); + g_free (ref); + } + g_list_free (references); + + GST_INFO_OBJECT (qtdemux, "posting redirect message: %" GST_PTR_FORMAT, s); + msg = gst_message_new_element (GST_OBJECT_CAST (qtdemux), s); + gst_element_post_message (GST_ELEMENT_CAST (qtdemux), msg); + qtdemux->posted_redirect = TRUE; +} + +/* look for redirect nodes, collect all redirect information and + * process it. + */ +static gboolean +qtdemux_parse_redirects (GstQTDemux * qtdemux) +{ + GNode *rmra, *rmda, *rdrf; + + rmra = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_rmra); + if (rmra) { + GList *redirects = NULL; + + rmda = qtdemux_tree_get_child_by_type (rmra, FOURCC_rmda); + while (rmda) { + GstQtReference ref = { NULL, NULL, 0, 0 }; + GNode *rmdr, *rmvc; + + if ((rmdr = qtdemux_tree_get_child_by_type (rmda, FOURCC_rmdr))) { + ref.min_req_bitrate = QT_UINT32 ((guint8 *) rmdr->data + 12); + GST_LOG_OBJECT (qtdemux, "data rate atom, required bitrate = %u", + ref.min_req_bitrate); + } + + if ((rmvc = qtdemux_tree_get_child_by_type (rmda, FOURCC_rmvc))) { + guint32 package = QT_FOURCC ((guint8 *) rmvc->data + 12); + guint version = QT_UINT32 ((guint8 *) rmvc->data + 16); + +#ifndef GST_DISABLE_GST_DEBUG + guint bitmask = QT_UINT32 ((guint8 *) rmvc->data + 20); +#endif + guint check_type = QT_UINT16 ((guint8 *) rmvc->data + 24); + + GST_LOG_OBJECT (qtdemux, + "version check atom [%" GST_FOURCC_FORMAT "], version=0x%08x" + ", mask=%08x, check_type=%u", GST_FOURCC_ARGS (package), version, + bitmask, check_type); + if (package == FOURCC_qtim && check_type == 0) { + ref.min_req_qt_version = version; + } + } + + rdrf = qtdemux_tree_get_child_by_type (rmda, FOURCC_rdrf); + if (rdrf) { + guint32 ref_type; + guint8 *ref_data; + guint ref_len; + + ref_len = QT_UINT32 ((guint8 *) rdrf->data); + if (ref_len > 20) { + ref_type = QT_FOURCC ((guint8 *) rdrf->data + 12); + ref_data = (guint8 *) rdrf->data + 20; + if (ref_type == FOURCC_alis) { + guint record_len, record_version, fn_len; + + if (ref_len > 70) { + /* MacOSX alias record, google for alias-layout.txt */ + record_len = QT_UINT16 (ref_data + 4); + record_version = QT_UINT16 (ref_data + 4 + 2); + fn_len = QT_UINT8 (ref_data + 50); + if (record_len > 50 && record_version == 2 && fn_len > 0) { + ref.location = g_strndup ((gchar *) ref_data + 51, fn_len); + } + } else { + GST_WARNING_OBJECT (qtdemux, "Invalid rdrf/alis size (%u < 70)", + ref_len); + } + } else if (ref_type == FOURCC_url_) { + ref.location = g_strndup ((gchar *) ref_data, ref_len - 8); + } else { + GST_DEBUG_OBJECT (qtdemux, + "unknown rdrf reference type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (ref_type)); + } + if (ref.location != NULL) { + GST_INFO_OBJECT (qtdemux, "New location: %s", ref.location); + redirects = + g_list_prepend (redirects, g_memdup (&ref, sizeof (ref))); + } else { + GST_WARNING_OBJECT (qtdemux, + "Failed to extract redirect location from rdrf atom"); + } + } else { + GST_WARNING_OBJECT (qtdemux, "Invalid rdrf size (%u < 20)", ref_len); + } + } + + /* look for others */ + rmda = qtdemux_tree_get_sibling_by_type (rmda, FOURCC_rmda); + } + + if (redirects != NULL) { + qtdemux_process_redirects (qtdemux, redirects); + } + } + return TRUE; +} + +static GstTagList * +qtdemux_add_container_format (GstQTDemux * qtdemux, GstTagList * tags) +{ + const gchar *fmt; + + if (tags == NULL) { + tags = gst_tag_list_new_empty (); + gst_tag_list_set_scope (tags, GST_TAG_SCOPE_GLOBAL); + } + + if (qtdemux->major_brand == FOURCC_mjp2) + fmt = "Motion JPEG 2000"; + else if ((qtdemux->major_brand & 0xffff) == FOURCC_3g__) + fmt = "3GP"; + else if (qtdemux->major_brand == FOURCC_qt__) + fmt = "Quicktime"; + else if (qtdemux->fragmented) + fmt = "ISO fMP4"; + else + fmt = "ISO MP4/M4A"; + + GST_LOG_OBJECT (qtdemux, "mapped %" GST_FOURCC_FORMAT " to '%s'", + GST_FOURCC_ARGS (qtdemux->major_brand), fmt); + + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CONTAINER_FORMAT, + fmt, NULL); + + return tags; +} + +/* we have read th complete moov node now. + * This function parses all of the relevant info, creates the traks and + * prepares all data structures for playback + */ +static gboolean +qtdemux_parse_tree (GstQTDemux * qtdemux) +{ + GNode *mvhd; + GNode *trak; + GNode *udta; + GNode *mvex; + GstClockTime duration; + GNode *pssh; + guint64 creation_time; + GstDateTime *datetime = NULL; + gint version; + + /* make sure we have a usable taglist */ + if (!qtdemux->tag_list) { + qtdemux->tag_list = gst_tag_list_new_empty (); + gst_tag_list_set_scope (qtdemux->tag_list, GST_TAG_SCOPE_GLOBAL); + } else { + qtdemux->tag_list = gst_tag_list_make_writable (qtdemux->tag_list); + } + + mvhd = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_mvhd); + if (mvhd == NULL) { + GST_LOG_OBJECT (qtdemux, "No mvhd node found, looking for redirects."); + return qtdemux_parse_redirects (qtdemux); + } + + version = QT_UINT8 ((guint8 *) mvhd->data + 8); + if (version == 1) { + creation_time = QT_UINT64 ((guint8 *) mvhd->data + 12); + qtdemux->timescale = QT_UINT32 ((guint8 *) mvhd->data + 28); + qtdemux->duration = QT_UINT64 ((guint8 *) mvhd->data + 32); + } else if (version == 0) { + creation_time = QT_UINT32 ((guint8 *) mvhd->data + 12); + qtdemux->timescale = QT_UINT32 ((guint8 *) mvhd->data + 20); + qtdemux->duration = QT_UINT32 ((guint8 *) mvhd->data + 24); + } else { + GST_WARNING_OBJECT (qtdemux, "Unhandled mvhd version %d", version); + return FALSE; + } + + /* Moving qt creation time (secs since 1904) to unix time */ + if (creation_time != 0) { + /* Try to use epoch first as it should be faster and more commonly found */ + if (creation_time >= QTDEMUX_SECONDS_FROM_1904_TO_1970) { + GTimeVal now; + + creation_time -= QTDEMUX_SECONDS_FROM_1904_TO_1970; + /* some data cleansing sanity */ + g_get_current_time (&now); + if (now.tv_sec + 24 * 3600 < creation_time) { + GST_DEBUG_OBJECT (qtdemux, "discarding bogus future creation time"); + } else { + datetime = gst_date_time_new_from_unix_epoch_utc (creation_time); + } + } else { + GDateTime *base_dt = g_date_time_new_utc (1904, 1, 1, 0, 0, 0); + GDateTime *dt, *dt_local; + + dt = g_date_time_add_seconds (base_dt, creation_time); + dt_local = g_date_time_to_local (dt); + datetime = gst_date_time_new_from_g_date_time (dt_local); + + g_date_time_unref (base_dt); + g_date_time_unref (dt); + } + } + if (datetime) { + /* Use KEEP as explicit tags should have a higher priority than mvhd tag */ + gst_tag_list_add (qtdemux->tag_list, GST_TAG_MERGE_KEEP, GST_TAG_DATE_TIME, + datetime, NULL); + gst_date_time_unref (datetime); + } + + GST_INFO_OBJECT (qtdemux, "timescale: %u", qtdemux->timescale); + GST_INFO_OBJECT (qtdemux, "duration: %" G_GUINT64_FORMAT, qtdemux->duration); + + /* check for fragmented file and get some (default) data */ + mvex = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_mvex); + if (mvex) { + GNode *mehd; + GstByteReader mehd_data; + + /* let track parsing or anyone know weird stuff might happen ... */ + qtdemux->fragmented = TRUE; + + /* compensate for total duration */ + mehd = qtdemux_tree_get_child_by_type_full (mvex, FOURCC_mehd, &mehd_data); + if (mehd) + qtdemux_parse_mehd (qtdemux, &mehd_data); + } + + /* set duration in the segment info */ + gst_qtdemux_get_duration (qtdemux, &duration); + if (duration) { + qtdemux->segment.duration = duration; + /* also do not exceed duration; stop is set that way post seek anyway, + * and segment activation falls back to duration, + * whereas loop only checks stop, so let's align this here as well */ + qtdemux->segment.stop = duration; + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + if (QTDEMUX_IS_CUSTOM_MODE (qtdemux)) { + qtdemux->segment.stop = -1; + } +#endif + } + + /* parse all traks */ + trak = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_trak); + while (trak) { + qtdemux_parse_trak (qtdemux, trak); + /* iterate all siblings */ + trak = qtdemux_tree_get_sibling_by_type (trak, FOURCC_trak); + } + + if (!qtdemux->tag_list) { + GST_DEBUG_OBJECT (qtdemux, "new tag list"); + qtdemux->tag_list = gst_tag_list_new_empty (); + gst_tag_list_set_scope (qtdemux->tag_list, GST_TAG_SCOPE_GLOBAL); + } else { + qtdemux->tag_list = gst_tag_list_make_writable (qtdemux->tag_list); + } + + /* find tags */ + udta = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_udta); + if (udta) { + qtdemux_parse_udta (qtdemux, qtdemux->tag_list, udta); + } else { + GST_LOG_OBJECT (qtdemux, "No udta node found."); + } + + /* maybe also some tags in meta box */ + udta = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_meta); + if (udta) { + GST_DEBUG_OBJECT (qtdemux, "Parsing meta box for tags."); + qtdemux_parse_udta (qtdemux, qtdemux->tag_list, udta); + } else { + GST_LOG_OBJECT (qtdemux, "No meta node found."); + } + + /* parse any protection system info */ + pssh = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_pssh); + while (pssh) { + GST_LOG_OBJECT (qtdemux, "Parsing pssh box."); + qtdemux_parse_pssh (qtdemux, pssh); + pssh = qtdemux_tree_get_sibling_by_type (pssh, FOURCC_pssh); + } + + qtdemux->tag_list = qtdemux_add_container_format (qtdemux, qtdemux->tag_list); + + return TRUE; +} + +/* taken from ffmpeg */ +static int +read_descr_size (guint8 * ptr, guint8 * end, guint8 ** end_out) +{ + int count = 4; + int len = 0; + + while (count--) { + int c; + + if (ptr >= end) + return -1; + + c = *ptr++; + len = (len << 7) | (c & 0x7f); + if (!(c & 0x80)) + break; + } + *end_out = ptr; + return len; +} + +static void +qtdemux_set_custom_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + GstCaps * caps) +{ + char *s, fourstr[5]; + const char *container; + + g_return_if_fail (stream != NULL); + g_return_if_fail (stream->fourcc); + g_return_if_fail (caps != NULL); + + caps = gst_caps_make_writable (caps); + if (qtdemux->major_brand == FOURCC_mjp2) + container = "Motion JPEG 2000"; + else if ((qtdemux->major_brand & 0xffff) == GST_MAKE_FOURCC ('3', 'g', 0, 0)) + container = "3GP"; + else if (qtdemux->major_brand == FOURCC_qt__) + container = "Quicktime"; + else if (qtdemux->fragmented) + container = "ISO fMP4"; + else + container = "ISO MP4/M4A"; + + GST_DEBUG_OBJECT (qtdemux, "fourcc %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->fourcc)); + + g_snprintf (fourstr, 5, "%" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->fourcc)); + s = g_strdup_printf ("%s", g_strstrip (fourstr)); + gst_caps_set_simple (caps, "container", G_TYPE_STRING, container, + "format", G_TYPE_STRING, s, "timestamptype", G_TYPE_BOOLEAN, TRUE, NULL); + + g_free (s); +} + +/* this can change the codec originally present in @list */ +static void +gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, + GNode * esds, GstTagList * list) +{ + int len = QT_UINT32 (esds->data); + guint8 *ptr = esds->data; + guint8 *end = ptr + len; + int tag; + guint8 *data_ptr = NULL; + int data_len = 0; + guint8 object_type_id = 0; + const char *codec_name = NULL; + GstCaps *caps = NULL; + + GST_MEMDUMP_OBJECT (qtdemux, "esds", ptr, len); + ptr += 8; + GST_DEBUG_OBJECT (qtdemux, "version/flags = %08x", QT_UINT32 (ptr)); + ptr += 4; + while (ptr + 1 < end) { + tag = QT_UINT8 (ptr); + GST_DEBUG_OBJECT (qtdemux, "tag = %02x", tag); + ptr++; + len = read_descr_size (ptr, end, &ptr); + GST_DEBUG_OBJECT (qtdemux, "len = %d", len); + + /* Check the stated amount of data is available for reading */ + if (len < 0 || ptr + len > end) + break; + + switch (tag) { + case ES_DESCRIPTOR_TAG: + GST_DEBUG_OBJECT (qtdemux, "ID %04x", QT_UINT16 (ptr)); + GST_DEBUG_OBJECT (qtdemux, "priority %04x", QT_UINT8 (ptr + 2)); + ptr += 3; + break; + case DECODER_CONFIG_DESC_TAG:{ + guint max_bitrate, avg_bitrate; + + object_type_id = QT_UINT8 (ptr); + max_bitrate = QT_UINT32 (ptr + 5); + avg_bitrate = QT_UINT32 (ptr + 9); + GST_DEBUG_OBJECT (qtdemux, "object_type_id %02x", object_type_id); + GST_DEBUG_OBJECT (qtdemux, "stream_type %02x", QT_UINT8 (ptr + 1)); + GST_DEBUG_OBJECT (qtdemux, "buffer_size_db %02x", QT_UINT24 (ptr + 2)); + GST_DEBUG_OBJECT (qtdemux, "max bitrate %u", max_bitrate); + GST_DEBUG_OBJECT (qtdemux, "avg bitrate %u", avg_bitrate); + if (max_bitrate > 0 && max_bitrate < G_MAXUINT32) { + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, max_bitrate, NULL); + } + if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32) { + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE, + avg_bitrate, NULL); + } + ptr += 13; + break; + } + case DECODER_SPECIFIC_INFO_TAG: + GST_MEMDUMP_OBJECT (qtdemux, "data", ptr, len); + if (object_type_id == 0xe0 && len == 0x40) { + guint8 *data; + GstStructure *s; + guint32 clut[16]; + gint i; + + GST_DEBUG_OBJECT (qtdemux, + "Have VOBSUB palette. Creating palette event"); + /* move to decConfigDescr data and read palette */ + data = ptr; + for (i = 0; i < 16; i++) { + clut[i] = QT_UINT32 (data); + data += 4; + } + + s = gst_structure_new ("application/x-gst-dvd", "event", + G_TYPE_STRING, "dvd-spu-clut-change", + "clut00", G_TYPE_INT, clut[0], "clut01", G_TYPE_INT, clut[1], + "clut02", G_TYPE_INT, clut[2], "clut03", G_TYPE_INT, clut[3], + "clut04", G_TYPE_INT, clut[4], "clut05", G_TYPE_INT, clut[5], + "clut06", G_TYPE_INT, clut[6], "clut07", G_TYPE_INT, clut[7], + "clut08", G_TYPE_INT, clut[8], "clut09", G_TYPE_INT, clut[9], + "clut10", G_TYPE_INT, clut[10], "clut11", G_TYPE_INT, clut[11], + "clut12", G_TYPE_INT, clut[12], "clut13", G_TYPE_INT, clut[13], + "clut14", G_TYPE_INT, clut[14], "clut15", G_TYPE_INT, clut[15], + NULL); + + /* store event and trigger custom processing */ + stream->pending_event = + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, s); + } else { + /* Generic codec_data handler puts it on the caps */ + data_ptr = ptr; + data_len = len; + } + + ptr += len; + break; + case SL_CONFIG_DESC_TAG: + GST_DEBUG_OBJECT (qtdemux, "data %02x", QT_UINT8 (ptr)); + ptr += 1; + break; + default: + GST_DEBUG_OBJECT (qtdemux, "Unknown/unhandled descriptor tag %02x", + tag); + GST_MEMDUMP_OBJECT (qtdemux, "descriptor data", ptr, len); + ptr += len; + break; + } + } + + /* object_type_id in the esds atom in mp4a and mp4v tells us which codec is + * in use, and should also be used to override some other parameters for some + * codecs. */ + switch (object_type_id) { + case 0x20: /* MPEG-4 */ + /* 4 bytes for the visual_object_sequence_start_code and 1 byte for the + * profile_and_level_indication */ + if (data_ptr != NULL && data_len >= 5 && + GST_READ_UINT32_BE (data_ptr) == 0x000001b0) { + gst_codec_utils_mpeg4video_caps_set_level_and_profile (stream->caps, + data_ptr + 4, data_len - 4); + } + break; /* Nothing special needed here */ + case 0x21: /* H.264 */ + codec_name = "H.264 / AVC"; + caps = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "avc", + "alignment", G_TYPE_STRING, "au", NULL); + break; + case 0x40: /* AAC (any) */ + case 0x66: /* AAC Main */ + case 0x67: /* AAC LC */ + case 0x68: /* AAC SSR */ + /* Override channels and rate based on the codec_data, as it's often + * wrong. */ + /* Only do so for basic setup without HE-AAC extension */ + if (data_ptr && data_len >= 2) { + guint channels, rateindex, rate; + + /* FIXME: add gst_codec_utils_aac_get_{channels|sample_rate}()? */ + channels = (data_ptr[1] & 0x7f) >> 3; + if (channels > 0 && channels < 7) { + stream->n_channels = channels; + } else if (channels == 7) { + stream->n_channels = 8; + } + + rateindex = ((data_ptr[0] & 0x7) << 1) | ((data_ptr[1] & 0x80) >> 7); + rate = gst_codec_utils_aac_get_sample_rate_from_index (rateindex); + if (rate > 0) + stream->rate = rate; + + /* Set level and profile if possible */ + gst_codec_utils_aac_caps_set_level_and_profile (stream->caps, + data_ptr, data_len); + } + break; + case 0x60: /* MPEG-2, various profiles */ + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + codec_name = "MPEG-2 video"; + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case 0x69: /* MPEG-2 BC audio */ + case 0x6B: /* MPEG-1 audio */ + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + codec_name = "MPEG-1 audio"; + break; + case 0x6A: /* MPEG-1 */ + codec_name = "MPEG-1 video"; + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 1, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case 0x6C: /* MJPEG */ + caps = + gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE, + NULL); + codec_name = "Motion-JPEG"; + break; + case 0x6D: /* PNG */ + caps = + gst_caps_new_simple ("image/png", "parsed", G_TYPE_BOOLEAN, TRUE, + NULL); + codec_name = "PNG still images"; + break; + case 0x6E: /* JPEG2000 */ + codec_name = "JPEG-2000"; + caps = gst_caps_new_simple ("image/x-j2c", "fields", G_TYPE_INT, 1, NULL); + break; + case 0xA4: /* Dirac */ + codec_name = "Dirac"; + caps = gst_caps_new_empty_simple ("video/x-dirac"); + break; + case 0xA5: /* AC3 */ + codec_name = "AC-3 audio"; + caps = gst_caps_new_simple ("audio/x-ac3", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + break; + case 0xE1: /* QCELP */ + /* QCELP, the codec_data is a riff tag (little endian) with + * more info (http://ftp.3gpp2.org/TSGC/Working/2003/2003-05-SanDiego/TSG-C-2003-05-San%20Diego/WG1/SWG12/C12-20030512-006%20=%20C12-20030217-015_Draft_Baseline%20Text%20of%20FFMS_R2.doc). */ + caps = gst_caps_new_empty_simple ("audio/qcelp"); + codec_name = "QCELP"; + break; + case 0xdd: + /* test file for vorbis... this value is not defined + exactly in ISO14496-1 So, I think that we need to send mesage + "no audio" to app. */ + GST_DEBUG_OBJECT (qtdemux, "vorbis"); + caps = gst_caps_new_empty_simple ("audio/x-unknown"); + codec_name = "unknown"; + break; + default: + break; + } + + /* If we have a replacement caps, then change our caps for this stream */ + if (caps) { + gst_caps_unref (stream->caps); + stream->caps = caps; + + if (stream->subtype == FOURCC_vide) + qtdemux_set_custom_video_caps (qtdemux, stream, stream->caps); + } + + if (codec_name && list) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_AUDIO_CODEC, codec_name, NULL); + + /* Add the codec_data attribute to caps, if we have it */ + if (data_ptr) { + GstBuffer *buffer; + + buffer = gst_buffer_new_and_alloc (data_len); + gst_buffer_fill (buffer, 0, data_ptr, data_len); + + GST_DEBUG_OBJECT (qtdemux, "setting codec_data from esds"); + GST_MEMDUMP_OBJECT (qtdemux, "codec_data from esds", data_ptr, data_len); + + gst_caps_set_simple (stream->caps, "codec_data", GST_TYPE_BUFFER, + buffer, NULL); + gst_buffer_unref (buffer); + } + +} + +#define _codec(name) \ + do { \ + if (codec_name) { \ + *codec_name = g_strdup (name); \ + } \ + } while (0) + +static GstCaps * +qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name) +{ + GstCaps *caps = NULL; + GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; + + switch (fourcc) { + case GST_MAKE_FOURCC ('p', 'n', 'g', ' '): + _codec ("PNG still images"); + caps = gst_caps_new_empty_simple ("image/png"); + break; + case FOURCC_jpeg: + /* fourcc is JPEG even if the file codec is Motion Jpeg so, detec it with + duration and codec name is Motion-JPEG. 2012-09-26 */ + if (stream->duration) + _codec ("Motion-JPEG"); + else + _codec ("JPEG still images"); + caps = + gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE, + NULL); + break; + case GST_MAKE_FOURCC ('m', 'j', 'p', 'a'): + case GST_MAKE_FOURCC ('A', 'V', 'D', 'J'): + case GST_MAKE_FOURCC ('M', 'J', 'P', 'G'): + case GST_MAKE_FOURCC ('d', 'm', 'b', '1'): + _codec ("Motion-JPEG"); + caps = + gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE, + NULL); + break; + case GST_MAKE_FOURCC ('m', 'j', 'p', 'b'): + _codec ("Motion-JPEG format B"); + caps = gst_caps_new_empty_simple ("video/x-mjpeg-b"); + break; + case FOURCC_mjp2: + _codec ("JPEG-2000"); + /* override to what it should be according to spec, avoid palette_data */ + stream->bits_per_sample = 24; + caps = gst_caps_new_simple ("image/x-j2c", "fields", G_TYPE_INT, 1, NULL); + break; + case FOURCC_SVQ3: + _codec ("Sorensen video v.3"); + caps = gst_caps_new_simple ("video/x-svq", + "svqversion", G_TYPE_INT, 3, NULL); + break; + case GST_MAKE_FOURCC ('s', 'v', 'q', 'i'): + case GST_MAKE_FOURCC ('S', 'V', 'Q', '1'): + _codec ("Sorensen video v.1"); + caps = gst_caps_new_simple ("video/x-svq", + "svqversion", G_TYPE_INT, 1, NULL); + break; + case GST_MAKE_FOURCC ('W', 'R', 'A', 'W'): + caps = gst_caps_new_empty_simple ("video/x-raw"); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, "RGB8P", NULL); + _codec ("Windows Raw RGB"); + break; + case FOURCC_raw_: + { + guint16 bps; + + bps = QT_UINT16 (stsd_entry_data + 82); + switch (bps) { + case 15: + format = GST_VIDEO_FORMAT_RGB15; + break; + case 16: + format = GST_VIDEO_FORMAT_RGB16; + break; + case 24: + format = GST_VIDEO_FORMAT_RGB; + break; + case 32: + format = GST_VIDEO_FORMAT_ARGB; + break; + default: + /* unknown */ + break; + } + break; + } + case GST_MAKE_FOURCC ('y', 'v', '1', '2'): + format = GST_VIDEO_FORMAT_I420; + break; + case GST_MAKE_FOURCC ('y', 'u', 'v', '2'): + case GST_MAKE_FOURCC ('Y', 'u', 'v', '2'): + format = GST_VIDEO_FORMAT_I420; + break; + case FOURCC_2vuy: + case GST_MAKE_FOURCC ('2', 'V', 'u', 'y'): + format = GST_VIDEO_FORMAT_UYVY; + break; + case GST_MAKE_FOURCC ('v', '3', '0', '8'): + format = GST_VIDEO_FORMAT_v308; + break; + case GST_MAKE_FOURCC ('v', '2', '1', '6'): + format = GST_VIDEO_FORMAT_v216; + break; + case FOURCC_v210: + format = GST_VIDEO_FORMAT_v210; + break; + case GST_MAKE_FOURCC ('r', '2', '1', '0'): + format = GST_VIDEO_FORMAT_r210; + break; + /* Packed YUV 4:4:4 10 bit in 32 bits, complex + case GST_MAKE_FOURCC ('v', '4', '1', '0'): + format = GST_VIDEO_FORMAT_v410; + break; + */ + /* Packed YUV 4:4:4:4 8 bit in 32 bits + * but different order than AYUV + case GST_MAKE_FOURCC ('v', '4', '0', '8'): + format = GST_VIDEO_FORMAT_v408; + break; + */ + case GST_MAKE_FOURCC ('m', 'p', 'e', 'g'): + case GST_MAKE_FOURCC ('m', 'p', 'g', '1'): + _codec ("MPEG-1 video"); + caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 1, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case GST_MAKE_FOURCC ('m', '2', 'v', '1'): /* MPEG2 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '1'): /* HDV 720p30 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '2'): /* HDV 1080i60 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '3'): /* HDV 1080i50 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '4'): /* HDV 720p24 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '5'): /* HDV 720p25 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '6'): /* HDV 1080p24 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '7'): /* HDV 1080p25 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '8'): /* HDV 1080p30 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', '9'): /* HDV 720p60 */ + case GST_MAKE_FOURCC ('h', 'd', 'v', 'a'): /* HDV 720p50 */ + case GST_MAKE_FOURCC ('m', 'x', '5', 'n'): /* MPEG2 IMX NTSC 525/60 50mb/s produced by FCP */ + case GST_MAKE_FOURCC ('m', 'x', '5', 'p'): /* MPEG2 IMX PAL 625/60 50mb/s produced by FCP */ + case GST_MAKE_FOURCC ('m', 'x', '4', 'n'): /* MPEG2 IMX NTSC 525/60 40mb/s produced by FCP */ + case GST_MAKE_FOURCC ('m', 'x', '4', 'p'): /* MPEG2 IMX PAL 625/60 40mb/s produced by FCP */ + case GST_MAKE_FOURCC ('m', 'x', '3', 'n'): /* MPEG2 IMX NTSC 525/60 30mb/s produced by FCP */ + case GST_MAKE_FOURCC ('m', 'x', '3', 'p'): /* MPEG2 IMX PAL 625/50 30mb/s produced by FCP */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '1'): /* XDCAM HD 720p30 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '2'): /* XDCAM HD 1080i60 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '3'): /* XDCAM HD 1080i50 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '4'): /* XDCAM HD 720p24 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '5'): /* XDCAM HD 720p25 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '6'): /* XDCAM HD 1080p24 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '7'): /* XDCAM HD 1080p25 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '8'): /* XDCAM HD 1080p30 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', '9'): /* XDCAM HD 720p60 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'a'): /* XDCAM HD 720p50 35Mb/s */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'b'): /* XDCAM EX 1080i60 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'c'): /* XDCAM EX 1080i50 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'd'): /* XDCAM HD 1080p24 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'e'): /* XDCAM HD 1080p25 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', 'v', 'f'): /* XDCAM HD 1080p30 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', '1'): /* XDCAM HD422 720p30 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', '4'): /* XDCAM HD422 720p24 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', '5'): /* XDCAM HD422 720p25 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', '9'): /* XDCAM HD422 720p60 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'a'): /* XDCAM HD422 720p50 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'b'): /* XDCAM HD422 1080i50 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'c'): /* XDCAM HD422 1080i50 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'd'): /* XDCAM HD422 1080p24 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'e'): /* XDCAM HD422 1080p25 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', '5', 'f'): /* XDCAM HD422 1080p30 50Mb/s CBR */ + case GST_MAKE_FOURCC ('x', 'd', 'h', 'd'): /* XDCAM HD 540p */ + case GST_MAKE_FOURCC ('x', 'd', 'h', '2'): /* XDCAM HD422 540p */ + case GST_MAKE_FOURCC ('A', 'V', 'm', 'p'): /* AVID IMX PAL */ + case GST_MAKE_FOURCC ('m', 'p', 'g', '2'): /* AVID IMX PAL */ + case GST_MAKE_FOURCC ('m', 'p', '2', 'v'): /* AVID IMX PAL */ + _codec ("MPEG-2 video"); + caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case GST_MAKE_FOURCC ('g', 'i', 'f', ' '): + _codec ("GIF still images"); + caps = gst_caps_new_empty_simple ("image/gif"); + break; + case FOURCC_h263: + case GST_MAKE_FOURCC ('H', '2', '6', '3'): + case FOURCC_s263: + case GST_MAKE_FOURCC ('U', '2', '6', '3'): + _codec ("H.263"); + /* ffmpeg uses the height/width props, don't know why */ + caps = gst_caps_new_simple ("video/x-h263", + "variant", G_TYPE_STRING, "itu", NULL); + break; + case FOURCC_mp4v: + case FOURCC_MP4V: + _codec ("MPEG-4 video"); + caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 4, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case GST_MAKE_FOURCC ('3', 'i', 'v', 'd'): + case GST_MAKE_FOURCC ('3', 'I', 'V', 'D'): + _codec ("Microsoft MPEG-4 4.3"); /* FIXME? */ + caps = gst_caps_new_simple ("video/x-msmpeg", + "msmpegversion", G_TYPE_INT, 43, NULL); + break; + case GST_MAKE_FOURCC ('D', 'I', 'V', '3'): + _codec ("XVID"); + caps = gst_caps_new_empty_simple ("video/mpeg"); + /* translate the fourcc divx to xvid */ + fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D'); + break; + case GST_MAKE_FOURCC ('F', 'F', 'V', '1'): + _codec ("FFV1"); + caps = gst_caps_new_simple ("video/x-ffv", + "ffvversion", G_TYPE_INT, 1, NULL); + break; + case GST_MAKE_FOURCC ('D', 'I', 'V', 'X'): + case GST_MAKE_FOURCC ('d', 'i', 'v', 'x'): + case GST_MAKE_FOURCC ('D', 'X', '5', '0'): + case GST_MAKE_FOURCC ('X', 'V', 'I', 'D'): + case GST_MAKE_FOURCC ('x', 'v', 'i', 'd'): + GST_DEBUG_OBJECT (qtdemux, "XVID"); + _codec ("XVID"); + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, NULL); + /* translate the fourcc divx to xvid */ + fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D'); + break; + case FOURCC_FMP4: + case GST_MAKE_FOURCC ('3', 'I', 'V', '1'): + case GST_MAKE_FOURCC ('3', 'I', 'V', '2'): + case GST_MAKE_FOURCC ('U', 'M', 'P', '4'): + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, NULL); + _codec ("MPEG-4"); + break; + + case GST_MAKE_FOURCC ('c', 'v', 'i', 'd'): + _codec ("Cinepak"); + caps = gst_caps_new_empty_simple ("video/x-cinepak"); + break; + case GST_MAKE_FOURCC ('q', 'd', 'r', 'w'): + _codec ("Apple QuickDraw"); + caps = gst_caps_new_empty_simple ("video/x-qdrw"); + break; + case GST_MAKE_FOURCC ('r', 'p', 'z', 'a'): + _codec ("Apple video"); + caps = gst_caps_new_empty_simple ("video/x-apple-video"); + break; + case FOURCC_H264: + case FOURCC_avc1: + case GST_MAKE_FOURCC ('d', 'v', 'a', 'v'): + _codec ("H.264 / AVC"); + caps = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "avc", + "alignment", G_TYPE_STRING, "au", NULL); + break; + case FOURCC_avc3: + _codec ("H.264 / AVC"); + caps = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "avc3", + "alignment", G_TYPE_STRING, "au", NULL); + break; + case FOURCC_H265: + case FOURCC_hvc1: + _codec ("H.265 / HEVC"); + caps = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "hvc1", + "alignment", G_TYPE_STRING, "au", NULL); + break; + case FOURCC_hev1: + case GST_MAKE_FOURCC ('d', 'v', 'h', 'e'): + _codec ("H.265 / HEVC"); + caps = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "hev1", + "alignment", G_TYPE_STRING, "au", NULL); + break; + case FOURCC_rle_: + _codec ("Run-length encoding"); + caps = gst_caps_new_simple ("video/x-rle", + "layout", G_TYPE_STRING, "quicktime", NULL); + break; + case FOURCC_WRLE: + _codec ("Run-length encoding"); + caps = gst_caps_new_simple ("video/x-rle", + "layout", G_TYPE_STRING, "microsoft", NULL); + break; + case GST_MAKE_FOURCC ('I', 'V', '3', '2'): + case GST_MAKE_FOURCC ('i', 'v', '3', '2'): + _codec ("Indeo Video 3"); + caps = gst_caps_new_simple ("video/x-indeo", + "indeoversion", G_TYPE_INT, 3, NULL); + break; + case GST_MAKE_FOURCC ('I', 'V', '4', '1'): + case GST_MAKE_FOURCC ('i', 'v', '4', '1'): + _codec ("Intel Video 4"); + caps = gst_caps_new_simple ("video/x-indeo", + "indeoversion", G_TYPE_INT, 4, NULL); + break; + case FOURCC_dvcp: + case FOURCC_dvc_: + case GST_MAKE_FOURCC ('d', 'v', 's', 'd'): + case GST_MAKE_FOURCC ('D', 'V', 'S', 'D'): + case GST_MAKE_FOURCC ('d', 'v', 'c', 's'): + case GST_MAKE_FOURCC ('D', 'V', 'C', 'S'): + case GST_MAKE_FOURCC ('d', 'v', '2', '5'): + case GST_MAKE_FOURCC ('d', 'v', 'p', 'p'): + _codec ("DV Video"); + caps = gst_caps_new_simple ("video/x-dv", "dvversion", G_TYPE_INT, 25, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case FOURCC_dv5n: /* DVCPRO50 NTSC */ + case FOURCC_dv5p: /* DVCPRO50 PAL */ + _codec ("DVCPro50 Video"); + caps = gst_caps_new_simple ("video/x-dv", "dvversion", G_TYPE_INT, 50, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case GST_MAKE_FOURCC ('d', 'v', 'h', '5'): /* DVCPRO HD 50i produced by FCP */ + case GST_MAKE_FOURCC ('d', 'v', 'h', '6'): /* DVCPRO HD 60i produced by FCP */ + _codec ("DVCProHD Video"); + caps = gst_caps_new_simple ("video/x-dv", "dvversion", G_TYPE_INT, 100, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case GST_MAKE_FOURCC ('s', 'm', 'c', ' '): + _codec ("Apple Graphics (SMC)"); + caps = gst_caps_new_empty_simple ("video/x-smc"); + break; + case GST_MAKE_FOURCC ('V', 'P', '3', '1'): + _codec ("VP3"); + caps = gst_caps_new_empty_simple ("video/x-vp3"); + break; + case GST_MAKE_FOURCC ('V', 'P', '6', 'F'): + _codec ("VP6 Flash"); + caps = gst_caps_new_empty_simple ("video/x-vp6-flash"); + break; + case FOURCC_XiTh: + _codec ("Theora"); + caps = gst_caps_new_empty_simple ("video/x-theora"); + /* theora uses one byte of padding in the data stream because it does not + * allow 0 sized packets while theora does */ + stream->padding = 1; + break; + case FOURCC_drac: + _codec ("Dirac"); + caps = gst_caps_new_empty_simple ("video/x-dirac"); + break; + case GST_MAKE_FOURCC ('t', 'i', 'f', 'f'): + _codec ("TIFF still images"); + caps = gst_caps_new_empty_simple ("image/tiff"); + break; + case GST_MAKE_FOURCC ('i', 'c', 'o', 'd'): + _codec ("Apple Intermediate Codec"); + caps = gst_caps_from_string ("video/x-apple-intermediate-codec"); + break; + case GST_MAKE_FOURCC ('A', 'V', 'd', 'n'): + _codec ("AVID DNxHD"); + caps = gst_caps_from_string ("video/x-dnxhd"); + break; + case GST_MAKE_FOURCC ('A', 'V', 'd', 'v'): + GST_DEBUG_OBJECT (qtdemux, "AVID DVCPRO 50 "); + _codec ("AVID DVCPRO 50"); + caps = gst_caps_from_string ("video/x-avdv"); + break; + case FOURCC_VP80: + _codec ("On2 VP8"); + caps = gst_caps_from_string ("video/x-vp8"); + break; + case FOURCC_vp09: + _codec ("Google VP9"); + caps = gst_caps_from_string ("video/x-vp9"); + break; + case FOURCC_apcs: + _codec ("Apple ProRes LT"); + caps = + gst_caps_new_simple ("video/x-prores", "variant", G_TYPE_STRING, "lt", + NULL); + break; + case FOURCC_apch: + _codec ("Apple ProRes HQ"); + caps = + gst_caps_new_simple ("video/x-prores", "variant", G_TYPE_STRING, "hq", + NULL); + break; + case FOURCC_apcn: + _codec ("Apple ProRes"); + caps = + gst_caps_new_simple ("video/x-prores", "variant", G_TYPE_STRING, + "standard", NULL); + break; + case FOURCC_apco: + _codec ("Apple ProRes Proxy"); + caps = + gst_caps_new_simple ("video/x-prores", "variant", G_TYPE_STRING, + "proxy", NULL); + break; + case FOURCC_ap4h: + _codec ("Apple ProRes 4444"); + caps = + gst_caps_new_simple ("video/x-prores", "variant", G_TYPE_STRING, + "4444", NULL); + break; + case FOURCC_vc_1: + case FOURCC_ovc1: + _codec ("VC-1"); + caps = gst_caps_new_simple ("video/x-wmv", + "wmvversion", G_TYPE_INT, 3, "format", G_TYPE_STRING, "WVC1", NULL); + break; + case GST_MAKE_FOURCC ('k', 'p', 'c', 'd'): + default: + { + char *s, fourstr[5]; + + g_snprintf (fourstr, 5, "%" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + s = g_strdup_printf ("video/x-gst-fourcc-%s", g_strstrip (fourstr)); + caps = gst_caps_new_empty_simple (s); + g_free (s); + break; + } + } + + if (caps) { + qtdemux_set_custom_video_caps (qtdemux, stream, caps); + } + + if (format != GST_VIDEO_FORMAT_UNKNOWN) { + GstVideoInfo info; + + gst_video_info_init (&info); + gst_video_info_set_format (&info, format, stream->width, stream->height); + + caps = gst_video_info_to_caps (&info); + *codec_name = gst_pb_utils_get_codec_description (caps); + + /* enable clipping for raw video streams */ + stream->need_clip = TRUE; + } + + return caps; +} + +static GstCaps * +qtdemux_audio_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 fourcc, const guint8 * data, int len, gchar ** codec_name) +{ + GstCaps *caps; + const GstStructure *s; + const gchar *name; + gint endian = 0; + GstAudioFormat format = 0; + gint depth; + + GST_DEBUG_OBJECT (qtdemux, "resolve fourcc 0x%08x", GUINT32_TO_BE (fourcc)); + + depth = stream->bytes_per_packet * 8; + + switch (fourcc) { + case GST_MAKE_FOURCC ('N', 'O', 'N', 'E'): + case FOURCC_raw_: + { + gchar *str; + + if (depth == 8) { + /* 8-bit audio is unsigned */ + format = GST_AUDIO_FORMAT_U8; + /* otherwise it's signed and big-endian just like 'twos' */ + GST_DEBUG_OBJECT (qtdemux, "Raw 8-bit PCM audio"); + _codec ("Raw 8-bit PCM audio"); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "U8", + "layout", G_TYPE_STRING, "interleaved", NULL); + } else { + /* add big endian for LPCM of audio */ + endian = G_BIG_ENDIAN; + format = gst_audio_format_build_integer (TRUE, endian, depth, depth); + str = g_strdup_printf ("Raw %d-bit PCM audio", depth); + GST_DEBUG_OBJECT (qtdemux, "%s", str); + _codec (str); + g_free (str); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, gst_audio_format_to_string (format), + "layout", G_TYPE_STRING, "interleaved", NULL); + } + break; + } + case FOURCC_twos: + endian = G_BIG_ENDIAN; + /* fall-through */ + case FOURCC_sowt: + { + gchar *str; + + if (!endian) + endian = G_LITTLE_ENDIAN; + + if (!format) + format = gst_audio_format_build_integer (TRUE, endian, depth, depth); + + str = g_strdup_printf ("Raw %d-bit PCM audio", depth); + _codec (str); + g_free (str); + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, gst_audio_format_to_string (format), + "layout", G_TYPE_STRING, "interleaved", NULL); + break; + } + case GST_MAKE_FOURCC ('f', 'l', '6', '4'): + _codec ("Raw 64-bit floating-point audio"); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "F64BE", + "layout", G_TYPE_STRING, "interleaved", NULL); + break; + case GST_MAKE_FOURCC ('f', 'l', '3', '2'): + _codec ("Raw 32-bit floating-point audio"); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "F32BE", + "layout", G_TYPE_STRING, "interleaved", NULL); + break; + case FOURCC_in24: + _codec ("Raw 24-bit PCM audio"); + /* we assume BIG ENDIAN, an enda box will tell us to change this to little + * endian later */ + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "S24BE", + "layout", G_TYPE_STRING, "interleaved", NULL); + break; + case GST_MAKE_FOURCC ('i', 'n', '3', '2'): + _codec ("Raw 32-bit PCM audio"); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, "S32BE", + "layout", G_TYPE_STRING, "interleaved", NULL); + break; + case FOURCC_ulaw: + _codec ("Mu-law audio"); + caps = gst_caps_new_empty_simple ("audio/x-mulaw"); + break; + case FOURCC_alaw: + _codec ("A-law audio"); + caps = gst_caps_new_empty_simple ("audio/x-alaw"); + break; + case 0x0200736d: + case 0x6d730002: + _codec ("Microsoft ADPCM"); + /* Microsoft ADPCM-ACM code 2 */ + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "microsoft", "block_align", G_TYPE_INT, stream->bytes_per_frame, + NULL); + break; + case 0x1100736d: + case 0x6d730011: + _codec ("DVI/IMA ADPCM"); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "dvi", "block_align", G_TYPE_INT, stream->bytes_per_frame, NULL); + break; + case 0x1700736d: + case 0x6d730017: + _codec ("DVI/Intel IMA ADPCM"); + /* FIXME DVI/Intel IMA ADPCM/ACM code 17 */ + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "quicktime" "block_align", G_TYPE_INT, stream->bytes_per_frame, NULL); + break; + case GST_MAKE_FOURCC ('.', 'm', 'p', '2'): + case 0x2e6d7032: + GST_DEBUG_OBJECT (qtdemux, "MPEG-1 layer 2"); + _codec ("MPEG-1 layer 2"); + /* MPEG layer 2 */ + caps = gst_caps_new_simple ("audio/mpeg", "layer", G_TYPE_INT, 2, + "mpegversion", G_TYPE_INT, 1, NULL); + break; + case 0x5500736d: + case 0x6d730055: + /* MPEG layer 3, CBR only (pre QT4.1) */ + case FOURCC__mp3: + _codec ("MPEG-1 layer 3"); + /* MPEG layer 3, CBR & VBR (QT4.1 and later) */ + caps = gst_caps_new_simple ("audio/mpeg", "layer", G_TYPE_INT, 3, + "mpegversion", G_TYPE_INT, 1, NULL); + break; + case 0x20736d: + case FOURCC_ec_3: + _codec ("EAC-3 audio"); + caps = gst_caps_new_simple ("audio/x-eac3", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + stream->sampled = TRUE; + break; + case GST_MAKE_FOURCC ('s', 'a', 'c', '3'): // Nero Recode + case FOURCC_ac_3: + case GST_MAKE_FOURCC ('3', '-', 'c', 'a'): + _codec ("AC-3 audio"); + caps = gst_caps_new_simple ("audio/x-ac3", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + stream->sampled = TRUE; + break; + case FOURCC_ac_4: + _codec ("AC-4 audio"); + caps = gst_caps_new_simple ("audio/x-ac4", + "framed", G_TYPE_BOOLEAN, TRUE, "frame-format", G_TYPE_STRING, "RAW", + NULL); + stream->sampled = TRUE; + break; + case FOURCC_MAC3: + _codec ("MACE-3"); + caps = gst_caps_new_simple ("audio/x-mace", + "maceversion", G_TYPE_INT, 3, NULL); + break; + case FOURCC_MAC6: + _codec ("MACE-6"); + caps = gst_caps_new_simple ("audio/x-mace", + "maceversion", G_TYPE_INT, 6, NULL); + break; + case GST_MAKE_FOURCC ('O', 'g', 'g', 'V'): + /* ogg/vorbis */ + caps = gst_caps_new_empty_simple ("application/ogg"); + break; + case GST_MAKE_FOURCC ('d', 'v', 'c', 'a'): + _codec ("DV audio"); + caps = gst_caps_new_empty_simple ("audio/x-dv"); + break; + case FOURCC_mp4a: + _codec ("MPEG-4 AAC audio"); + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 4, "framed", G_TYPE_BOOLEAN, TRUE, + "stream-format", G_TYPE_STRING, "raw", NULL); + break; + case FOURCC_mhm1: + _codec ("MPEG-H 3D audio"); + caps = gst_caps_new_simple ("audio/mpeg-h", + "framed", G_TYPE_BOOLEAN, TRUE, NULL); + stream->sampled = TRUE; + break; + case GST_MAKE_FOURCC ('Q', 'D', 'M', 'C'): + _codec ("QDesign Music"); + caps = gst_caps_new_empty_simple ("audio/x-qdm"); + break; + case FOURCC_QDM2: + _codec ("QDesign Music v.2"); + /* FIXME: QDesign music version 2 (no constant) */ + if (FALSE && data) { + caps = gst_caps_new_simple ("audio/x-qdm2", + "framesize", G_TYPE_INT, QT_UINT32 (data + 52), + "bitrate", G_TYPE_INT, QT_UINT32 (data + 40), + "blocksize", G_TYPE_INT, QT_UINT32 (data + 44), NULL); + } else { + caps = gst_caps_new_empty_simple ("audio/x-qdm2"); + } + break; + case FOURCC_agsm: + _codec ("GSM audio"); + caps = gst_caps_new_empty_simple ("audio/x-gsm"); + break; + case FOURCC_samr: + _codec ("AMR audio"); + caps = gst_caps_new_empty_simple ("audio/AMR"); + break; + case FOURCC_sawb: + _codec ("AMR-WB audio"); + caps = gst_caps_new_empty_simple ("audio/AMR-WB"); + break; + case FOURCC_ima4: + _codec ("Quicktime IMA ADPCM"); + caps = gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, + "quicktime", "block_align", G_TYPE_INT, stream->bytes_per_frame, + NULL); + break; + case FOURCC_alac: + _codec ("Apple lossless audio"); + caps = gst_caps_new_empty_simple ("audio/x-alac"); + break; + case GST_MAKE_FOURCC ('Q', 'c', 'l', 'p'): + _codec ("QualComm PureVoice"); + caps = gst_caps_from_string ("audio/qcelp"); + break; + case FOURCC_wma_: + case FOURCC_owma: + _codec ("WMA"); + caps = gst_caps_new_empty_simple ("audio/x-wma"); + break; + case FOURCC_opus: + _codec ("Opus"); + caps = gst_caps_new_empty_simple ("audio/x-opus"); + break; + case GST_MAKE_FOURCC ('l', 'p', 'c', 'm'): + { + guint32 flags = 0; + guint32 depth = 0; + guint32 width = 0; + GstAudioFormat format; + enum + { + FLAG_IS_FLOAT = 0x1, + FLAG_IS_BIG_ENDIAN = 0x2, + FLAG_IS_SIGNED = 0x4, + FLAG_IS_PACKED = 0x8, + FLAG_IS_ALIGNED_HIGH = 0x10, + FLAG_IS_NON_INTERLEAVED = 0x20 + }; + _codec ("Raw LPCM audio"); + + if (data && len >= 56) { + depth = QT_UINT32 (data + 40); + flags = QT_UINT32 (data + 44); + width = QT_UINT32 (data + 48) * 8 / stream->n_channels; + } + if ((flags & FLAG_IS_FLOAT) == 0) { + if (depth == 0) + depth = 16; + if (width == 0) + width = 16; + format = gst_audio_format_build_integer ((flags & FLAG_IS_SIGNED) ? + TRUE : FALSE, (flags & FLAG_IS_BIG_ENDIAN) ? + G_BIG_ENDIAN : G_LITTLE_ENDIAN, width, depth); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, gst_audio_format_to_string (format), + "layout", G_TYPE_STRING, (flags & FLAG_IS_NON_INTERLEAVED) ? + "non-interleaved" : "interleaved", NULL); + } else { + if (width == 0) + width = 32; + if (width == 64) { + if (flags & FLAG_IS_BIG_ENDIAN) + format = GST_AUDIO_FORMAT_F64BE; + else + format = GST_AUDIO_FORMAT_F64LE; + } else { + if (flags & FLAG_IS_BIG_ENDIAN) + format = GST_AUDIO_FORMAT_F32BE; + else + format = GST_AUDIO_FORMAT_F32LE; + } + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, gst_audio_format_to_string (format), + "layout", G_TYPE_STRING, (flags & FLAG_IS_NON_INTERLEAVED) ? + "non-interleaved" : "interleaved", NULL); + } + break; + } + case GST_MAKE_FOURCC ('q', 't', 'v', 'r'): + /* ? */ + default: + { + char *s, fourstr[5]; + + g_snprintf (fourstr, 5, "%" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + s = g_strdup_printf ("audio/x-gst-fourcc-%s", g_strstrip (fourstr)); + caps = gst_caps_new_empty_simple (s); + g_free (s); + break; + } + } + + if (caps) { + GstCaps *templ_caps = + gst_static_pad_template_get_caps (&gst_qtdemux_audiosrc_template); + GstCaps *intersection = gst_caps_intersect (caps, templ_caps); + gst_caps_unref (caps); + gst_caps_unref (templ_caps); + caps = intersection; + } + + /* enable clipping for raw audio streams */ + s = gst_caps_get_structure (caps, 0); + name = gst_structure_get_name (s); + if (g_str_has_prefix (name, "audio/x-raw")) { + stream->need_clip = TRUE; + stream->max_buffer_size = stream->rate * stream->bytes_per_frame; + GST_DEBUG ("setting max buffer size to %d", stream->max_buffer_size); + } + return caps; +} + +static GstCaps * +qtdemux_sub_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name) +{ + GstCaps *caps; + + GST_DEBUG_OBJECT (qtdemux, "resolve fourcc 0x%08x", GUINT32_TO_BE (fourcc)); + + switch (fourcc) { + case FOURCC_mp4s: + _codec ("DVD subtitle"); + caps = gst_caps_new_empty_simple ("subpicture/x-dvd"); + stream->need_process = TRUE; + break; + case FOURCC_text: + _codec ("Quicktime timed text"); + goto text; + case FOURCC_tx3g: + _codec ("3GPP timed text"); + text: + caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING, + "utf8", NULL); + /* actual text piece needs to be extracted */ + stream->need_process = TRUE; + break; + default: + { + char *s, fourstr[5]; + + g_snprintf (fourstr, 5, "%" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + s = g_strdup_printf ("text/x-gst-fourcc-%s", g_strstrip (fourstr)); + caps = gst_caps_new_empty_simple (s); + g_free (s); + break; + } + } + return caps; +} + +static GstCaps * +qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream, + guint32 fourcc, const guint8 * stsd_entry_data, gchar ** codec_name) +{ + GstCaps *caps; + + switch (fourcc) { + case FOURCC_m1v: + _codec ("MPEG 1 video"); + caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 1, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; + default: + caps = NULL; + break; + } + return caps; +} + +static void +gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux, + const gchar * system_id) +{ + gint i; + + if (!qtdemux->protection_system_ids) + qtdemux->protection_system_ids = + g_ptr_array_new_with_free_func ((GDestroyNotify) g_free); + /* Check whether we already have an entry for this system ID. */ + for (i = 0; i < qtdemux->protection_system_ids->len; ++i) { + const gchar *id = g_ptr_array_index (qtdemux->protection_system_ids, i); + if (g_ascii_strcasecmp (system_id, id) == 0) { + return; + } + } + GST_DEBUG_OBJECT (qtdemux, "Adding cenc protection system ID %s", system_id); + g_ptr_array_add (qtdemux->protection_system_ids, g_ascii_strdown (system_id, + -1)); +} + +static gboolean +is_common_enc_scheme_type (guint32 scheme_type) +{ + if (scheme_type == FOURCC_cenc || scheme_type == FOURCC_cens || + scheme_type == FOURCC_cbc1 || scheme_type == FOURCC_cbcs) { + return TRUE; + } + return FALSE; +} + +#ifdef DOLBYHDR_SUPPORT +static gboolean +qtdemux_prepare_dolby_track (GstQTDemux * qtdemux, QtDemuxStream * stream) +{ + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (qtdemux->is_dolby_hdr, FALSE); + + GST_DEBUG_OBJECT (qtdemux, "prepare dolby track"); + + /* dv_profile ref. in Table 2-1, Dolby Vision Profiles + * 0 : dvav.per, BL(SDR):EL = 1:1/4 + * 1 : dvav.pen, BL(non):EL = 1:1 + * 2 : dvhe.der, BL(SDR):EL = 1:1/4 + * 3 : dvhe.den, BL(non):EL = 1:1 + * 4 : dvhe.dtr, BL(SDR):EL = 1:1/4 + * 5 : dvhe.stn, BL(non):EL = 1:none + * 6 : dvhe.dth, BL(HDR):EL = 1:1/4 + * 7 : dvhe.dtb, BL(HDR):EL = 1:1/4 (for UHD) or 1:1 (for FHD)*/ + + /* 1. Check profile and supportability */ + if (!qtdemux->dolby_vision_support) { + if (qtdemux->dv_profile == 1 || qtdemux->dv_profile == 3 + || qtdemux->dv_profile == 5) { + /* if following conditions are met, "no playable stream error" will be posted + * - Dolby Vision cannot be supported by this platform + * - non-backward compatible profile (i.e., profile 1, 3, and 5) + */ + gst_qtdemux_post_no_playable_stream_error (qtdemux); + return FALSE; + } + } else { + GST_DEBUG_OBJECT (qtdemux, "Set dolby-vision TRUE"); + gst_caps_set_simple (stream->caps, "dolby-vision", G_TYPE_BOOLEAN, + TRUE, NULL); + if (qtdemux->dv_profile >= 0 && qtdemux->dv_profile <= 7) + gst_caps_set_simple (stream->caps, "dolby-vision-profile", G_TYPE_INT, + qtdemux->dv_profile, NULL); + } + + /* 2. Check single- or dual-track */ + if (qtdemux->has_dolby_bl_cand && qtdemux->has_dolby_el_cand) { + gst_caps_set_simple (stream->caps, "need-compositor", + G_TYPE_BOOLEAN, TRUE, "dolby-vision-track", G_TYPE_STRING, "dual", + NULL); + } + + return TRUE; +} +#endif diff --git a/gst/isomp4_1_8/qtdemux.h b/gst/isomp4_1_8/qtdemux.h new file mode 100644 index 0000000000..83d9ac7eb5 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux.h @@ -0,0 +1,251 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GST_QTDEMUX_H__ +#define __GST_QTDEMUX_H__ + +#include +#include +#include +#include "gstisoff.h" + +G_BEGIN_DECLS GST_DEBUG_CATEGORY_EXTERN (qtdemux_debug); +#define GST_CAT_DEFAULT qtdemux_debug + +#define GST_TYPE_QTDEMUX \ + (gst_qtdemux_get_type()) +#define GST_QTDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QTDEMUX,GstQTDemux)) +#define GST_QTDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QTDEMUX,GstQTDemuxClass)) +#define GST_IS_QTDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QTDEMUX)) +#define GST_IS_QTDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QTDEMUX)) + +#define GST_QTDEMUX_CAST(obj) ((GstQTDemux *)(obj)) + +/* qtdemux produces these for atoms it cannot parse */ +#define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag" +#define GST_QT_DEMUX_CLASSIFICATION_TAG "classification" + +#define GST_QTDEMUX_MAX_STREAMS 32 + +#define MP4_PUSHMODE_TRICK + +#ifdef MP4_PUSHMODE_TRICK +#define TIME_ADJUST 100000000 +#endif + +#define MPEGDASH_MODE +#define ATSC3_MODE +#define DOLBYHDR_SUPPORT + +typedef struct _GstQTDemux GstQTDemux; +typedef struct _GstQTDemuxClass GstQTDemuxClass; +typedef struct _QtDemuxStream QtDemuxStream; + +struct _GstQTDemux +{ + GstElement element; + + /* pads */ + GstPad *sinkpad; + + GstStreamCollection *collection; + + QtDemuxStream *streams[GST_QTDEMUX_MAX_STREAMS]; + gint n_streams; + gint n_video_streams; + gint n_audio_streams; + gint n_sub_streams; + + GstFlowCombiner *flowcombiner; + + gboolean have_group_id; + guint group_id; + + guint major_brand; + GstBuffer *comp_brands; + GNode *moov_node; + GNode *moov_node_compressed; + + guint32 timescale; + GstClockTime duration; + + gboolean fragmented; + gboolean fragmented_seek_pending; + guint64 moof_offset; + + gint state; + + gboolean pullbased; + gboolean posted_redirect; + gboolean seek_to_key_frame; + + /* Protect pad exposing from flush event */ + GMutex expose_lock; + +#ifdef MP4_PUSHMODE_TRICK + gdouble demux_rate; + gboolean pushed_Iframe; + gboolean pushed_Audio; + gboolean all_audio_pushed; + gboolean segment_event_recvd; + guint64 trick_offset; + guint64 prev_seek_offset; + guint64 prev_segment_position; + /* For MCVT Trick Play */ + guint64 next_seek_offset; + gboolean rate_changed; +#endif + + /* push based variables */ + GstClockTime upstream_basetime; /* basetime given by upstream to be added to output pts */ + GstClockTime upstream_basetime_offset; /* time offset derived by demux to be subtracted to output pts */ + gboolean new_collection; + guint neededbytes; + guint todrop; + GstAdapter *adapter; + GstBuffer *mdatbuffer; + guint64 mdatleft; + /* When restoring the mdat to the adatpter, this buffer + * stores any trailing data that was after the last atom parsed as it + * has to be restored later along with the correct offset. Used in + * fragmented scenario where mdat/moof are one after the other + * in any order. + * + * Check https://bugzilla.gnome.org/show_bug.cgi?id=710623 */ + GstBuffer *restoredata_buffer; + guint64 restoredata_offset; + + guint64 offset; + /* offset of the mdat atom */ + guint64 mdatoffset; + guint64 first_mdat; + gboolean got_moov; + guint64 last_moov_offset; + guint header_size; + + GstTagList *tag_list; + + /* configured playback region */ + GstSegment segment; + GstEvent *pending_newsegment; + guint32 segment_seqnum; + gboolean upstream_format_is_time; /* qtdemux received upstream + * newsegment in TIME format which likely + * means that upstream is driving the pipeline + * (adaptive demuxers / dlna) */ + guint32 offset_seek_seqnum; + gint64 seek_offset; + gint64 push_seek_start; + gint64 push_seek_stop; + +#if 0 + /* gst index support */ + GstIndex *element_index; + gint index_id; +#endif + + gboolean upstream_seekable; + gint64 upstream_size; + + /* MSS streams have a single media that is unspecified at the atoms, so + * upstream provides it at the caps */ + GstCaps *media_caps; + gboolean exposed; + gboolean mss_mode; /* flag to indicate that we're working with a smoothstreaming fragment + * Mss doesn't have 'moov' or any information about the streams format, + * requiring qtdemux to expose and create the streams */ + guint64 fragment_start; + guint64 fragment_start_offset; + + gint64 chapters_track_id; + + /* protection support */ + GPtrArray *protection_system_ids; /* Holds identifiers of all content protection systems for all tracks */ + GQueue protection_event_queue; /* holds copy of upstream protection events */ + guint64 cenc_aux_info_offset; + guint8 *cenc_aux_info_sizes; + guint32 cenc_aux_sample_count; + + /* mpu specific variables */ + guint64 mpu_offset; + guint32 mpu_seq_num; + gchar *asset_id; + gboolean has_mmth; + gboolean is_mmth_timed; + gboolean ignore_hintsample; + + gboolean thumbnail_mode; + gboolean isInterleaved; + gboolean isBigData; + gboolean isStartKeyFrame; + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + /* mpegdash mode flag (jaehoon.shim@lge.com) */ + gboolean dash_mode; + GstClockTimeDiff dash_pts_offset; + guint64 dash_fragment_start; + GstClockTime dash_segment_start; + guint64 dash_period_start; + gint64 dash_subtitle_offset; + guint dash_subtitle_index; + gboolean use_svp; +#endif + +#ifdef ATSC3_MODE + gboolean atsc3_mode; + GstClockTime prev_decode_time; + gboolean configure_dvr; +#endif + +#ifdef DOLBYHDR_SUPPORT + /* Dolby HDR */ + gboolean dolby_vision_support; + gboolean is_dolby_hdr; + gboolean has_dolby_bl_cand; + gboolean has_dolby_el_cand; + + /* Dolby HDR dvcC info. */ + gint8 dv_profile; + gboolean rpu_present_flag; + gboolean el_present_flag; + gboolean bl_present_flag; +#endif + guint32 dlna_opval; + + gint highest_temporal_id; +}; + +struct _GstQTDemuxClass +{ + GstElementClass parent_class; + +#if defined (MPEGDASH_MODE) || defined (ATSC3_MODE) + void (*start_time) (GstQTDemux * qtdemux, gpointer start_time, gpointer updated_start_time, gpointer time); +#endif +}; + +GType gst_qtdemux_get_type (void); + +G_END_DECLS +#endif /* __GST_QTDEMUX_H__ */ diff --git a/gst/isomp4_1_8/qtdemux_dump.c b/gst/isomp4_1_8/qtdemux_dump.c new file mode 100644 index 0000000000..880bb74cbe --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_dump.c @@ -0,0 +1,955 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) 2009 Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "qtdemux_types.h" +#include "qtdemux_dump.h" +#include "fourcc.h" + +#include "qtatomparser.h" + +#include + +#define GET_UINT8(data) gst_byte_reader_get_uint8_unchecked(data) +#define GET_UINT16(data) gst_byte_reader_get_uint16_be_unchecked(data) +#define GET_UINT32(data) gst_byte_reader_get_uint32_be_unchecked(data) +#define GET_UINT64(data) gst_byte_reader_get_uint64_be_unchecked(data) +#define GET_FP32(data) (gst_byte_reader_get_uint32_be_unchecked(data)/65536.0) +#define GET_FP16(data) (gst_byte_reader_get_uint16_be_unchecked(data)/256.0) +#define GET_FOURCC(data) qt_atom_parser_get_fourcc_unchecked(data) + +gboolean +qtdemux_dump_mvhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version = 0; + + if (!qt_atom_parser_has_remaining (data, 100)) + return FALSE; + + version = GET_UINT32 (data); + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + version = version >> 24; + if (version == 0) { + GST_LOG ("%*s creation time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s modify time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s time scale: 1/%u sec", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s duration: %u", depth, "", GET_UINT32 (data)); + } else if (version == 1) { + GST_LOG ("%*s creation time: %" G_GUINT64_FORMAT, + depth, "", GET_UINT64 (data)); + GST_LOG ("%*s modify time: %" G_GUINT64_FORMAT, + depth, "", GET_UINT64 (data)); + GST_LOG ("%*s time scale: 1/%u sec", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s duration: %" G_GUINT64_FORMAT, + depth, "", GET_UINT64 (data)); + } else + return FALSE; + + GST_LOG ("%*s pref. rate: %g", depth, "", GET_FP32 (data)); + GST_LOG ("%*s pref. volume: %g", depth, "", GET_FP16 (data)); + gst_byte_reader_skip_unchecked (data, 46); + GST_LOG ("%*s preview time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s preview dur.: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s poster time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s select time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s select dur.: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s current time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s next track ID: %d", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +qtdemux_dump_tkhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint64 duration, ctime, mtime; + guint32 version = 0, track_id = 0, iwidth = 0, iheight = 0; + guint16 layer = 0, alt_group = 0, ivol = 0; + guint value_size; + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + value_size = ((version >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + + if (qt_atom_parser_get_offset (data, value_size, &ctime) && + qt_atom_parser_get_offset (data, value_size, &mtime) && + gst_byte_reader_get_uint32_be (data, &track_id) && + gst_byte_reader_skip (data, 4) && + qt_atom_parser_get_offset (data, value_size, &duration) && + gst_byte_reader_skip (data, 4) && + gst_byte_reader_get_uint16_be (data, &layer) && + gst_byte_reader_get_uint16_be (data, &alt_group) && + gst_byte_reader_skip (data, 4) && + gst_byte_reader_get_uint16_be (data, &ivol) && + gst_byte_reader_skip (data, 2 + (9 * 4)) && + gst_byte_reader_get_uint32_be (data, &iwidth) && + gst_byte_reader_get_uint32_be (data, &iheight)) { + GST_LOG ("%*s creation time: %" G_GUINT64_FORMAT, depth, "", ctime); + GST_LOG ("%*s modify time: %" G_GUINT64_FORMAT, depth, "", mtime); + GST_LOG ("%*s track ID: %u", depth, "", track_id); + GST_LOG ("%*s duration: %" G_GUINT64_FORMAT, depth, "", duration); + GST_LOG ("%*s layer: %u", depth, "", layer); + GST_LOG ("%*s alt group: %u", depth, "", alt_group); + GST_LOG ("%*s volume: %g", depth, "", ivol / 256.0); + GST_LOG ("%*s track width: %g", depth, "", iwidth / 65536.0); + GST_LOG ("%*s track height: %g", depth, "", iheight / 65536.0); + return TRUE; + } + + return FALSE; +} + +gboolean +qtdemux_dump_elst (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4 + 4 + 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s track dur: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s media time: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s media rate: %g", depth, "", GET_FP32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_mdhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version = 0; + guint64 duration, ctime, mtime; + guint32 time_scale = 0; + guint16 language = 0, quality = 0; + guint value_size; + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + value_size = ((version >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + + if (qt_atom_parser_get_offset (data, value_size, &ctime) && + qt_atom_parser_get_offset (data, value_size, &mtime) && + gst_byte_reader_get_uint32_be (data, &time_scale) && + qt_atom_parser_get_offset (data, value_size, &duration) && + gst_byte_reader_get_uint16_be (data, &language) && + gst_byte_reader_get_uint16_be (data, &quality)) { + GST_LOG ("%*s creation time: %" G_GUINT64_FORMAT, depth, "", ctime); + GST_LOG ("%*s modify time: %" G_GUINT64_FORMAT, depth, "", mtime); + GST_LOG ("%*s time scale: 1/%u sec", depth, "", time_scale); + GST_LOG ("%*s duration: %" G_GUINT64_FORMAT, depth, "", duration); + GST_LOG ("%*s language: %u", depth, "", language); + GST_LOG ("%*s quality: %u", depth, "", quality); + return TRUE; + } + + return FALSE; +} + +gboolean +qtdemux_dump_hdlr (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version, type, subtype, manufacturer; + const gchar *name; + + if (!qt_atom_parser_has_remaining (data, 4 + 4 + 4 + 4 + 4 + 4 + 1)) + return FALSE; + + version = GET_UINT32 (data); + type = GET_FOURCC (data); + subtype = GET_FOURCC (data); + manufacturer = GET_FOURCC (data); + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + GST_LOG ("%*s type: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (type)); + GST_LOG ("%*s subtype: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (subtype)); + GST_LOG ("%*s manufacturer: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (manufacturer)); + GST_LOG ("%*s flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s flags mask: %08x", depth, "", GET_UINT32 (data)); + + /* quicktime uses pascal string, mp4 zero-terminated string */ + if (gst_byte_reader_peek_string (data, &name)) { + GST_LOG ("%*s name: %s", depth, "", name); + } else { + gchar buf[256]; + guint len; + + len = gst_byte_reader_get_uint8_unchecked (data); + if (qt_atom_parser_has_remaining (data, len)) { + memcpy (buf, gst_byte_reader_peek_data_unchecked (data), len); + buf[len] = '\0'; + GST_LOG ("%*s name: %s", depth, "", buf); + } + } + return TRUE; +} + +gboolean +qtdemux_dump_vmhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4 + 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s mode/color: %08x", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +qtdemux_dump_dref (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %u", depth, "", num_entries); + for (i = 0; i < num_entries; i++) { + guint32 size = 0, fourcc; + + if (!gst_byte_reader_get_uint32_be (data, &size) || + !qt_atom_parser_get_fourcc (data, &fourcc) || size < 8 || + !gst_byte_reader_skip (data, size - 8)) + return FALSE; + + GST_LOG ("%*s size: %u", depth, "", size); + GST_LOG ("%*s type: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (fourcc)); + } + return TRUE; +} + +static gboolean +qtdemux_dump_stsd_avc1 (GstQTDemux * qtdemux, GstByteReader * data, guint size, + int depth) +{ + guint32 fourcc; + + /* Size of avc1 = 78 bytes */ + if (size < (6 + 2 + 4 + 4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 2 + 1 + 31 + 2 + 2)) + return FALSE; + + gst_byte_reader_skip_unchecked (data, 6); + GST_LOG_OBJECT (qtdemux, "%*s data reference:%d", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s version/rev.: %08x", depth, "", + GET_UINT32 (data)); + fourcc = GET_FOURCC (data); + GST_LOG_OBJECT (qtdemux, "%*s vendor: %" GST_FOURCC_FORMAT, depth, + "", GST_FOURCC_ARGS (fourcc)); + GST_LOG_OBJECT (qtdemux, "%*s temporal qual: %u", depth, "", + GET_UINT32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s spatial qual: %u", depth, "", + GET_UINT32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s width: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s height: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s horiz. resol: %g", depth, "", + GET_FP32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s vert. resol.: %g", depth, "", + GET_FP32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s data size: %u", depth, "", + GET_UINT32 (data)); + GST_LOG_OBJECT (qtdemux, "%*s frame count: %u", depth, "", + GET_UINT16 (data)); + /* something is not right with this, it's supposed to be a string but it's + * not apparently, so just skip this for now */ + gst_byte_reader_skip_unchecked (data, 1 + 31); + GST_LOG_OBJECT (qtdemux, "%*s compressor: (skipped)", depth, ""); + GST_LOG_OBJECT (qtdemux, "%*s depth: %u", depth, "", + GET_UINT16 (data)); + GST_LOG_OBJECT (qtdemux, "%*s color table ID:%u", depth, "", + GET_UINT16 (data)); + + return TRUE; +} + +gboolean +qtdemux_dump_stsd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + for (i = 0; i < num_entries; i++) { + GstByteReader sub; + guint32 size, remain; + guint32 fourcc; + + if (!gst_byte_reader_get_uint32_be (data, &size) || + !qt_atom_parser_get_fourcc (data, &fourcc)) + return FALSE; + + GST_LOG_OBJECT (qtdemux, "%*s size: %u", depth, "", size); + GST_LOG_OBJECT (qtdemux, "%*s type: %" GST_FOURCC_FORMAT, depth, + "", GST_FOURCC_ARGS (fourcc)); + + remain = gst_byte_reader_get_remaining (data); + /* Size includes the 8 bytes we just read: len & fourcc, then 8 bytes + * version, flags, entries_count */ + if (size > remain + 8) { + GST_LOG_OBJECT (qtdemux, + "Not enough data left for this atom (have %u need %u)", remain, size); + return FALSE; + } + + qt_atom_parser_peek_sub (data, 0, size, &sub); + switch (fourcc) { + case FOURCC_avc1: + if (!qtdemux_dump_stsd_avc1 (qtdemux, &sub, size, depth + 1)) + return FALSE; + break; + case FOURCC_mp4s: + if (!gst_byte_reader_get_uint32_be (&sub, &ver_flags) || + !gst_byte_reader_get_uint32_be (&sub, &num_entries)) + return FALSE; + if (!qtdemux_dump_unknown (qtdemux, &sub, depth + 1)) + return FALSE; + break; + default: + /* Unknown stsd data, dump the bytes */ + if (!qtdemux_dump_unknown (qtdemux, &sub, depth + 1)) + return FALSE; + break; + } + + if (!gst_byte_reader_skip (data, size - (4 + 4))) + return FALSE; + } + return TRUE; +} + +gboolean +qtdemux_dump_stts (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4 + 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s count: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s duration: %u", depth, "", GET_UINT32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_stps (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s sample: %u", depth, "", GET_UINT32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_stss (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s sample: %u", depth, "", GET_UINT32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_stsc (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4 + 4 + 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s first chunk: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s sample per ch: %u", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s sample desc id:%08x", depth, "", GET_UINT32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_stsz (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, sample_size = 0, num_entries = 0; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &sample_size)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s sample size: %d", depth, "", sample_size); + + if (sample_size == 0) { + if (!gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s n entries: %d", depth, "", num_entries); +#if 0 + if (!qt_atom_parser_has_chunks (data, num_entries, 4)) + return FALSE; + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s sample size: %u", depth, "", GET_UINT32 (data)); + } +#endif + } + return TRUE; +} + +gboolean +qtdemux_dump_stco (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s chunk offset: %u", depth, "", GET_UINT32 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_ctts (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i, count; + gint32 offset; + + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %u", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 4 + 4)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + count = GET_UINT32 (data); + offset = GET_UINT32 (data); + GST_LOG ("%*s sample count :%8d offset: %8d", depth, "", count, offset); + } + return TRUE; +} + +gboolean +qtdemux_dump_cslg (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, shift = 0; + gint32 least_offset = 0, start_time = 0, end_time = 0; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &shift) || + !gst_byte_reader_get_int32_be (data, &least_offset) || + !gst_byte_reader_get_int32_be (data, &start_time) || + !gst_byte_reader_get_int32_be (data, &end_time)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s shift: %u", depth, "", shift); + GST_LOG ("%*s least offset: %d", depth, "", least_offset); + GST_LOG ("%*s start time: %d", depth, "", start_time); + GST_LOG ("%*s end time: %d", depth, "", end_time); + + return TRUE; +} + +gboolean +qtdemux_dump_co64 (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 ver_flags = 0, num_entries = 0, i; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + GST_LOG ("%*s n entries: %d", depth, "", num_entries); + + if (!qt_atom_parser_has_chunks (data, num_entries, 8)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + GST_LOG ("%*s chunk offset: %" G_GUINT64_FORMAT, depth, "", + GET_UINT64 (data)); + } + return TRUE; +} + +gboolean +qtdemux_dump_dcom (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4)) + return FALSE; + + GST_LOG ("%*s compression type: %" GST_FOURCC_FORMAT, depth, "", + GST_FOURCC_ARGS (GET_FOURCC (data))); + return TRUE; +} + +gboolean +qtdemux_dump_cmvd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4)) + return FALSE; + + GST_LOG ("%*s length: %d", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +qtdemux_dump_mfro (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s size: %d", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +qtdemux_dump_mfhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s sequence_number: %d", depth, "", GET_UINT32 (data)); + return TRUE; +} + +gboolean +qtdemux_dump_tfra (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint64 time = 0, moof_offset = 0; + guint32 len = 0, num_entries = 0, ver_flags = 0, track_id = 0, i; + guint value_size, traf_size, trun_size, sample_size; + + if (!gst_byte_reader_get_uint32_be (data, &ver_flags)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", ver_flags); + + if (!gst_byte_reader_get_uint32_be (data, &track_id) || + !gst_byte_reader_get_uint32_be (data, &len) || + !gst_byte_reader_get_uint32_be (data, &num_entries)) + return FALSE; + + GST_LOG ("%*s track ID: %u", depth, "", track_id); + GST_LOG ("%*s length: 0x%x", depth, "", len); + GST_LOG ("%*s n entries: %u", depth, "", num_entries); + + value_size = ((ver_flags >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + sample_size = (len & 3) + 1; + trun_size = ((len & 12) >> 2) + 1; + traf_size = ((len & 48) >> 4) + 1; + + if (!qt_atom_parser_has_chunks (data, num_entries, + value_size + value_size + traf_size + trun_size + sample_size)) + return FALSE; + + for (i = 0; i < num_entries; i++) { + qt_atom_parser_get_offset (data, value_size, &time); + qt_atom_parser_get_offset (data, value_size, &moof_offset); + GST_LOG ("%*s time: %" G_GUINT64_FORMAT, depth, "", time); + GST_LOG ("%*s moof_offset: %" G_GUINT64_FORMAT, + depth, "", moof_offset); + GST_LOG ("%*s traf_number: %u", depth, "", + qt_atom_parser_get_uint_with_size_unchecked (data, traf_size)); + GST_LOG ("%*s trun_number: %u", depth, "", + qt_atom_parser_get_uint_with_size_unchecked (data, trun_size)); + GST_LOG ("%*s sample_number: %u", depth, "", + qt_atom_parser_get_uint_with_size_unchecked (data, sample_size)); + } + + return TRUE; +} + +gboolean +qtdemux_dump_tfhd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 flags = 0, n = 0, track_id = 0; + guint64 base_data_offset = 0; + + if (!gst_byte_reader_skip (data, 1) || + !gst_byte_reader_get_uint24_be (data, &flags)) + return FALSE; + GST_LOG ("%*s flags: %08x", depth, "", flags); + + if (!gst_byte_reader_get_uint32_be (data, &track_id)) + return FALSE; + GST_LOG ("%*s track_id: %u", depth, "", track_id); + + if (flags & TF_BASE_DATA_OFFSET) { + if (!gst_byte_reader_get_uint64_be (data, &base_data_offset)) + return FALSE; + GST_LOG ("%*s base-data-offset: %" G_GUINT64_FORMAT, + depth, "", base_data_offset); + } + + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s sample-description-index: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_DURATION) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-duration: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_SIZE) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-size: %u", depth, "", n); + } + + if (flags & TF_DEFAULT_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &n)) + return FALSE; + GST_LOG ("%*s default-sample-flags: %u", depth, "", n); + } + + GST_LOG ("%*s duration-is-empty: %s", depth, "", + flags & TF_DURATION_IS_EMPTY ? "yes" : "no"); + + return TRUE; +} + +gboolean +qtdemux_dump_trun (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 flags = 0, samples_count = 0, data_offset = 0, first_sample_flags = 0; + guint32 sample_duration = 0, sample_size = 0, sample_flags = + 0, composition_time_offsets = 0; + int i = 0; + + if (!gst_byte_reader_skip (data, 1) || + !gst_byte_reader_get_uint24_be (data, &flags)) + return FALSE; + + GST_LOG ("%*s flags: %08x", depth, "", flags); + + if (!gst_byte_reader_get_uint32_be (data, &samples_count)) + return FALSE; + GST_LOG ("%*s samples_count: %u", depth, "", samples_count); + + if (flags & TR_DATA_OFFSET) { + if (!gst_byte_reader_get_uint32_be (data, &data_offset)) + return FALSE; + GST_LOG ("%*s data-offset: %u", depth, "", data_offset); + } + + if (flags & TR_FIRST_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &first_sample_flags)) + return FALSE; + GST_LOG ("%*s first-sample-flags: %u", depth, "", first_sample_flags); + } + + for (i = 0; i < samples_count; i++) { + if (flags & TR_SAMPLE_DURATION) { + if (!gst_byte_reader_get_uint32_be (data, &sample_duration)) + return FALSE; + GST_LOG ("%*s sample-duration: %u", depth, "", sample_duration); + } + + if (flags & TR_SAMPLE_SIZE) { + if (!gst_byte_reader_get_uint32_be (data, &sample_size)) + return FALSE; + GST_LOG ("%*s sample-size: %u", depth, "", sample_size); + } + + if (flags & TR_SAMPLE_FLAGS) { + if (!gst_byte_reader_get_uint32_be (data, &sample_flags)) + return FALSE; + GST_LOG ("%*s sample-flags: %u", depth, "", sample_flags); + } + + if (flags & TR_COMPOSITION_TIME_OFFSETS) { + if (!gst_byte_reader_get_uint32_be (data, &composition_time_offsets)) + return FALSE; + GST_LOG ("%*s composition_time_offsets: %u", depth, "", + composition_time_offsets); + } + } + + return TRUE; +} + +gboolean +qtdemux_dump_trex (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + if (!qt_atom_parser_has_remaining (data, 4 + 4 + 4 + 4 + 4 + 4)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s track ID: %08x", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s default sample desc. index: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample duration: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample size: %08x", depth, "", + GET_UINT32 (data)); + GST_LOG ("%*s default sample flags: %08x", depth, "", + GET_UINT32 (data)); + + return TRUE; +} + +gboolean +qtdemux_dump_mehd (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version = 0; + guint64 fragment_duration; + guint value_size; + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + value_size = ((version >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + if (qt_atom_parser_get_offset (data, value_size, &fragment_duration)) { + GST_LOG ("%*s fragment duration: %" G_GUINT64_FORMAT, + depth, "", fragment_duration); + return TRUE; + } + + return FALSE; +} + +gboolean +qtdemux_dump_tfdt (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version = 0; + guint64 decode_time; + guint value_size; + + if (!gst_byte_reader_get_uint32_be (data, &version)) + return FALSE; + + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + value_size = ((version >> 24) == 1) ? sizeof (guint64) : sizeof (guint32); + if (qt_atom_parser_get_offset (data, value_size, &decode_time)) { + GST_LOG ("%*s Track fragment decode time: %" G_GUINT64_FORMAT, + depth, "", decode_time); + return TRUE; + } + + return FALSE; +} + +gboolean +qtdemux_dump_sdtp (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version; + guint8 val; + guint i = 1; + + version = GET_UINT32 (data); + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + /* the sample_count is specified in the stsz or stz2 box. + * the information for a sample is stored in a single byte, + * so we read until there are no remaining bytes */ + while (qt_atom_parser_has_remaining (data, 1)) { + val = GET_UINT8 (data); + GST_LOG ("%*s sample number: %d", depth, "", i); + GST_LOG ("%*s sample_depends_on: %d", depth, "", + ((guint16) (val)) & 0x3); + GST_LOG ("%*s sample_is_depended_on: %d", depth, "", + ((guint16) (val >> 2)) & 0x3); + GST_LOG ("%*s sample_has_redundancy: %d", depth, "", + ((guint16) (val >> 4)) & 0x3); + GST_LOG ("%*s early display: %d", depth, "", + ((guint16) (val >> 6)) & 0x1); + ++i; + } + return TRUE; +} + +gboolean +qtdemux_dump_svmi (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + guint32 version; + guint stereo_mono_change_count; + guint i; + + version = GET_UINT32 (data); + GST_LOG ("%*s version/flags: %08x", depth, "", version); + + if (!version) { + /* stereoscopic visual type information */ + GST_LOG ("%*s stereo_composition_type: %d", depth, "", + GET_UINT8 (data)); + GST_LOG ("%*s is_left_first: %d", depth, "", + ((guint8) GET_UINT8 (data)) & 0x01); + + /* stereo_mono_change information */ + stereo_mono_change_count = GET_UINT32 (data); + GST_LOG ("%*s stereo_mono_change_count: %d", depth, "", + stereo_mono_change_count); + for (i = 1; i <= stereo_mono_change_count; i++) { + GST_LOG ("%*s sample_count: %d", depth, "", GET_UINT32 (data)); + GST_LOG ("%*s stereo_flag: %d", depth, "", + ((guint8) GET_UINT8 (data)) & 0x01); + } + } + return TRUE; +} + +gboolean +qtdemux_dump_unknown (GstQTDemux * qtdemux, GstByteReader * data, int depth) +{ + int len; + + len = gst_byte_reader_get_remaining (data); + GST_LOG ("%*s length: %d", depth, "", len); + + GST_MEMDUMP_OBJECT (qtdemux, "unknown atom data", + gst_byte_reader_peek_data_unchecked (data), len); + return TRUE; +} + +static gboolean +qtdemux_node_dump_foreach (GNode * node, gpointer qtdemux) +{ + GstByteReader parser; + guint8 *buffer = (guint8 *) node->data; /* FIXME: move to byte reader */ + guint32 node_length; + guint32 fourcc; + const QtNodeType *type; + int depth; + + node_length = GST_READ_UINT32_BE (buffer); + fourcc = GST_READ_UINT32_LE (buffer + 4); + + g_warn_if_fail (node_length >= 8); + + gst_byte_reader_init (&parser, buffer + 8, node_length - 8); + + type = qtdemux_type_get (fourcc); + + depth = (g_node_depth (node) - 1) * 2; + GST_LOG ("%*s'%" GST_FOURCC_FORMAT "', [%d], %s", + depth, "", GST_FOURCC_ARGS (fourcc), node_length, type->name); + + if (type->dump) { + gboolean ret; + + ret = type->dump (GST_QTDEMUX_CAST (qtdemux), &parser, depth); + + if (!ret) { + GST_WARNING ("%*s not enough data parsing atom %" GST_FOURCC_FORMAT, + depth, "", GST_FOURCC_ARGS (fourcc)); + } + } + + return FALSE; +} + +gboolean +qtdemux_node_dump (GstQTDemux * qtdemux, GNode * node) +{ +#ifndef GST_DISABLE_GST_DEBUG + /* Only traverse/dump if we know it will be outputted in the end */ + if (qtdemux_debug->threshold < GST_LEVEL_LOG) + return TRUE; + + g_node_traverse (node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + qtdemux_node_dump_foreach, qtdemux); +#endif + return TRUE; +} diff --git a/gst/isomp4_1_8/qtdemux_dump.h b/gst/isomp4_1_8/qtdemux_dump.h new file mode 100644 index 0000000000..4234023b40 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_dump.h @@ -0,0 +1,92 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2009> STEricsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_QTDEMUX_DUMP_H__ +#define __GST_QTDEMUX_DUMP_H__ + +#include +#include + +G_BEGIN_DECLS + gboolean qtdemux_dump_mvhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_tkhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_elst (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_mdhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_hdlr (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_vmhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_dref (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stsd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stts (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stss (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stps (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stsc (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stsz (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_stco (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_co64 (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_dcom (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_cmvd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_ctts (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_cslg (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_mfro (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_mfhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_tfra (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_tfhd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_trun (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_trex (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_mehd (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_sdtp (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_tfdt (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_unknown (GstQTDemux * qtdemux, GstByteReader * data, + int depth); +gboolean qtdemux_dump_svmi (GstQTDemux *qtdemux, GstByteReader *data, + int depth); + +gboolean qtdemux_node_dump (GstQTDemux * qtdemux, GNode * node); + +G_END_DECLS +#endif /* __GST_QTDEMUX_DUMP_H__ */ diff --git a/gst/isomp4_1_8/qtdemux_lang.c b/gst/isomp4_1_8/qtdemux_lang.c new file mode 100644 index 0000000000..59a78d7e9b --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_lang.c @@ -0,0 +1,205 @@ +/* GStreamer Quicktime/ISO demuxer language utility functions + * Copyright (C) 2010 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "qtdemux.h" +#include "qtdemux_lang.h" + +#include + +/* http://developer.apple.com/mac/library/documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */ + +static const gchar qt_lang_map[][4] = { + +/* 000 English + * 001 French + * 002 German + * 003 Italian + * 004 Dutch + * 005 Swedish + * 006 Spanish + * 007 Danish + * 008 Portuguese + * 009 Norwegian + */ + "eng", "fre", "deu", "ita", "nld", "swe", "spa", "dan", "por", "nor", + +/* 010 Hebrew + * 011 Japanese + * 012 Arabic + * 013 Finnish + * 014 Greek + * 015 Icelandic + * 016 Maltese + * 017 Turkish + * 018 Croatian + * 019 Traditional Chinese (ISO 639-2 can't express script differences, so zho) + */ + "heb", "jpn", "ara", "fin", "ell", "isl", "mlt", "tur", "hrv", "zho", + +/* 020 Urdu + * 021 Hindi + * 022 Thai + * 023 Korean + * 024 Lithuanian + * 025 Polish + * 026 Hungarian + * 027 Estonian + * 028 Latvian / Lettish + * 029 Lappish / Saamish (used code for Nothern Sami) + */ + "urd", "hin", "tha", "kor", "lit", "pol", "hun", "est", "lav", "sme", + +/* 030 Faeroese + * 031 Farsi + * 032 Russian + * 033 Simplified Chinese (ISO 639-2 can't express script differences, so zho) + * 034 Flemish (no ISO 639-2 code, used Dutch code) + * 035 Irish + * 036 Albanian + * 037 Romanian + * 038 Czech + * 039 Slovak + */ + "fao", "fas", "rus", "zho", "nld", "gle", "sqi", "ron", "ces", "slk", + +/* 040 Slovenian + * 041 Yiddish + * 042 Serbian + * 043 Macedonian + * 044 Bulgarian + * 045 Ukrainian + * 046 Byelorussian + * 047 Uzbek + * 048 Kazakh + * 049 Azerbaijani + */ + "slv", "yid", "srp", "mkd", "bul", "ukr", "bel", "uzb", "kaz", "aze", + +/* 050 AzerbaijanAr (presumably script difference? used aze here) + * 051 Armenian + * 052 Georgian + * 053 Moldavian + * 054 Kirghiz + * 055 Tajiki + * 056 Turkmen + * 057 Mongolian + * 058 MongolianCyr (presumably script difference? used mon here) + * 059 Pashto + */ + + "aze", "hye", "kat", "mol", "kir", "tgk", "tuk", "mon", "mon", "pus", + + +/* 060 Kurdish + * 061 Kashmiri + * 062 Sindhi + * 063 Tibetan + * 064 Nepali + * 065 Sanskrit + * 066 Marathi + * 067 Bengali + * 068 Assamese + * 069 Gujarati + */ + "kur", "kas", "snd", "bod", "nep", "san", "mar", "ben", "asm", "guj", + +/* 070 Punjabi + * 071 Oriya + * 072 Malayalam + * 073 Kannada + * 074 Tamil + * 075 Telugu + * 076 Sinhalese + * 077 Burmese + * 078 Khmer + * 079 Lao + */ + "pan", "ori", "mal", "kan", "tam", "tel", "sin", "mya", "khm", "lao", + +/* 080 Vietnamese + * 081 Indonesian + * 082 Tagalog + * 083 MalayRoman + * 084 MalayArabic + * 085 Amharic + * 087 Galla (same as Oromo?) + * 087 Oromo + * 088 Somali + * 089 Swahili + */ + "vie", "ind", "tgl", "msa", "msa", "amh", "orm", "orm", "som", "swa", + +/* 090 Ruanda + * 091 Rundi + * 092 Chewa + * 093 Malagasy + * 094 Esperanto + * 095 --- + * 096 --- + * 097 --- + * 098 --- + * 099 --- + */ + "kin", "run", "nya", "mlg", "ep", "und", "und", "und", "und", "und", + +/* 100-109 --- + * 110-119 --- + */ + "und", "und", "und", "und", "und", "und", "und", "und", "und", "und", + "und", "und", "und", "und", "und", "und", "und", "und", "und", "und", + +/* 120-127 --- + * 128 Welsh + * 129 Basque + */ + "und", "und", "und", "und", "und", "und", "und", "und", "cym", "eus", + +/* 130 Catalan + * 131 Latin + * 132 Quechua + * 133 Guarani + * 134 Aymara + * 135 Tatar + * 136 Uighur + * 137 Dzongkha + * 138 JavaneseRom + */ + "cat", "lat", "que", "grn", "aym", "tat", "uig", "dzo", "jav" +}; + +/* map quicktime language code to ISO-639-2T id, returns "und" if unknown */ +void +qtdemux_lang_map_qt_code_to_iso (gchar id[4], guint16 qt_lang_code) +{ + const gchar *iso_code; + + g_assert (qt_lang_code < 0x400); + + if (qt_lang_code < G_N_ELEMENTS (qt_lang_map)) + iso_code = qt_lang_map[qt_lang_code]; + else + iso_code = "und"; + + GST_DEBUG ("mapped quicktime language code %u to ISO 639-2T code '%s'", + qt_lang_code, iso_code); + + memcpy (id, iso_code, 4); + + g_assert (id[3] == '\0'); +} diff --git a/gst/isomp4_1_8/qtdemux_lang.h b/gst/isomp4_1_8/qtdemux_lang.h new file mode 100644 index 0000000000..707c5f7213 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_lang.h @@ -0,0 +1,31 @@ +/* GStreamer Quicktime/ISO demuxer language utility functions + * Copyright (C) 2010 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_QTDEMUX_LANG_H__ +#define __GST_QTDEMUX_LANG_H__ + +G_BEGIN_DECLS + +#include + +void qtdemux_lang_map_qt_code_to_iso (gchar id[4], guint16 qt_lang_code); + +G_END_DECLS + +#endif /* __GST_QTDEMUX_LANG_H__ */ diff --git a/gst/isomp4_1_8/qtdemux_types.c b/gst/isomp4_1_8/qtdemux_types.c new file mode 100644 index 0000000000..934703a665 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_types.c @@ -0,0 +1,227 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "qtdemux_types.h" +#include "qtdemux_dump.h" +#include "fourcc.h" + +static const QtNodeType qt_node_types[] = { + {FOURCC_moov, "movie", QT_FLAG_CONTAINER,}, + {FOURCC_mvhd, "movie header", 0, + qtdemux_dump_mvhd}, + {FOURCC_clip, "clipping", QT_FLAG_CONTAINER,}, + {FOURCC_trak, "track", QT_FLAG_CONTAINER,}, + {FOURCC_udta, "user data", QT_FLAG_CONTAINER,}, /* special container */ + {FOURCC_ctab, "color table", 0,}, + {FOURCC_tkhd, "track header", 0, + qtdemux_dump_tkhd}, + {FOURCC_crgn, "clipping region", 0,}, + {FOURCC_matt, "track matte", QT_FLAG_CONTAINER,}, + {FOURCC_kmat, "compressed matte", 0,}, + {FOURCC_edts, "edit", QT_FLAG_CONTAINER,}, + {FOURCC_elst, "edit list", 0, + qtdemux_dump_elst}, + {FOURCC_load, "track load settings", 0,}, + {FOURCC_tref, "track reference", QT_FLAG_CONTAINER,}, + {FOURCC_imap, "track input map", QT_FLAG_CONTAINER,}, + {FOURCC___in, "track input", 0,}, /* special container */ + {FOURCC___ty, "input type", 0,}, + {FOURCC_mdia, "media", QT_FLAG_CONTAINER}, + {FOURCC_mdhd, "media header", 0, + qtdemux_dump_mdhd}, + {FOURCC_hdlr, "handler reference", 0, + qtdemux_dump_hdlr}, + {FOURCC_minf, "media information", QT_FLAG_CONTAINER}, + {FOURCC_vmhd, "video media information", 0, + qtdemux_dump_vmhd}, + {FOURCC_smhd, "sound media information", 0}, + {FOURCC_gmhd, "base media information header", 0}, + {FOURCC_gmin, "base media info", 0}, + {FOURCC_dinf, "data information", QT_FLAG_CONTAINER}, + {FOURCC_dref, "data reference", 0, + qtdemux_dump_dref}, + {FOURCC_stbl, "sample table", QT_FLAG_CONTAINER}, + {FOURCC_stsd, "sample description", 0, + qtdemux_dump_stsd}, + {FOURCC_stts, "time-to-sample", 0, + qtdemux_dump_stts}, + {FOURCC_stps, "partial sync sample", 0, + qtdemux_dump_stps}, + {FOURCC_stss, "sync sample", 0, + qtdemux_dump_stss}, + {FOURCC_stsc, "sample-to-chunk", 0, + qtdemux_dump_stsc}, + {FOURCC_stsz, "sample size", 0, + qtdemux_dump_stsz}, + {FOURCC_stco, "chunk offset", 0, + qtdemux_dump_stco}, + {FOURCC_co64, "64-bit chunk offset", 0, + qtdemux_dump_co64}, + {FOURCC_vide, "video media", 0}, + {FOURCC_cmov, "compressed movie", QT_FLAG_CONTAINER}, + {FOURCC_dcom, "compressed data", 0, qtdemux_dump_dcom}, + {FOURCC_cmvd, "compressed movie data", 0, qtdemux_dump_cmvd}, + {FOURCC_hint, "hint", 0,}, + {FOURCC_mp4a, "mp4a", 0,}, + {FOURCC_mp4v, "mp4v", 0,}, + {FOURCC_mjp2, "mjp2", 0,}, + {FOURCC_mhdr, "mhdr", QT_FLAG_CONTAINER,}, + {FOURCC_jp2h, "jp2h", QT_FLAG_CONTAINER,}, + {FOURCC_colr, "colr", 0,}, + {FOURCC_fiel, "fiel", 0,}, + {FOURCC_jp2x, "jp2x", 0,}, + {FOURCC_alac, "alac", 0,}, + {FOURCC_wave, "wave", QT_FLAG_CONTAINER}, + {FOURCC_appl, "appl", QT_FLAG_CONTAINER}, + {FOURCC_esds, "esds", 0}, + {FOURCC_hnti, "hnti", QT_FLAG_CONTAINER}, + {FOURCC_rtp_, "rtp ", 0, qtdemux_dump_unknown}, + {FOURCC_sdp_, "sdp ", 0, qtdemux_dump_unknown}, + {FOURCC_meta, "meta", 0, qtdemux_dump_unknown}, + {FOURCC_ilst, "ilst", QT_FLAG_CONTAINER,}, + {FOURCC__nam, "Name", QT_FLAG_CONTAINER,}, + {FOURCC_titl, "Title", QT_FLAG_CONTAINER,}, + {FOURCC__ART, "Artist", QT_FLAG_CONTAINER,}, + {FOURCC_aART, "Album Artist", QT_FLAG_CONTAINER,}, + {FOURCC_auth, "Author", QT_FLAG_CONTAINER,}, + {FOURCC_perf, "Performer", QT_FLAG_CONTAINER,}, + {FOURCC__wrt, "Writer", QT_FLAG_CONTAINER,}, + {FOURCC__grp, "Grouping", QT_FLAG_CONTAINER,}, + {FOURCC__alb, "Album", QT_FLAG_CONTAINER,}, + {FOURCC_albm, "Album", QT_FLAG_CONTAINER,}, + {FOURCC__day, "Date", QT_FLAG_CONTAINER,}, + {FOURCC__cpy, "Copyright", QT_FLAG_CONTAINER,}, + {FOURCC__cmt, "Comment", QT_FLAG_CONTAINER,}, + {FOURCC__des, "Description", QT_FLAG_CONTAINER,}, + {FOURCC_desc, "Description", QT_FLAG_CONTAINER,}, + {FOURCC_dscp, "Description", QT_FLAG_CONTAINER,}, + {FOURCC__lyr, "Lyrics", QT_FLAG_CONTAINER,}, + {FOURCC__req, "Requirement", QT_FLAG_CONTAINER,}, + {FOURCC__enc, "Encoder", QT_FLAG_CONTAINER,}, + {FOURCC_gnre, "Genre", QT_FLAG_CONTAINER,}, + {FOURCC_trkn, "Track Number", QT_FLAG_CONTAINER,}, + {FOURCC_disc, "Disc Number", QT_FLAG_CONTAINER,}, + {FOURCC_disk, "Disc Number", QT_FLAG_CONTAINER,}, + {FOURCC_cprt, "Copyright", QT_FLAG_CONTAINER,}, + {FOURCC_cpil, "Compilation", QT_FLAG_CONTAINER,}, + {FOURCC_pgap, "Gapless", QT_FLAG_CONTAINER,}, + {FOURCC_pcst, "Podcast", QT_FLAG_CONTAINER,}, + {FOURCC_tmpo, "Tempo", QT_FLAG_CONTAINER,}, + {FOURCC_covr, "Cover", QT_FLAG_CONTAINER,}, + {FOURCC_sonm, "Sort Title", QT_FLAG_CONTAINER,}, + {FOURCC_soal, "Sort Album", QT_FLAG_CONTAINER,}, + {FOURCC_soar, "Sort Artist", QT_FLAG_CONTAINER,}, + {FOURCC_soaa, "Sort Album Artist", QT_FLAG_CONTAINER,}, + {FOURCC_soco, "Sort Composer", QT_FLAG_CONTAINER,}, + {FOURCC_sosn, "Sort TV Show", QT_FLAG_CONTAINER,}, + {FOURCC_tvsh, "TV Show", QT_FLAG_CONTAINER,}, + {FOURCC_tven, "TV Episode ID", QT_FLAG_CONTAINER,}, + {FOURCC_tvsn, "TV Season Number", QT_FLAG_CONTAINER,}, + {FOURCC_tves, "TV Episode Number", QT_FLAG_CONTAINER,}, + {FOURCC_keyw, "Keywords", QT_FLAG_CONTAINER,}, + {FOURCC_kywd, "Keywords", QT_FLAG_CONTAINER,}, + {FOURCC__too, "Encoder", QT_FLAG_CONTAINER,}, + {FOURCC_____, "----", QT_FLAG_CONTAINER,}, + {FOURCC_data, "data", 0, qtdemux_dump_unknown}, + {FOURCC_free, "free", 0,}, + {FOURCC_SVQ3, "SVQ3", 0,}, + {FOURCC_rmra, "rmra", QT_FLAG_CONTAINER,}, + {FOURCC_rmda, "rmda", QT_FLAG_CONTAINER,}, + {FOURCC_rdrf, "rdrf", 0,}, + {FOURCC__gen, "Custom Genre", QT_FLAG_CONTAINER,}, + {FOURCC_ctts, "Composition time to sample", 0, qtdemux_dump_ctts}, + {FOURCC_cslg, "Composition Shift Least Greatest", 0, qtdemux_dump_cslg}, + {FOURCC_XiTh, "XiTh", 0}, + {FOURCC_XdxT, "XdxT", 0}, + {FOURCC_loci, "loci", 0}, + {FOURCC_clsf, "clsf", 0}, + {FOURCC_mfra, "movie fragment random access", + QT_FLAG_CONTAINER,}, + {FOURCC_tfra, "track fragment random access", 0, + qtdemux_dump_tfra}, + {FOURCC_mfro, "movie fragment random access offset", 0, + qtdemux_dump_mfro}, + {FOURCC_moof, "movie fragment", QT_FLAG_CONTAINER,}, + {FOURCC_mfhd, "movie fragment header", 0, qtdemux_dump_mfhd}, + {FOURCC_traf, "track fragment", QT_FLAG_CONTAINER,}, + {FOURCC_tfhd, "track fragment header", 0, + qtdemux_dump_tfhd}, + {FOURCC_sdtp, "independent and disposable samples", 0, + qtdemux_dump_sdtp}, + {FOURCC_trun, "track fragment run", 0, qtdemux_dump_trun}, + {FOURCC_mdat, "moovie data", 0, qtdemux_dump_unknown}, + {FOURCC_trex, "moovie data", 0, qtdemux_dump_trex}, + {FOURCC_mvex, "mvex", QT_FLAG_CONTAINER,}, + {FOURCC_mehd, "movie extends header", 0, + qtdemux_dump_mehd}, + {FOURCC_ovc1, "ovc1", 0}, + {FOURCC_owma, "owma", 0}, + {FOURCC_avcC, "AV codec configuration container", 0}, + {FOURCC_avc1, "AV codec configuration v1", 0}, + {FOURCC_avc3, "AV codec configuration v3", 0}, + {FOURCC_mp4s, "VOBSUB codec configuration", 0}, + {FOURCC_hvc1, "HEVC codec configuration", 0}, + {FOURCC_hev1, "HEVC codec configuration", 0}, + {FOURCC_hvcC, "HEVC codec configuration container", 0}, + {FOURCC_tfdt, "Track fragment decode time", 0, qtdemux_dump_tfdt}, + {FOURCC_chap, "Chapter Reference"}, + {FOURCC_btrt, "Bitrate information", 0}, + {FOURCC_frma, "Audio codec format", 0}, + {FOURCC_name, "name", 0}, + {FOURCC_mean, "mean", 0}, + {FOURCC_svmi, "Stereoscopic Video Media Information", 0, + qtdemux_dump_svmi}, + {FOURCC_scdi, "Stereoscopic Camera and Display Information", 0, + qtdemux_dump_unknown}, + {FOURCC_saiz, "sample auxiliary information sizes", 0}, + {FOURCC_saio, "sample auxiliary information offsets", 0}, + {FOURCC_encv, "encrypted visual sample entry", 0}, + {FOURCC_enca, "encrypted audio sample entry", 0}, + {FOURCC_enct, "encrypted text sample entry", 0}, + {FOURCC_encs, "encrypted system sample entry", 0}, + {FOURCC_sinf, "protection scheme information", QT_FLAG_CONTAINER}, + {FOURCC_frma, "original format", 0}, + {FOURCC_schm, "scheme type", 0}, + {FOURCC_schi, "scheme information", QT_FLAG_CONTAINER}, + {FOURCC_pssh, "protection system specific header", 0}, + {FOURCC_tenc, "track encryption", 0}, + {FOURCC_senc, "Sample encryption", 0,}, + {FOURCC_ec_3, "ec-3 sample entry", 0, qtdemux_dump_unknown}, + {FOURCC_mmth, "MMT hint sample entry", 0}, + {0, "unknown", 0,}, +}; + +static const int n_qt_node_types = + sizeof (qt_node_types) / sizeof (qt_node_types[0]); + +const QtNodeType * +qtdemux_type_get (guint32 fourcc) +{ + int i; + + for (i = 0; i < n_qt_node_types; i++) { + if (G_UNLIKELY (qt_node_types[i].fourcc == fourcc)) + return qt_node_types + i; + } + + GST_WARNING ("unknown QuickTime node type %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + + return qt_node_types + n_qt_node_types - 1; +} diff --git a/gst/isomp4_1_8/qtdemux_types.h b/gst/isomp4_1_8/qtdemux_types.h new file mode 100644 index 0000000000..43ef77c3a0 --- /dev/null +++ b/gst/isomp4_1_8/qtdemux_types.h @@ -0,0 +1,83 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2009> STEricsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_QTDEMUX_TYPES_H__ +#define __GST_QTDEMUX_TYPES_H__ + +#include +#include + +#include "qtdemux.h" + +G_BEGIN_DECLS + +typedef gboolean (*QtDumpFunc) (GstQTDemux * qtdemux, GstByteReader * data, + int depth); + +typedef struct _QtNodeType QtNodeType; + +#define QT_UINT32(a) (GST_READ_UINT32_BE(a)) +#define QT_UINT24(a) (GST_READ_UINT32_BE(a) >> 8) +#define QT_UINT16(a) (GST_READ_UINT16_BE(a)) +#define QT_UINT8(a) (GST_READ_UINT8(a)) +#define QT_FP32(a) ((GST_READ_UINT32_BE(a))/65536.0) +#define QT_SFP32(a) (((gint)(GST_READ_UINT32_BE(a)))/65536.0) +#define QT_FP16(a) ((GST_READ_UINT16_BE(a))/256.0) +#define QT_FOURCC(a) (GST_READ_UINT32_LE(a)) +#define QT_UINT64(a) ((((guint64)QT_UINT32(a))<<32)|QT_UINT32(((guint8 *)a)+4)) + +typedef enum { + QT_FLAG_NONE = (0), + QT_FLAG_CONTAINER = (1 << 0) +} QtFlags; + +struct _QtNodeType { + guint32 fourcc; + const gchar *name; + QtFlags flags; + QtDumpFunc dump; +}; + +enum TfFlags +{ + TF_BASE_DATA_OFFSET = 0x000001, /* base-data-offset-present */ + TF_SAMPLE_DESCRIPTION_INDEX = 0x000002, /* sample-description-index-present */ + TF_DEFAULT_SAMPLE_DURATION = 0x000008, /* default-sample-duration-present */ + TF_DEFAULT_SAMPLE_SIZE = 0x000010, /* default-sample-size-present */ + TF_DEFAULT_SAMPLE_FLAGS = 0x000020, /* default-sample-flags-present */ + TF_DURATION_IS_EMPTY = 0x010000, /* duration-is-empty */ + TF_DEFAULT_BASE_IS_MOOF = 0x020000 /* default-base-is-moof */ +}; + +enum TrFlags +{ + TR_DATA_OFFSET = 0x000001, /* data-offset-present */ + TR_FIRST_SAMPLE_FLAGS = 0x000004, /* first-sample-flags-present */ + TR_SAMPLE_DURATION = 0x000100, /* sample-duration-present */ + TR_SAMPLE_SIZE = 0x000200, /* sample-size-present */ + TR_SAMPLE_FLAGS = 0x000400, /* sample-flags-present */ + TR_COMPOSITION_TIME_OFFSETS = 0x000800 /* sample-composition-time-offsets-presents */ +}; + +const QtNodeType *qtdemux_type_get (guint32 fourcc); + +G_END_DECLS + +#endif /* __GST_QTDEMUX_TYPES_H__ */ diff --git a/gst/isomp4_1_8/qtpalette.h b/gst/isomp4_1_8/qtpalette.h new file mode 100644 index 0000000000..a41e9911cc --- /dev/null +++ b/gst/isomp4_1_8/qtpalette.h @@ -0,0 +1,137 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#ifndef __GST_QTPALLETE_H__ +#define __GST_QTPALLETE_H__ + +#include + +G_BEGIN_DECLS + +static const guint32 ff_qt_default_palette_2[2] = { + 0xffffff, 0x000000 +}; + +static const guint32 ff_qt_default_palette_4[4] = { + 0x93655e, 0xffffff, 0xdfd0ab, 0x000000 +}; + +static const guint32 ff_qt_default_palette_16[16] = { + 0xfffbff, 0xefd9bb, 0xe8c9b1, 0x93655e, + 0xfcdee8, 0x9d8891, 0xffffff, 0xffffff, + 0xffffff, 0x474837, 0x7a5e55, 0xdfd0ab, + 0xfffbf9, 0xe8cac5, 0x8a7c77, 0x000000 +}; +static const guint32 ff_qt_default_palette_256[256] = { + 0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00, + 0xFFCCFF, 0xFFCCCC, 0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00, + 0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966, 0xFF9933, 0xFF9900, + 0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600, + 0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300, + 0xFF00FF, 0xFF00CC, 0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000, + 0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66, 0xCCFF33, 0xCCFF00, + 0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00, + 0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900, + 0xCC66FF, 0xCC66CC, 0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600, + 0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366, 0xCC3333, 0xCC3300, + 0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000, + 0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00, + 0x99CCFF, 0x99CCCC, 0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00, + 0x9999FF, 0x9999CC, 0x999999, 0x999966, 0x999933, 0x999900, + 0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600, + 0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300, + 0x9900FF, 0x9900CC, 0x990099, 0x990066, 0x990033, 0x990000, + 0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66, 0x66FF33, 0x66FF00, + 0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00, + 0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900, + 0x6666FF, 0x6666CC, 0x666699, 0x666666, 0x666633, 0x666600, + 0x6633FF, 0x6633CC, 0x663399, 0x663366, 0x663333, 0x663300, + 0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000, + 0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00, + 0x33CCFF, 0x33CCCC, 0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00, + 0x3399FF, 0x3399CC, 0x339999, 0x339966, 0x339933, 0x339900, + 0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600, + 0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300, + 0x3300FF, 0x3300CC, 0x330099, 0x330066, 0x330033, 0x330000, + 0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66, 0x00FF33, 0x00FF00, + 0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00, + 0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900, + 0x0066FF, 0x0066CC, 0x006699, 0x006666, 0x006633, 0x006600, + 0x0033FF, 0x0033CC, 0x003399, 0x003366, 0x003333, 0x003300, + 0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033, 0xEE0000, + 0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000, + 0x440000, 0x220000, 0x110000, 0x00EE00, 0x00DD00, 0x00BB00, + 0x00AA00, 0x008800, 0x007700, 0x005500, 0x004400, 0x002200, + 0x001100, 0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088, + 0x000077, 0x000055, 0x000044, 0x000022, 0x000011, 0xEEEEEE, + 0xDDDDDD, 0xBBBBBB, 0xAAAAAA, 0x888888, 0x777777, 0x555555, + 0x444444, 0x222222, 0x111111, 0x000000 +}; + +static const guint32 ff_qt_grayscale_palette_16[16] = { + 0xffffff, 0xeeeeee, 0xdddddd, 0xcccccc, + 0xbbbbbb, 0xaaaaaa, 0x999999, 0x888888, + 0x777777, 0x666666, 0x555555, 0x444444, + 0x333333, 0x222222, 0x111111, 0x000000 +}; + +static const guint32 ff_qt_grayscale_palette_256[256] = { + 0xffffff, 0xfefefe, 0xfdfdfd, 0xfcfcfc, 0xfbfbfb, 0xfafafa, 0xf9f9f9, + 0xf8f8f8, 0xf7f7f7, 0xf6f6f6, 0xf5f5f5, 0xf4f4f4, 0xf3f3f3, 0xf2f2f2, + 0xf1f1f1, 0xf0f0f0, 0xefefef, 0xeeeeee, 0xededed, 0xececec, 0xebebeb, + 0xeaeaea, 0xe9e9e9, 0xe8e8e8, 0xe7e7e7, 0xe6e6e6, 0xe5e5e5, 0xe4e4e4, + 0xe3e3e3, 0xe2e2e2, 0xe1e1e1, 0xe0e0e0, 0xdfdfdf, 0xdedede, 0xdddddd, + 0xdcdcdc, 0xdbdbdb, 0xdadada, 0xd9d9d9, 0xd8d8d8, 0xd7d7d7, 0xd6d6d6, + 0xd5d5d5, 0xd4d4d4, 0xd3d3d3, 0xd2d2d2, 0xd1d1d1, 0xd0d0d0, 0xcfcfcf, + 0xcecece, 0xcdcdcd, 0xcccccc, 0xcbcbcb, 0xcacaca, 0xc9c9c9, 0xc8c8c8, + 0xc7c7c7, 0xc6c6c6, 0xc5c5c5, 0xc4c4c4, 0xc3c3c3, 0xc2c2c2, 0xc1c1c1, + 0xc0c0c0, 0xbfbfbf, 0xbebebe, 0xbdbdbd, 0xbcbcbc, 0xbbbbbb, 0xbababa, + 0xb9b9b9, 0xb8b8b8, 0xb7b7b7, 0xb6b6b6, 0xb5b5b5, 0xb4b4b4, 0xb3b3b3, + 0xb2b2b2, 0xb1b1b1, 0xb0b0b0, 0xafafaf, 0xaeaeae, 0xadadad, 0xacacac, + 0xababab, 0xaaaaaa, 0xa9a9a9, 0xa8a8a8, 0xa7a7a7, 0xa6a6a6, 0xa5a5a5, + 0xa4a4a4, 0xa3a3a3, 0xa2a2a2, 0xa1a1a1, 0xa0a0a0, 0x9f9f9f, 0x9e9e9e, + 0x9d9d9d, 0x9c9c9c, 0x9b9b9b, 0x9a9a9a, 0x999999, 0x989898, 0x979797, + 0x969696, 0x959595, 0x949494, 0x939393, 0x929292, 0x919191, 0x909090, + 0x8f8f8f, 0x8e8e8e, 0x8d8d8d, 0x8c8c8c, 0x8b8b8b, 0x8a8a8a, 0x898989, + 0x888888, 0x878787, 0x868686, 0x858585, 0x848484, 0x838383, 0x828282, + 0x818181, 0x808080, 0x7f7f7f, 0x7e7e7e, 0x7d7d7d, 0x7c7c7c, 0x7b7b7b, + 0x7a7a7a, 0x797979, 0x787878, 0x777777, 0x767676, 0x757575, 0x747474, + 0x737373, 0x727272, 0x717171, 0x707070, 0x6f6f6f, 0x6e6e6e, 0x6d6d6d, + 0x6c6c6c, 0x6b6b6b, 0x6a6a6a, 0x696969, 0x686868, 0x676767, 0x666666, + 0x656565, 0x646464, 0x636363, 0x626262, 0x616161, 0x606060, 0x5f5f5f, + 0x5e5e5e, 0x5d5d5d, 0x5c5c5c, 0x5b5b5b, 0x5a5a5a, 0x595959, 0x585858, + 0x575757, 0x565656, 0x555555, 0x545454, 0x535353, 0x525252, 0x515151, + 0x505050, 0x4f4f4f, 0x4e4e4e, 0x4d4d4d, 0x4c4c4c, 0x4b4b4b, 0x4a4a4a, + 0x494949, 0x484848, 0x474747, 0x464646, 0x454545, 0x444444, 0x434343, + 0x424242, 0x414141, 0x404040, 0x3f3f3f, 0x3e3e3e, 0x3d3d3d, 0x3c3c3c, + 0x3b3b3b, 0x3a3a3a, 0x393939, 0x383838, 0x373737, 0x363636, 0x353535, + 0x343434, 0x333333, 0x323232, 0x313131, 0x303030, 0x2f2f2f, 0x2e2e2e, + 0x2d2d2d, 0x2c2c2c, 0x2b2b2b, 0x2a2a2a, 0x292929, 0x282828, 0x272727, + 0x262626, 0x252525, 0x242424, 0x232323, 0x222222, 0x212121, 0x202020, + 0x1f1f1f, 0x1e1e1e, 0x1d1d1d, 0x1c1c1c, 0x1b1b1b, 0x1a1a1a, 0x191919, + 0x181818, 0x171717, 0x161616, 0x151515, 0x141414, 0x131313, 0x121212, + 0x111111, 0x101010, 0x0f0f0f, 0x0e0e0e, 0x0d0d0d, 0x0c0c0c, 0x0b0b0b, + 0x0a0a0a, 0x090909, 0x080808, 0x070707, 0x060606, 0x050505, 0x040404, + 0x030303, 0x020202, 0x010101, 0x000000 +}; + +G_END_DECLS + +#endif /* __GST_QTPALETTE_H__ */ diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index 1b95451fb4..74ff83a3e8 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -83,12 +83,17 @@ enum PROP_0, PROP_METADATA, PROP_STREAMINFO, - PROP_MAX_GAP_TIME + PROP_MAX_GAP_TIME, + PROP_THUMBNAIL_MODE }; -#define DEFAULT_MAX_GAP_TIME (2 * GST_SECOND) +#define DEFAULT_MAX_GAP_TIME (60 * GST_SECOND) #define INVALID_DATA_THRESHOLD (2 * 1024 * 1024) +#define IS_HIGH_SPEED_TRICK(e) \ + (((e)->is_higher_than_FHD && (e)->common.segment.rate == 2) \ + ||((e)->common.segment.rate < 1.0)||((e)->common.segment.rate > 2.0)) + static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -174,12 +179,8 @@ static GstCaps static void gst_matroska_demux_reset (GstElement * element); static gboolean perform_seek_to_offset (GstMatroskaDemux * demux, gdouble rate, guint64 offset, guint32 seqnum, GstSeekFlags flags); - -/* gobject functions */ -static void gst_matroska_demux_set_property (GObject * object, - guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_matroska_demux_get_property (GObject * object, - guint prop_id, GValue * value, GParamSpec * pspec); +static gboolean perform_seek_to_keyframe_offset (GstMatroskaDemux * + demux, gdouble rate, guint64 offset); GType gst_matroska_demux_get_type (void); #define parent_class gst_matroska_demux_parent_class @@ -206,15 +207,6 @@ gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) gobject_class->finalize = gst_matroska_demux_finalize; - gobject_class->get_property = gst_matroska_demux_get_property; - gobject_class->set_property = gst_matroska_demux_set_property; - - g_object_class_install_property (gobject_class, PROP_MAX_GAP_TIME, - g_param_spec_uint64 ("max-gap-time", "Maximum gap time", - "The demuxer sends out segment events for skipping " - "gaps longer than this (0 = disabled).", 0, G_MAXUINT64, - DEFAULT_MAX_GAP_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_matroska_demux_change_state); gstelement_class->send_event = @@ -266,6 +258,7 @@ gst_matroska_demux_init (GstMatroskaDemux * demux) GST_OBJECT_FLAG_SET (demux, GST_ELEMENT_FLAG_INDEXABLE); demux->flowcombiner = gst_flow_combiner_new (); + demux->thumbnail_mode = FALSE; /* finish off */ gst_matroska_demux_reset (GST_ELEMENT (demux)); @@ -316,6 +309,7 @@ gst_matroska_demux_reset (GstElement * element) demux->requested_seek_time = GST_CLOCK_TIME_NONE; demux->seek_offset = -1; demux->building_index = FALSE; + demux->seek_rate = 1.0; if (demux->seek_event) { gst_event_unref (demux->seek_event); demux->seek_event = NULL; @@ -329,10 +323,20 @@ gst_matroska_demux_reset (GstElement * element) demux->new_segment = NULL; } + demux->h264_codec = -1; demux->invalid_duration = FALSE; demux->cached_length = G_MAXUINT64; + demux->keyframe_push_done = FALSE; + demux->is_higher_than_FHD = FALSE; + demux->has_audio = FALSE; + demux->audio_frame_push_ref = 0; + demux->audio_frame_push_check = 0; + demux->audio_frame_push_done = FALSE; + demux->is_rate_changed = TRUE; + demux->scan_next_cluster_push = FALSE; + demux->is_flushing = FALSE; gst_flow_combiner_clear (demux->flowcombiner); } @@ -391,6 +395,7 @@ gst_matroska_demux_add_stream_headers_to_caps (GstMatroskaDemux * demux, g_value_unset (&buf_val); } +#if 0 static GstFlowReturn gst_matroska_demux_parse_colour (GstMatroskaDemux * demux, GstEbmlRead * ebml, GstMatroskaTrackVideoContext * video_context) @@ -571,6 +576,403 @@ gst_matroska_demux_parse_colour (GstMatroskaDemux * demux, GstEbmlRead * ebml, beach: DEBUG_ELEMENT_STOP (demux, ebml, "TrackVideoColour", ret); + + return ret; +} +#endif + +static GstFlowReturn +gst_matroska_demux_parse_masteringmetadata (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstMatroskaTrackVideoContext * videocontext) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "MasteringMetaData"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "MasteringMetaData", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_PRIMARYRCHROMATICITYX:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryRChromaticityX %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryRChromaticityX: %lf", num); + videocontext->primaryR_chromaticityX = num; + break; + } + case GST_MATROSKA_ID_PRIMARYRCHROMATICITYY:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryRChromaticityY %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryRChromaticityY: %lf", num); + videocontext->primaryR_chromaticityY = num; + break; + } + case GST_MATROSKA_ID_PRIMARYGCHROMATICITYX:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryGChromaticityX %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryGChromaticityX: %lf", num); + videocontext->primaryG_chromaticityX = num; + break; + } + case GST_MATROSKA_ID_PRIMARYGCHROMATICITYY:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryGChromaticityY %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryGChromaticityY: %lf", num); + videocontext->primaryG_chromaticityY = num; + break; + } + case GST_MATROSKA_ID_PRIMARYBCHROMATICITYX:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryBChromaticityX %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryBChromaticityX: %lf", num); + videocontext->primaryB_chromaticityX = num; + break; + } + case GST_MATROSKA_ID_PRIMARYBCHROMATICITYY:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, "Invalid PrimaryBChromaticityY %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "PrimaryBChromaticityY: %lf", num); + videocontext->primaryB_chromaticityY = num; + break; + } + case GST_MATROSKA_ID_WHITEPOINTCHROMATICITYX:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, + "Invalid WhitePointChromaticityX %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "WhitePointChromaticityX: %lf", num); + videocontext->white_point_chromaticityX = num; + break; + } + case GST_MATROSKA_ID_WHITEPOINTCHROMATICITYY:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num > 1.0) { + GST_WARNING_OBJECT (demux, + "Invalid WhitePointChromaticityY %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "WhitePointChromaticityY: %lf", num); + videocontext->white_point_chromaticityY = num; + break; + } + case GST_MATROSKA_ID_LUMINANCEMAX:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num >= 10000.0) { + GST_WARNING_OBJECT (demux, "Invalid LuminanceMax %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "LuminanceMax: %lf", num); + videocontext->luminance_max = num; + break; + } + case GST_MATROSKA_ID_LUMINANCEMIN:{ + gdouble num; + + if ((ret = gst_ebml_read_float (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num < 0.0 || num >= 1000.0) { + GST_WARNING_OBJECT (demux, "Invalid LuminanceMin %lf", num); + break; + } + + GST_DEBUG_OBJECT (demux, "LuminanceMin: %lf", num); + videocontext->luminance_min = num; + break; + } + default: + ret = + gst_matroska_read_common_parse_skip (&demux->common, ebml, + "MasteringMetaData", id); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "MasteringMetaData", ret); + + return ret; +} + +static GstFlowReturn +gst_matroska_demux_parse_videocolour (GstMatroskaDemux * demux, + GstEbmlRead * ebml, GstMatroskaTrackVideoContext * videocontext, + gint * valid_color) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "VideoColour"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "VideoColour", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) + break; + + switch (id) { + case GST_MATROSKA_ID_MATRIXCOEFFICIENTS:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 2) { + GST_WARNING_OBJECT (demux, "Invalid MatrixCoefficients 2"); + break; + } + + GST_DEBUG_OBJECT (demux, "MatrixCoefficients: %" G_GUINT64_FORMAT, num); + videocontext->matrix_coefficients = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_BITSPERCHANNEL:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid BitsPerChannel 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "BitsPerChannel: %" G_GUINT64_FORMAT, num); + videocontext->bits_per_channel = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CHROMASUBSAMPLINGHORZ:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, + "ChromaSubsamplingHorz: %" G_GUINT64_FORMAT, num); + videocontext->chroma_subsampling_horz = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CHROMASUBSAMPLINGVERT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, + "ChromaSubsamplingVert: %" G_GUINT64_FORMAT, num); + videocontext->chroma_subsampling_vert = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CBSUBSAMPLINGHORZ:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "CbSubsamplingHorz: %" G_GUINT64_FORMAT, num); + videocontext->cb_subsampling_horz = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CBSUBSAMPLINGVERT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "CbSubsamplingVert: %" G_GUINT64_FORMAT, num); + videocontext->cb_subsampling_vert = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CHROMASITINGHORZ:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "ChromaSitingHorz: %" G_GUINT64_FORMAT, num); + videocontext->chroma_siting_horz = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_CHROMASITINGVERT:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "ChromaSitingVert: %" G_GUINT64_FORMAT, num); + videocontext->chroma_siting_vert = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_RANGE:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 0) { + GST_WARNING_OBJECT (demux, "Invalid Range 0"); + break; + } + + GST_DEBUG_OBJECT (demux, "Range: %" G_GUINT64_FORMAT, num); + videocontext->range = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_TRANSFERCHARACTERISTICS:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 2) { + GST_WARNING_OBJECT (demux, "Invalid TransferCharacteristics 2"); + break; + } + + GST_DEBUG_OBJECT (demux, + "TransferCharacteristics: %" G_GUINT64_FORMAT, num); + videocontext->transfer_characteristics = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_PRIMARIES:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + if (num == 2) { + GST_WARNING_OBJECT (demux, "Invalid Primaries 2"); + break; + } + + GST_DEBUG_OBJECT (demux, "Primaries: %" G_GUINT64_FORMAT, num); + videocontext->primaries = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_MAXCLL:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "MaxCLL: %" G_GUINT64_FORMAT, num); + videocontext->max_CLL = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_MAXFALL:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + GST_DEBUG_OBJECT (demux, "MaxFALL: %" G_GUINT64_FORMAT, num); + videocontext->max_FALL = num; + *valid_color = 1; + break; + } + case GST_MATROSKA_ID_MASTERINGMETADATA:{ + ret = + gst_matroska_demux_parse_masteringmetadata (demux, ebml, + videocontext); + *valid_color = 1; + break; + } + default: + ret = + gst_matroska_read_common_parse_skip (&demux->common, ebml, + "VideoColour", id); + break; + } + } + + DEBUG_ELEMENT_STOP (demux, ebml, "VideoColour", ret); + return ret; } @@ -585,6 +987,7 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, guint32 id, riff_fourcc = 0; guint16 riff_audio_fmt = 0; gchar *codec = NULL; + gint valid_color = 0; DEBUG_ELEMENT_START (demux, ebml, "TrackEntry"); @@ -900,11 +1303,13 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, break; } +#if 0 /* color info */ case GST_MATROSKA_ID_VIDEOCOLOUR:{ ret = gst_matroska_demux_parse_colour (demux, ebml, videocontext); break; } +#endif case GST_MATROSKA_ID_VIDEOSTEREOMODE: { @@ -956,7 +1361,14 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, } break; } - + /* HDR metadata */ + case GST_MATROSKA_ID_VIDEOCOLOUR:{ + ret = + gst_matroska_demux_parse_videocolour (demux, ebml, + videocontext, &valid_color); + videocontext->valid_color = valid_color; + break; + } default: GST_WARNING_OBJECT (demux, "Unknown TrackVideo subelement 0x%x - ignoring", id); @@ -972,6 +1384,11 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, } } + /* For pushing I-frame only in FFx2 trick for UHD */ + if ((videocontext->pixel_width / 16 * videocontext->pixel_height / 16) > + 8704) + demux->is_higher_than_FHD = TRUE; + DEBUG_ELEMENT_STOP (demux, ebml, "TrackVideo", ret); break; } @@ -1348,6 +1765,22 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE, GST_TAG_VIDEO_CODEC, codec, NULL); context->tags_changed = TRUE; + + if (riff_fourcc) { + gchar *format = + g_strdup_printf ("%c%c%c%c", GST_FOURCC_ARGS (riff_fourcc)); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, format, NULL); + g_free (format); + } + + /* V_MS Video Codec is unseekable */ + if (riff_fourcc == 0x44495658 && context->uid == 1836268893) { + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_application (GST_OBJECT_CAST (demux), + gst_structure_new ("GstMessageSeekable", "SEEKABLE", + G_TYPE_BOOLEAN, FALSE, NULL))); + } + g_free (codec); } break; @@ -1357,14 +1790,16 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, GstMatroskaTrackAudioContext *audiocontext = (GstMatroskaTrackAudioContext *) context; - caps = gst_matroska_demux_audio_caps (audiocontext, - context->codec_id, context->codec_priv, context->codec_priv_size, - &codec, &riff_audio_fmt); + if (!demux->thumbnail_mode) + caps = gst_matroska_demux_audio_caps (audiocontext, + context->codec_id, context->codec_priv, context->codec_priv_size, + &codec, &riff_audio_fmt); if (codec) { gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE, GST_TAG_AUDIO_CODEC, codec, NULL); context->tags_changed = TRUE; + demux->has_audio = TRUE; g_free (codec); } break; @@ -1374,8 +1809,15 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, GstMatroskaTrackSubtitleContext *subtitlecontext = (GstMatroskaTrackSubtitleContext *) context; - caps = gst_matroska_demux_subtitle_caps (subtitlecontext, - context->codec_id, context->codec_priv, context->codec_priv_size); + if (!demux->thumbnail_mode) + caps = gst_matroska_demux_subtitle_caps (subtitlecontext, + context->codec_id, context->codec_priv, context->codec_priv_size); + + if (caps != NULL) { + gst_caps_set_simple (caps, "track-num", G_TYPE_INT, + demux->num_t_streams - 1, NULL); + } + break; } @@ -1402,6 +1844,11 @@ gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml, lang = gst_tag_get_language_code (context->language); gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE, GST_TAG_LANGUAGE_CODE, (lang) ? lang : context->language, NULL); + + if (context->name) { + gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE, + GST_TAG_TITLE, context->name, NULL); + } context->tags_changed = TRUE; } @@ -1459,6 +1906,7 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEvent *stream_start; gchar *stream_id; + guint track_num = 0; g_ptr_array_add (demux->common.src, context); context->index = demux->common.num_streams++; @@ -1471,22 +1919,30 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, case GST_MATROSKA_TRACK_TYPE_VIDEO: padname = g_strdup_printf ("video_%u", demux->num_v_streams++); templ = gst_element_class_get_pad_template (klass, "video_%u"); + track_num = demux->num_v_streams; break; case GST_MATROSKA_TRACK_TYPE_AUDIO: padname = g_strdup_printf ("audio_%u", demux->num_a_streams++); templ = gst_element_class_get_pad_template (klass, "audio_%u"); + track_num = demux->num_a_streams; + demux->audio_frame_push_ref |= 1 << context->index; break; case GST_MATROSKA_TRACK_TYPE_SUBTITLE: padname = g_strdup_printf ("subtitle_%u", demux->num_t_streams++); templ = gst_element_class_get_pad_template (klass, "subtitle_%u"); + track_num = demux->num_t_streams; break; default: /* we should already have quit by now */ g_assert_not_reached (); } + /* custom caps */ + gst_caps_set_simple (context->caps, "seekable", G_TYPE_BOOLEAN, + demux->seekable, NULL); + gst_caps_set_simple (context->caps, "trickable", G_TYPE_BOOLEAN, TRUE, NULL); /* the pad in here */ context->pad = gst_pad_new_from_template (templ, padname); @@ -1506,8 +1962,8 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, stream_id = gst_pad_create_stream_id_printf (context->pad, GST_ELEMENT_CAST (demux), - "%03" G_GUINT64_FORMAT ":%03" G_GUINT64_FORMAT, - context->num, context->uid); + "%03" G_GUINT64_FORMAT ":%02d_%03" G_GUINT64_FORMAT, + context->num, track_num, context->uid); stream_start = gst_pad_get_sticky_event (demux->common.sinkpad, GST_EVENT_STREAM_START, 0); @@ -1523,6 +1979,63 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, } stream_start = gst_event_new_stream_start (stream_id); + + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + /* HDR metadata */ + if (videocontext->valid_color) { + GstStructure *color_structure; + + color_structure = gst_structure_new ("hdr-info", + "stream-id", G_TYPE_STRING, stream_id, + "transfer-characteristics", G_TYPE_UINT, + videocontext->transfer_characteristics, "color-primaries", + G_TYPE_UINT, videocontext->primaries, "matrix-coeffs", + G_TYPE_UINT, videocontext->matrix_coefficients, + "display-primaries-x0", G_TYPE_UINT, + (guint) (videocontext->primaryG_chromaticityX * 50000.0), + "display-primaries-x1", G_TYPE_UINT, + (guint) (videocontext->primaryB_chromaticityX * 50000.0), + "display-primaries-x2", G_TYPE_UINT, + (guint) (videocontext->primaryR_chromaticityX * 50000.0), + "display-primaries-y0", G_TYPE_UINT, + (guint) (videocontext->primaryG_chromaticityY * 50000.0), + "display-primaries-y1", G_TYPE_UINT, + (guint) (videocontext->primaryB_chromaticityY * 50000.0), + "display-primaries-y2", G_TYPE_UINT, + (guint) (videocontext->primaryR_chromaticityY * 50000.0), + "white-point-x", G_TYPE_UINT, + (guint) (videocontext->white_point_chromaticityX * 50000.0), + "white-point-y", G_TYPE_UINT, + (guint) (videocontext->white_point_chromaticityY * 50000.0), + "max-display-mastering-luminance", G_TYPE_UINT, + (guint) videocontext->luminance_max, + "min-display-mastering-luminance", G_TYPE_UINT, + (guint) videocontext->luminance_min, "video-full-range-flag", + G_TYPE_BOOLEAN, (videocontext->range == 2 ? TRUE : FALSE), + "max-content-light-level", G_TYPE_INT, + (gint) videocontext->max_CLL, "max-pic-average-light-level", + G_TYPE_INT, (gint) videocontext->max_FALL, NULL); + + /* HACK: following mapping might not be correct + * from specification point of view. It's customized definition */ + if (videocontext->transfer_characteristics == 16) { + gst_structure_set (color_structure, + "hdr-type", G_TYPE_STRING, "hdr10", NULL); + } else if (videocontext->transfer_characteristics == 18) { + gst_structure_set (color_structure, + "hdr-type", G_TYPE_STRING, "hlg", NULL); + } + + GST_DEBUG_OBJECT (demux, + "Posting media-info about HDR: %" GST_PTR_FORMAT, color_structure); + + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_element (GST_OBJECT_CAST (demux), color_structure)); + } + } + g_free (stream_id); if (demux->have_group_id) gst_event_set_group_id (stream_start, demux->group_id); @@ -1531,6 +2044,9 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, stream_flags |= GST_STREAM_FLAG_SPARSE; if (context->flags & GST_MATROSKA_TRACK_DEFAULT) stream_flags |= GST_STREAM_FLAG_SELECT; + else if (!(context->flags & GST_MATROSKA_TRACK_ENABLED)) + stream_flags |= GST_STREAM_FLAG_UNSELECT; + gst_event_set_stream_flags (stream_start, stream_flags); gst_pad_push_event (context->pad, stream_start); gst_pad_set_caps (context->pad, context->caps); @@ -1633,7 +2149,25 @@ gst_matroska_demux_query (GstMatroskaDemux * demux, GstPad * pad, } break; } + case GST_QUERY_CUSTOM:{ + gboolean trickable = TRUE; + GstStructure *s; + + s = (GstStructure *) gst_query_get_structure (query); + if (gst_structure_has_name (s, "custom-trickable")) { + + if (demux->streaming && demux->is_higher_than_FHD) + trickable = FALSE; + + gst_structure_set (s, "trickable", G_TYPE_BOOLEAN, trickable, NULL); + res = TRUE; + break; + } else { + res = gst_pad_query_default (pad, (GstObject *) demux, query); + break; + } + } case GST_QUERY_SEEKING: { GstFormat fmt; @@ -2242,6 +2776,149 @@ gst_matroska_demux_search_pos (GstMatroskaDemux * demux, GstClockTime time) return entry; } +/* scans for a cluster start from @pos in streaming mode, + * return GST_FLOW_OK and cluster position in @pos if found */ +static GstFlowReturn +gst_matroska_demux_search_cluster_push (GstMatroskaDemux * demux, gint64 * pos) +{ + gint64 newpos; + gint cluster_pos; + gint64 orig_offset; + const guint8 *data = NULL; + gsize size; + guint64 length; + guint32 id; + guint needed; + GstFlowReturn ret = GST_FLOW_OK; + GstByteReader reader; + GstBuffer *buffer; + + + orig_offset = demux->common.offset; + newpos = demux->common.offset; + GST_LOG_OBJECT (demux, "searching cluster following offset %" G_GINT64_FORMAT, + orig_offset); + size = gst_adapter_available (demux->common.adapter); + + if (demux->clusters) { + gint64 *cpos; + + cpos = gst_util_array_binary_search (demux->clusters->data, + demux->clusters->len, sizeof (gint64), + (GCompareDataFunc) gst_matroska_cluster_compare, + GST_SEARCH_MODE_AFTER, pos, NULL); + /* sanity check */ + if (cpos) { + GST_DEBUG_OBJECT (demux, + "cluster reported at offset %" G_GINT64_FORMAT, *cpos); + if (demux->common.offset + size - *cpos < 8) { + /* need more data */ + ret = GST_FLOW_EOS; + goto exit; + } + demux->common.offset = *cpos; + ret = gst_matroska_read_common_peek_id_length_push (&demux->common, + GST_ELEMENT_CAST (demux), &id, &length, &needed); + if (ret == GST_FLOW_OK && id == GST_MATROSKA_ID_CLUSTER) { + newpos = *cpos; + goto exit; + } + } + } + + /* read in at newpos and scan for ebml cluster id */ + data = gst_adapter_map (demux->common.adapter, size); + + gst_byte_reader_init (&reader, data, size); + cluster_pos = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff, + GST_MATROSKA_ID_CLUSTER, 0, gst_byte_reader_get_remaining (&reader)); + + if (cluster_pos >= 0) { + int size_to_skip = 0; + newpos += cluster_pos; + + GST_DEBUG_OBJECT (demux, "cluster pos %" G_GINT64_FORMAT, newpos); + + /* prepare resuming at next byte */ + if (!gst_byte_reader_skip (&reader, cluster_pos + 1)) { + GST_DEBUG_OBJECT (demux, "Need more data -> "); + /* need more data */ + ret = GST_FLOW_EOS; + goto exit; + } + GST_DEBUG_OBJECT (demux, + "found cluster ebml id at offset %" G_GINT64_FORMAT, newpos); + /* extra checks whether we really sync'ed to a cluster: + * - either it is the first and only cluster + * - either there is a cluster after this one + * - either cluster length is undefined + */ + + /* ok if first cluster (there may not a subsequent one) */ + if (newpos == demux->first_cluster_offset) { + GST_DEBUG_OBJECT (demux, "cluster is first cluster -> OK"); + demux->common.offset = orig_offset; + /* need more data */ + ret = GST_FLOW_OK; + goto exit; + } + /* Adusting the buffer position to next cluster found */ + size_to_skip = newpos - demux->common.offset; + buffer = gst_adapter_take_buffer (demux->common.adapter, size_to_skip); + gst_buffer_unref (buffer); + + GST_LOG_OBJECT (demux, "Buffer avail: %" G_GSIZE_FORMAT, + gst_adapter_available (demux->common.adapter)); + ret = gst_matroska_read_common_peek_id_length_push (&demux->common, + GST_ELEMENT_CAST (demux), &id, &length, &needed); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (demux, "need more data -> "); + /* need more data */ + ret = GST_FLOW_EOS; + goto exit; + } + + GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d", + length, needed); + /* ok if undefined length or first cluster */ + if (length == GST_EBML_SIZE_UNKNOWN || length == G_MAXUINT64 + || id != GST_MATROSKA_ID_CLUSTER) { + GST_DEBUG_OBJECT (demux, "Invalid cluster ID or undefined length "); + ret = GST_FLOW_ERROR; + goto exit; + } + + /* skip cluster */ + demux->common.offset += length + needed; + ret = gst_matroska_read_common_peek_id_length_push (&demux->common, + GST_ELEMENT_CAST (demux), &id, &length, &needed); + if (ret != GST_FLOW_OK) { + ret = GST_FLOW_EOS; + goto exit; + } + + GST_DEBUG_OBJECT (demux, "next element is %scluster", + id == GST_MATROSKA_ID_CLUSTER ? "" : "not "); + + if (id == GST_MATROSKA_ID_CLUSTER) + ret = GST_FLOW_OK; + + } else { + /* partial cluster id may have been in tail of buffer */ + newpos += MAX (gst_byte_reader_get_remaining (&reader), 4) - 3; + GST_DEBUG_OBJECT (demux, "need more data -> "); + ret = GST_FLOW_EOS; + } + +exit: + + if (data) + gst_adapter_unmap (demux->common.adapter); + demux->common.offset = orig_offset; + *pos = newpos; + return ret; +} + static gboolean gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, GstPad * pad, GstEvent * event) @@ -2263,6 +2940,9 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, g_return_val_if_fail (event != NULL, FALSE); + demux->skip_find_next_keyframe = FALSE; + demux->is_rate_changed = TRUE; + if (pad) track = gst_pad_get_element_private (pad); @@ -2425,6 +3105,11 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, } finish: + if ((rate < 0.0) && keyunit) { + GST_INFO_OBJECT (demux, "Set the keyunit false for rewind trickplay"); + keyunit = FALSE; + } + if (keyunit && seeksegment.rate > 0) { GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT, @@ -2450,7 +3135,10 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, /* need to seek to cluster start to pick up cluster time */ /* upstream takes care of flushing and all that * ... and newsegment event handling takes care of the rest */ - return perform_seek_to_offset (demux, rate, + if (!gst_matroska_demux_move_to_entry (demux, entry, TRUE, update)) { + goto seek_error; + } + return perform_seek_to_offset (demux, (rate > 0.0) ? rate : 1.0, entry->pos + demux->common.ebml_segment_start, seqnum, flags); } @@ -2461,6 +3149,10 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, GST_DEBUG_OBJECT (demux, "Stopping flush"); gst_pad_push_event (demux->common.sinkpad, gst_event_ref (flush_event)); gst_matroska_demux_send_event (demux, flush_event); + if (rate == 1.0) { + demux->skip_find_next_keyframe = TRUE; + GST_DEBUG_OBJECT (demux, "skip_find_next_keyframe flag is TRUE"); + } } GST_OBJECT_LOCK (demux); @@ -2487,9 +3179,14 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, if (demux->new_segment) gst_event_unref (demux->new_segment); - /* On port from 0.10, discarded !update (for segment.update) here, FIXME? */ - demux->new_segment = gst_event_new_segment (&demux->common.segment); - gst_event_set_seqnum (demux->new_segment, seqnum); + if (demux->common.segment.rate == 1) { + demux->need_segment = TRUE; + demux->common.segment.position = 0; + } else { + /* On port from 0.10, discarded !update (for segment.update) here, FIXME? */ + demux->new_segment = gst_event_new_segment (&demux->common.segment); + gst_event_set_seqnum (demux->new_segment, seqnum); + } if (demux->common.segment.rate < 0 && demux->common.segment.stop == -1) demux->to_time = demux->common.segment.position; else @@ -2536,6 +3233,9 @@ gst_matroska_demux_handle_seek_push (GstMatroskaDemux * demux, GstPad * pad, gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); + demux->is_rate_changed = TRUE; + demux->seek_rate = rate; + /* sanity checks */ /* we can only seek on time */ @@ -2544,11 +3244,6 @@ gst_matroska_demux_handle_seek_push (GstMatroskaDemux * demux, GstPad * pad, return FALSE; } - if (stop_type != GST_SEEK_TYPE_NONE && stop != GST_CLOCK_TIME_NONE) { - GST_DEBUG_OBJECT (demux, "Seek end-time not supported in streaming mode"); - return FALSE; - } - if (!(flags & GST_SEEK_FLAG_FLUSH)) { GST_DEBUG_OBJECT (demux, "Non-flushing seek not supported in streaming mode"); @@ -2592,7 +3287,7 @@ gst_matroska_demux_handle_seek_push (GstMatroskaDemux * demux, GstPad * pad, if (!building_index) { /* seek to the first subindex or legacy index */ GST_INFO_OBJECT (demux, "Seeking to Cues at %" G_GUINT64_FORMAT, offset); - return perform_seek_to_offset (demux, rate, offset, + return perform_seek_to_offset (demux, 1.0, offset, gst_event_get_seqnum (event), GST_SEEK_FLAG_NONE); } @@ -2613,22 +3308,15 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstObject * parent, switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: - /* no seeking until we are (safely) ready */ - if (demux->common.state != GST_MATROSKA_READ_STATE_DATA) { - GST_DEBUG_OBJECT (demux, "not ready for seeking yet"); + { + guint32 seqnum = gst_event_get_seqnum (event); + if (seqnum == demux->segment_seqnum) { + GST_LOG_OBJECT (pad, + "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum); gst_event_unref (event); - return FALSE; - } - - { - guint32 seqnum = gst_event_get_seqnum (event); - if (seqnum == demux->segment_seqnum) { - GST_LOG_OBJECT (pad, - "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum); - gst_event_unref (event); - return TRUE; - } + return TRUE; } + } if (!demux->streaming) res = gst_matroska_demux_handle_seek_event (demux, pad, event); @@ -2697,19 +3385,110 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstObject * parent, break; } - /* events we don't need to handle */ - case GST_EVENT_NAVIGATION: - gst_event_unref (event); - res = FALSE; - break; + /* events we don't need to handle */ + case GST_EVENT_NAVIGATION: + gst_event_unref (event); + res = FALSE; + break; + + case GST_EVENT_LATENCY: + default: + res = gst_pad_push_event (demux->common.sinkpad, event); + break; + } + + return res; +} + +/* for highspeed trick-play, change the offset value to next keyframe position. */ +static gboolean +gst_matroska_demux_move_to_next_keyframe_offset (GstMatroskaDemux * demux, + GstMatroskaIndex * entry, gboolean reset) +{ + gint i; + GST_OBJECT_LOCK (demux); + + /* seek (relative to matroska segment) */ + /* position might be invalid; will error when streaming resumes ... */ + demux->common.offset = entry->pos + demux->common.ebml_segment_start; + + GST_DEBUG_OBJECT (demux, "Seeked to offset %" G_GUINT64_FORMAT ", block %d, " + "time %" GST_TIME_FORMAT, entry->pos + demux->common.ebml_segment_start, + entry->block, GST_TIME_ARGS (entry->time)); + + GST_DEBUG_OBJECT (demux, "resetting stream state"); + + g_assert (demux->common.src->len == demux->common.num_streams); + + for (i = 0; i < demux->common.src->len; i++) { + GstMatroskaTrackContext *context = g_ptr_array_index (demux->common.src, i); + context->pos = entry->time; + context->set_discont = TRUE; + context->eos = FALSE; + context->from_time = GST_CLOCK_TIME_NONE; + + if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + /* demux object lock held by caller */ + videocontext->earliest_time = GST_CLOCK_TIME_NONE; + } + } + demux->common.segment.position = entry->time; + demux->seek_block = entry->block; + demux->seek_first = TRUE; + demux->last_stop_end = GST_CLOCK_TIME_NONE; + + GST_OBJECT_UNLOCK (demux); - case GST_EVENT_LATENCY: - default: - res = gst_pad_push_event (demux->common.sinkpad, event); - break; + return TRUE; + +} + +static GstFlowReturn +gst_matroska_demux_seek_to_next_keyframe (GstMatroskaDemux * demux) +{ + GstFlowReturn ret = GST_FLOW_EOS; + GstMatroskaIndex *entry, *entry_prev; + + if (!demux->seek_entry) { + GST_DEBUG_OBJECT (demux, "is the first entry?"); } - return res; + if (!demux->seek_index) { + GST_ERROR_OBJECT (demux, "there is no demux->seek_index"); + return GST_FLOW_ERROR; + } + + if ((demux->seek_entry + 1) >= demux->seek_index->len) { + GST_INFO_OBJECT (demux, "no more index entry"); + goto exit; + } + + entry_prev = &g_array_index (demux->seek_index, GstMatroskaIndex, + demux->seek_entry); + + entry = &g_array_index (demux->seek_index, GstMatroskaIndex, + ++demux->seek_entry); + + if (entry_prev->pos == entry->pos || + demux->common.offset > entry->pos + demux->common.ebml_segment_start) { + GST_INFO_OBJECT (demux, "entry_prev->pos=[%" G_GUINT64_FORMAT "] " + "entry->pos=[%" G_GUINT64_FORMAT "] " + "demux->offset=[%" G_GUINT64_FORMAT "], " + "entry->pos + demux->ebml_segment_start=[%" G_GUINT64_FORMAT "]", + entry_prev->pos, entry->pos, + demux->common.offset, entry->pos + demux->common.ebml_segment_start); + return GST_FLOW_ERROR; + } + + if (!gst_matroska_demux_move_to_next_keyframe_offset (demux, entry, FALSE)) + goto exit; + + return GST_FLOW_OK; + +exit: + return ret; } static GstFlowReturn @@ -2765,6 +3544,45 @@ gst_matroska_demux_seek_to_previous_keyframe (GstMatroskaDemux * demux) return ret; } +static GstFlowReturn +gst_matroska_demux_parse_drm (GstMatroskaDemux * demux, GstEbmlRead * ebml) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint64 version; + guint32 id; + + DEBUG_ELEMENT_START (demux, ebml, "drm"); + + if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { + DEBUG_ELEMENT_STOP (demux, ebml, "drm", ret); + return ret; + } + + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { + if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) { + break; + } + + switch (id) { + case GST_MATROSKA_ID_TRACKSDATAVERSION: + if ((ret = gst_ebml_read_uint (ebml, &id, &version)) != GST_FLOW_OK) + break; + GST_ERROR_OBJECT (demux, "drm version is %" G_GUINT64_FORMAT, version); + if (version > 0) + GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL), + ("DivX HD Plus DRM file")); + break; + + default: + ret = + gst_matroska_read_common_parse_skip (&demux->common, ebml, "DRM", + id); + break; + } + } + return ret; +} + static GstFlowReturn gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml) { @@ -2802,6 +3620,10 @@ gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml) break; } + case GST_MATROSKA_ID_TRACKSDATA: + ret = gst_matroska_demux_parse_drm (demux, ebml); + break; + default: ret = gst_matroska_read_common_parse_skip (&demux->common, ebml, "Track", id); @@ -3039,6 +3861,28 @@ gst_matroska_demux_sync_streams (GstMatroskaDemux * demux) GST_OBJECT_UNLOCK (demux); gst_pad_push_event (context->pad, event); GST_OBJECT_LOCK (demux); + } else if (context->set_discont && + !GST_CLOCK_TIME_IS_VALID (context->pos) && + GST_CLOCK_TIME_IS_VALID (demux->common.segment.position) && + demux->common.segment.position > + demux->common.segment.start + gap_threshold && + demux->common.segment.position < + demux->common.segment.start + 2 * gap_threshold) { + /* Send gap event once after seek when nothing have been feeded yet + but lagging way behind others */ + GstEvent *event; + gint64 start = demux->common.segment.start; + context->pos = demux->common.segment.position; + + GST_DEBUG_OBJECT (demux, + "Synchronizing stream %d with other by advancing time from %" + GST_TIME_FORMAT " to %" GST_TIME_FORMAT, stream_nr, + GST_TIME_ARGS (start), GST_TIME_ARGS (context->pos)); + + event = gst_event_new_gap (start, context->pos - start); + GST_OBJECT_UNLOCK (demux); + gst_pad_push_event (context->pad, event); + GST_OBJECT_LOCK (demux); } } @@ -3082,6 +3926,68 @@ gst_matroska_demux_push_stream_headers (GstMatroskaDemux * demux, return ret; } +static void +gst_matroska_demux_make_vorbis_header (GstCaps * caps, + GstMatroskaTrackContext * stream) +{ + guint8 *p = (guint8 *) stream->codec_priv; + gint i, offset, num_packets; + guint *length, last; + guint total_len; + GstBuffer *buffer; + guint buf_offset; + GstMapInfo map; + gpointer data; + + if (stream->codec_priv == NULL || stream->codec_priv_size == 0) { + return; + } + + num_packets = p[0] + 1; + + length = g_alloca (num_packets * sizeof (guint)); + last = 0; + offset = 1; + + /* first packets, read length values */ + for (i = 0; i < num_packets - 1; i++) { + length[i] = 0; + while (offset < stream->codec_priv_size) { + length[i] += p[offset]; + if (p[offset++] != 0xff) + break; + } + last += length[i]; + } + if (offset + last > stream->codec_priv_size) + return; + + /* last packet is the remaining size */ + length[i] = stream->codec_priv_size - offset - last; + + total_len = 0; + for (i = 0; i < num_packets; i++) { + total_len += length[i]; + } + + buffer = gst_buffer_new_allocate (NULL, total_len, NULL); + buf_offset = 0; + gst_buffer_map (buffer, &map, GST_MAP_WRITE); + data = map.data; + + for (i = 0; i < num_packets; i++) { + if (offset + length[i] > stream->codec_priv_size) + return; + memcpy ((guint8 *) data + buf_offset, p + offset, length[i]); + buf_offset += length[i]; + offset += length[i]; + } + + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL); + gst_buffer_unmap (buffer, &map); + gst_buffer_unref (buffer); +} + static void gst_matroska_demux_push_dvd_clut_change_event (GstMatroskaDemux * demux, GstMatroskaTrackContext * stream) @@ -3894,6 +4800,17 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, guint64 clace_time; GstEvent *segment_event; + /* Trying to align the new segment to video key-frame start time, + rather than the cluster start time itself to avoid audio being + played without video */ + if (demux->num_v_streams > 0 && + ((stream->type != GST_MATROSKA_TRACK_TYPE_VIDEO) || + (is_simpleblock && !(flags & 0x80)) || referenceblock)) { + GST_DEBUG_OBJECT (demux, + "Trying to align the new segment to video key-frame"); + goto done; + } + if (!GST_CLOCK_TIME_IS_VALID (demux->stream_start_time)) { demux->stream_start_time = lace_time; GST_DEBUG_OBJECT (demux, @@ -3909,10 +4826,17 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, clace_time = demux->common.segment.position; segment->position = GST_CLOCK_TIME_NONE; } - segment->start = clace_time; - segment->stop = GST_CLOCK_TIME_NONE; - segment->time = segment->start - demux->stream_start_time; - segment->position = segment->start - demux->stream_start_time; + if (demux->seek_rate > 0.0) { + segment->start = clace_time; + segment->stop = GST_CLOCK_TIME_NONE; + segment->time = segment->start - demux->stream_start_time; + segment->position = segment->start - demux->stream_start_time; + } else { + segment->start = 0.0; + segment->stop = clace_time; + segment->rate = demux->seek_rate; + } + GST_DEBUG_OBJECT (demux, "generated segment starting at %" GST_TIME_FORMAT ": %" GST_SEGMENT_FORMAT, GST_TIME_ARGS (lace_time), segment); @@ -3953,13 +4877,25 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, (!strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP8) || !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP9) || !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_AV1)); + } else { + if (demux->h264_codec == -1) + demux->h264_codec = + !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC); + + if (demux->h264_codec > 0) { + guint8 nal_unit_type = + map.data[gst_buffer_get_size (buf) - size + 4] & 0x1F; + + if (nal_unit_type == 1) + delta_unit = TRUE; + } } /* If we're doing a keyframe-only trickmode, only push keyframes on video * streams */ if (delta_unit - && demux->common. - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + && demux->common.segment. + flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { GST_LOG_OBJECT (demux, "Skipping non-keyframe on stream %d", stream->index); ret = GST_FLOW_OK; @@ -3967,6 +4903,26 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, } } + if (is_simpleblock && demux->common.segment.rate == 1 + && !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC) + && stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && stream->set_discont + && delta_unit && demux->skip_find_next_keyframe) + goto done; + + if ((demux->common.segment.rate < 0 || demux->common.segment.rate > 2) + || (demux->common.segment.rate == 2 && demux->is_higher_than_FHD)) { + if (stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { + if (!delta_unit) + demux->keyframe_push_done = TRUE; + else + goto done; + } else if (stream->type == GST_MATROSKA_TRACK_TYPE_AUDIO) { + demux->audio_frame_push_check |= 1 << stream->index; + if (demux->audio_frame_push_ref == demux->audio_frame_push_check) + demux->audio_frame_push_done = TRUE; + } + } + for (n = 0; n < laces; n++) { GstBuffer *sub; @@ -4039,6 +4995,7 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, GST_BUFFER_PTS (sub) = lace_time; } else { GST_BUFFER_DTS (sub) = lace_time; + GST_BUFFER_TIMESTAMP (sub) = lace_time; if (stream->intra_only) GST_BUFFER_PTS (sub) = lace_time; } @@ -5106,7 +6063,7 @@ gst_matroska_demux_loop (GstPad * pad) ret = gst_matroska_demux_parse_id (demux, id, length, needed); if (ret == GST_FLOW_EOS) goto eos; - if (ret != GST_FLOW_OK) + if (ret != GST_FLOW_OK && ret < GST_FLOW_CUSTOM_SUCCESS) goto pause; /* check if we're at the end of a configured segment */ @@ -5139,6 +6096,25 @@ gst_matroska_demux_loop (GstPad * pad) } } + /* add to check skip_find_next_keyframe to resolve mismatching pts + when we return play from trickmode. */ + if (!demux->skip_find_next_keyframe && demux->keyframe_push_done + && (!demux->has_audio || demux->audio_frame_push_done)) { + demux->keyframe_push_done = FALSE; + demux->audio_frame_push_done = FALSE; + demux->audio_frame_push_check = 0; + + if (demux->common.segment.rate > 0.0) { + ret = gst_matroska_demux_seek_to_next_keyframe (demux); + if (ret == GST_FLOW_EOS) + goto eos; + } else { + ret = gst_matroska_demux_seek_to_previous_keyframe (demux); + if (ret == GST_FLOW_EOS) + goto pause; + } + } + return; /* ERRORS */ @@ -5146,8 +6122,10 @@ gst_matroska_demux_loop (GstPad * pad) { if (demux->common.segment.rate < 0.0) { ret = gst_matroska_demux_seek_to_previous_keyframe (demux); - if (ret == GST_FLOW_OK) - return; + if (ret == GST_FLOW_EOS) + goto pause; + + return; } /* fall-through */ } @@ -5247,6 +6225,30 @@ perform_seek_to_offset (GstMatroskaDemux * demux, gdouble rate, guint64 offset, return res; } +static gboolean +perform_seek_to_keyframe_offset (GstMatroskaDemux * demux, + gdouble rate, guint64 offset) +{ + GstEvent *event; + gboolean res = 0; + + if (demux->is_flushing) { + GST_WARNING_OBJECT (demux, "In flushing so we skip this step."); + return res; + } + + GST_DEBUG_OBJECT (demux, "seeking to kf offset:%" G_GUINT64_FORMAT, offset); + demux->is_rate_changed = FALSE; + event = + gst_event_new_seek (rate, GST_FORMAT_BYTES, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, offset, GST_SEEK_TYPE_NONE, -1); + res = gst_pad_push_event (demux->common.sinkpad, event); + + /* segment event will update offset */ + return res; + +} + static GstFlowReturn gst_matroska_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { @@ -5254,8 +6256,8 @@ gst_matroska_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) guint available; GstFlowReturn ret = GST_FLOW_OK; guint needed = 0; - guint32 id; - guint64 length; + guint32 id = 0; + guint64 length = 0; if (G_UNLIKELY (GST_BUFFER_IS_DISCONT (buffer))) { GST_DEBUG_OBJECT (demux, "got DISCONT"); @@ -5272,6 +6274,29 @@ gst_matroska_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) next: available = gst_adapter_available (demux->common.adapter); + if (demux->scan_next_cluster_push) { + gint64 pos; + + GST_LOG_OBJECT (demux, "cluster scanning ..."); + ret = gst_matroska_demux_search_cluster_push (demux, &pos); + if (ret == GST_FLOW_EOS) { + GST_LOG_OBJECT (demux, "Need more data.."); + /* need more data */ + return GST_FLOW_OK; + } else if (ret != GST_FLOW_OK) { + /* clear this flag for scanning the next valid cluster */ + /* no valid clusters found */ + demux->scan_next_cluster_push = FALSE; + return ret; + } + GST_DEBUG_OBJECT (demux, "cluster found at %" G_GUINT64_FORMAT, pos); + /* clear this flag for scanning the next valid cluster */ + /* valid clusters found */ + demux->scan_next_cluster_push = FALSE; + /* try that position */ + demux->common.offset = pos; + } + ret = gst_matroska_read_common_peek_id_length_push (&demux->common, GST_ELEMENT_CAST (demux), &id, &length, &needed); if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_EOS)) { @@ -5279,6 +6304,20 @@ gst_matroska_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) && demux->common.offset >= demux->common.ebml_segment_start + demux->common.ebml_segment_length) { return GST_FLOW_OK; + } else if (!demux->scan_next_cluster_push) { + /* check parse error */ + if (!IS_HIGH_SPEED_TRICK (demux)) { + /* set this flag for scanning the next valid cluster */ + demux->scan_next_cluster_push = TRUE; + goto next; + } else { + /* clear the following flags so that seek to next kf will happen */ + demux->keyframe_push_done = TRUE; + demux->audio_frame_push_done = TRUE; + demux->audio_frame_push_check = demux->audio_frame_push_ref; + /* flush the currently available data */ + length = available; + } } else { gint64 bytes_scanned; if (demux->common.start_resync_offset == -1) { @@ -5310,10 +6349,36 @@ gst_matroska_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) return GST_FLOW_OK; ret = gst_matroska_demux_parse_id (demux, id, length, needed); + + if (!demux->skip_find_next_keyframe && demux->keyframe_push_done + && (!demux->has_audio || demux->audio_frame_push_done)) { + + GST_LOG_OBJECT (demux, "### Keyframe_push_done"); + demux->keyframe_push_done = FALSE; + demux->audio_frame_push_done = FALSE; + demux->audio_frame_push_check = 0; + + if (demux->seek_rate < 0.0) { + ret = gst_matroska_demux_seek_to_previous_keyframe (demux); + if (ret == GST_FLOW_OK) { + perform_seek_to_keyframe_offset (demux, 1.0, demux->common.offset); + } else if (ret == GST_FLOW_EOS) { + gst_matroska_demux_send_event (demux, gst_event_new_eos ()); + } + } else { + /*In case of FWD highspeed trick-play */ + if (gst_matroska_demux_seek_to_next_keyframe (demux) == GST_FLOW_OK) { + /* seek to next key-frame when valid index is returned */ + perform_seek_to_keyframe_offset (demux, demux->common.segment.rate, + demux->common.offset); + } + } + } + if (ret == GST_FLOW_EOS) { /* need more data */ return GST_FLOW_OK; - } else if (ret != GST_FLOW_OK) { + } else if (ret != GST_FLOW_OK && ret < GST_FLOW_CUSTOM_SUCCESS) { return ret; } else goto next; @@ -5334,6 +6399,9 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, { const GstSegment *segment; + if (!demux->is_rate_changed) + goto exit; + /* some debug output */ gst_event_parse_segment (event, &segment); /* FIXME: do we need to update segment base here (like accum in 0.10)? */ @@ -5371,8 +6439,11 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, demux->need_segment = TRUE; demux->segment_seqnum = gst_event_get_seqnum (event); /* but keep some of the upstream segment */ - demux->common.segment.rate = segment->rate; demux->common.segment.flags = segment->flags; + if (demux->seek_rate > 0.0) + demux->common.segment.rate = segment->rate; + else + demux->common.segment.rate = demux->seek_rate; /* also check if need to keep some of the requested seek position */ if (demux->seek_offset == segment->start) { GST_DEBUG_OBJECT (demux, "position matches requested seek"); @@ -5406,6 +6477,16 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, } break; } + case GST_EVENT_FLUSH_START: + { + if (!demux->is_rate_changed) { + gst_event_unref (event); + return res; + } + demux->is_flushing = TRUE; + res = gst_pad_event_default (pad, parent, event); + break; + } case GST_EVENT_FLUSH_STOP: { guint64 dur; @@ -5415,12 +6496,21 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, gst_matroska_read_common_reset_streams (&demux->common, GST_CLOCK_TIME_NONE, TRUE); gst_flow_combiner_reset (demux->flowcombiner); + demux->cluster_time = GST_CLOCK_TIME_NONE; + demux->cluster_offset = 0; dur = demux->common.segment.duration; + if (!demux->is_rate_changed) { + gst_event_unref (event); + GST_OBJECT_UNLOCK (demux); + return res; + } gst_segment_init (&demux->common.segment, GST_FORMAT_TIME); demux->common.segment.duration = dur; - demux->cluster_time = GST_CLOCK_TIME_NONE; - demux->cluster_offset = 0; + demux->keyframe_push_done = FALSE; + demux->audio_frame_push_done = FALSE; + demux->audio_frame_push_check = 0; GST_OBJECT_UNLOCK (demux); + demux->is_flushing = FALSE; /* fall-through */ } default: @@ -5541,9 +6631,31 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * size - offset), size - offset); } - if (riff_fourcc) + if (riff_fourcc) { *riff_fourcc = vids->compression; + switch (videocontext->fourcc) { + case GST_MAKE_FOURCC ('D', 'X', '5', '0'): + case GST_MAKE_FOURCC ('d', 'i', 'v', 'x'): + case GST_MAKE_FOURCC ('D', 'I', 'V', 'X'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '3'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '3'): + case GST_MAKE_FOURCC ('D', 'V', 'X', '3'): + case GST_MAKE_FOURCC ('d', 'v', 'x', '3'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '4'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '4'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '5'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '5'): + case GST_MAKE_FOURCC ('D', 'I', 'V', '6'): + case GST_MAKE_FOURCC ('d', 'i', 'v', '6'): + *riff_fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D'); + break; + + default: + break; + } + } + caps = gst_riff_create_video_caps (vids->compression, NULL, vids, buf, NULL, codec_name); @@ -5554,8 +6666,14 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * static GstStaticCaps intra_caps = GST_STATIC_CAPS ("image/jpeg; " "video/x-raw; image/png; video/x-dv; video/x-huffyuv; video/x-ffv; " "video/x-compressed-yuv"); + GstStructure *s; context->intra_only = gst_caps_can_intersect (gst_static_caps_get (&intra_caps), caps); + + s = gst_caps_get_structure (caps, 0); + if (gst_structure_has_name (s, "video/x-h264")) + gst_structure_set (s, "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", NULL); } if (buf) @@ -5608,12 +6726,9 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * caps = gst_video_info_to_caps (&info); *codec_name = gst_pb_utils_get_codec_description (caps); context->alignment = 32; - } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP)) { - caps = gst_caps_new_simple ("video/x-divx", - "divxversion", G_TYPE_INT, 4, NULL); - *codec_name = g_strdup ("MPEG-4 simple profile"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP) || - !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP)) { + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AP) || + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP)) { caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 4, "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); @@ -5630,15 +6745,10 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * *codec_name = g_strdup ("MPEG-4 advanced simple profile"); else *codec_name = g_strdup ("MPEG-4 advanced profile"); + gst_caps_set_simple (caps, "format", G_TYPE_STRING, "MP4V", NULL); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3)) { -#if 0 - caps = gst_caps_new_full (gst_structure_new ("video/x-divx", - "divxversion", G_TYPE_INT, 3, NULL), - gst_structure_new ("video/x-msmpeg", - "msmpegversion", G_TYPE_INT, 43, NULL), NULL); -#endif caps = gst_caps_new_simple ("video/x-msmpeg", - "msmpegversion", G_TYPE_INT, 43, NULL); + "msmpegversion", G_TYPE_INT, 43, "format", G_TYPE_STRING, "MP43", NULL); *codec_name = g_strdup ("Microsoft MPEG-4 v.3"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG1) || !strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG2)) { @@ -5652,10 +6762,15 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * caps = gst_caps_new_simple ("video/mpeg", "systemstream", G_TYPE_BOOLEAN, FALSE, "mpegversion", G_TYPE_INT, mpegversion, NULL); + + gst_caps_set_simple (caps, "format", G_TYPE_STRING, + (mpegversion == 2) ? "MP2V" : "MP1V", NULL); + *codec_name = g_strdup_printf ("MPEG-%d video", mpegversion); context->postprocess_frame = gst_matroska_demux_add_mpeg_seq_header; } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MJPEG)) { - caps = gst_caps_new_empty_simple ("image/jpeg"); + caps = gst_caps_new_simple ("image/jpeg", + "format", G_TYPE_STRING, "MJPG", NULL); *codec_name = g_strdup ("Motion-JPEG"); context->intra_only = TRUE; } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC)) { @@ -5674,7 +6789,8 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * gst_buffer_unref (priv); gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "avc", - "alignment", G_TYPE_STRING, "au", NULL); + "alignment", G_TYPE_STRING, "au", + "format", G_TYPE_STRING, "H264", NULL); } else { GST_WARNING ("No codec data found, assuming output is byte-stream"); gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "byte-stream", @@ -5694,49 +6810,14 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * gst_buffer_unref (priv); gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "hvc1", - "alignment", G_TYPE_STRING, "au", NULL); + "alignment", G_TYPE_STRING, "au", "format", G_TYPE_STRING, "HEVC", + NULL); } else { GST_WARNING ("No codec data found, assuming output is byte-stream"); gst_caps_set_simple (caps, "stream-format", G_TYPE_STRING, "byte-stream", NULL); } *codec_name = g_strdup ("HEVC"); - } else if ((!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1)) || - (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2)) || - (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3)) || - (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4))) { - gint rmversion = -1; - - if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1)) - rmversion = 1; - else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2)) - rmversion = 2; - else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3)) - rmversion = 3; - else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4)) - rmversion = 4; - - caps = gst_caps_new_simple ("video/x-pn-realvideo", - "rmversion", G_TYPE_INT, rmversion, NULL); - GST_DEBUG ("data:%p, size:0x%x", data, size); - /* We need to extract the extradata ! */ - if (data && (size >= 0x22)) { - GstBuffer *priv; - guint rformat; - guint subformat; - - subformat = GST_READ_UINT32_BE (data + 0x1a); - rformat = GST_READ_UINT32_BE (data + 0x1e); - - priv = - gst_buffer_new_wrapped (g_memdup (data + 0x1a, size - 0x1a), - size - 0x1a); - gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, "format", - G_TYPE_INT, rformat, "subformat", G_TYPE_INT, subformat, NULL); - gst_buffer_unref (priv); - - } - *codec_name = g_strdup_printf ("RealVideo %d.0", rmversion); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_THEORA)) { caps = gst_caps_new_empty_simple ("video/x-theora"); context->stream_headers = @@ -5748,13 +6829,25 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * caps = gst_caps_new_empty_simple ("video/x-dirac"); *codec_name = g_strdup_printf ("Dirac"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP8)) { - caps = gst_caps_new_empty_simple ("video/x-vp8"); + caps = gst_caps_new_simple ("video/x-vp8", + "format", G_TYPE_STRING, "VP80", NULL); *codec_name = g_strdup_printf ("On2 VP8"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP9)) { - caps = gst_caps_new_empty_simple ("video/x-vp9"); + caps = gst_caps_new_simple ("video/x-vp9", + "format", G_TYPE_STRING, "VP90", NULL); *codec_name = g_strdup_printf ("On2 VP9"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_AV1)) { - caps = gst_caps_new_empty_simple ("video/x-av1"); + caps = gst_caps_new_simple ("video/x-av1", + "format", G_TYPE_STRING, "AV01", NULL); + if (data) { + GstBuffer *priv; + + priv = gst_buffer_new_wrapped (g_memdup (data, size), size); + gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); + gst_buffer_unref (priv); + } else { + GST_WARNING ("No AV1 codec data found!"); + } *codec_name = g_strdup_printf ("AOM AV1"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_PRORES)) { guint32 fourcc; @@ -5807,6 +6900,9 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * int i; GstStructure *structure; + gst_caps_set_simple (caps, "container", G_TYPE_STRING, "mkv", + "timestamptype", G_TYPE_BOOLEAN, TRUE, NULL); + for (i = 0; i < gst_caps_get_size (caps); i++) { structure = gst_caps_get_structure (caps, i); @@ -5902,6 +6998,12 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * caps = gst_caps_simplify (caps); } + if (!g_strcmp0 (*codec_name, "ITU H.26n") + && (riff_fourcc && *riff_fourcc == GST_MAKE_FOURCC ('H', '2', '6', '3'))) { + g_free (*codec_name); + *codec_name = g_strdup_printf ("H.263"); + } + return caps; } @@ -6069,9 +7171,6 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * strlen (GST_MATROSKA_CODEC_ID_AUDIO_TRUEHD))) { caps = gst_caps_new_empty_simple ("audio/x-true-hd"); *codec_name = g_strdup ("Dolby TrueHD"); - } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_DTS)) { - caps = gst_caps_new_empty_simple ("audio/x-dts"); - *codec_name = g_strdup ("DTS audio"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS)) { caps = gst_caps_new_empty_simple ("audio/x-vorbis"); context->stream_headers = @@ -6079,6 +7178,7 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * context->codec_priv_size); /* FIXME: mark stream as broken and skip if there are no stream headers */ context->send_stream_headers = TRUE; + *codec_name = g_strdup ("VORBIS audio"); } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_FLAC)) { caps = gst_caps_new_empty_simple ("audio/x-flac"); context->stream_headers = @@ -6150,6 +7250,7 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * if (data && size >= 18) { GstBuffer *codec_data = NULL; + guint16 word_size = 0; /* little-endian -> byte-order */ auds.format = GST_READ_UINT16_LE (data); @@ -6158,6 +7259,11 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * auds.av_bps = GST_READ_UINT32_LE (data + 8); auds.blockalign = GST_READ_UINT16_LE (data + 12); auds.bits_per_sample = GST_READ_UINT16_LE (data + 16); + word_size = GST_READ_UINT16_LE (data + 14); + + /* do not support wma version 1 */ + if (auds.format == 352) + return NULL; /* 18 is the waveformatex size */ if (size > 18) { @@ -6176,6 +7282,8 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * if (caps == NULL) { GST_WARNING ("Unhandled RIFF audio format 0x%02x", auds.format); + } else { + gst_caps_set_simple (caps, "word_size", G_TYPE_INT, word_size, NULL); } } else { GST_WARNING ("Invalid codec data size (%d expected, got %d)", 18, size); @@ -6282,67 +7390,6 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * *codec_name = g_strdup ("Wavpack audio"); context->postprocess_frame = gst_matroska_demux_add_wvpk_header; audiocontext->wvpk_block_index = 0; - } else if ((!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4)) || - (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8)) || - (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK))) { - gint raversion = -1; - - if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4)) - raversion = 1; - else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK)) - raversion = 8; - else - raversion = 2; - - caps = gst_caps_new_simple ("audio/x-pn-realaudio", - "raversion", G_TYPE_INT, raversion, NULL); - /* Extract extra information from caps, mapping varies based on codec */ - if (data && (size >= 0x50)) { - GstBuffer *priv; - guint flavor; - guint packet_size; - guint height; - guint leaf_size; - guint sample_width; - guint extra_data_size; - - GST_DEBUG ("real audio raversion:%d", raversion); - if (raversion == 8) { - /* COOK */ - flavor = GST_READ_UINT16_BE (data + 22); - packet_size = GST_READ_UINT32_BE (data + 24); - height = GST_READ_UINT16_BE (data + 40); - leaf_size = GST_READ_UINT16_BE (data + 44); - sample_width = GST_READ_UINT16_BE (data + 58); - extra_data_size = GST_READ_UINT32_BE (data + 74); - - GST_DEBUG - ("flavor:%d, packet_size:%d, height:%d, leaf_size:%d, sample_width:%d, extra_data_size:%d", - flavor, packet_size, height, leaf_size, sample_width, - extra_data_size); - gst_caps_set_simple (caps, "flavor", G_TYPE_INT, flavor, "packet_size", - G_TYPE_INT, packet_size, "height", G_TYPE_INT, height, "leaf_size", - G_TYPE_INT, leaf_size, "width", G_TYPE_INT, sample_width, NULL); - - if ((size - 78) >= extra_data_size) { - priv = gst_buffer_new_wrapped (g_memdup (data + 78, extra_data_size), - extra_data_size); - gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL); - gst_buffer_unref (priv); - } - } - } - - *codec_name = g_strdup_printf ("RealAudio %d.0", raversion); - } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_SIPR)) { - caps = gst_caps_new_empty_simple ("audio/x-sipro"); - *codec_name = g_strdup ("Sipro/ACELP.NET Voice Codec"); - } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_RALF)) { - caps = gst_caps_new_empty_simple ("audio/x-ralf-mpeg4-generic"); - *codec_name = g_strdup ("Real Audio Lossless"); - } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_REAL_ATRC)) { - caps = gst_caps_new_empty_simple ("audio/x-vnd.sony.atrac3"); - *codec_name = g_strdup ("Sony ATRAC3"); } else { GST_WARNING ("Unknown codec '%s', cannot build Caps", codec_id); return NULL; @@ -6358,6 +7405,11 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * "rate", G_TYPE_INT, audiocontext->samplerate, NULL); } } + if (context->send_stream_headers == TRUE && + !strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS)) { + gst_matroska_demux_make_vorbis_header (caps, context); + context->send_stream_headers = FALSE; + } caps = gst_caps_simplify (caps); } @@ -6399,6 +7451,7 @@ gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext * caps = gst_caps_new_empty_simple ("application/x-ass"); context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; subtitlecontext->check_markup = FALSE; +#if 0 } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_SUBTITLE_USF)) { caps = gst_caps_new_empty_simple ("application/x-usf"); context->postprocess_frame = gst_matroska_demux_check_subtitle_buffer; @@ -6415,6 +7468,7 @@ gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext * context->codec_priv_size); /* FIXME: mark stream as broken and skip if there are no stream headers */ context->send_stream_headers = TRUE; +#endif } else { GST_DEBUG ("Unknown subtitle stream: codec_id='%s'", codec_id); caps = gst_caps_new_empty_simple ("application/x-subtitle-unknown"); @@ -6469,9 +7523,24 @@ gst_matroska_demux_change_state (GstElement * element, { GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (element); GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstSmartPropertiesReturn property_ret; /* handle upwards state changes here */ switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + property_ret = gst_element_get_smart_properties (GST_ELEMENT_CAST (demux), + "thumbnail-mode", &demux->thumbnail_mode, "max-gap-time", + &demux->max_gap_time, NULL); + + if (property_ret != GST_SMART_PROPERTIES_OK) { + GST_WARNING_OBJECT (demux, "faield to get properties"); + } + + GST_INFO_OBJECT (demux, "thumbnail-mode = %d", demux->thumbnail_mode); + GST_INFO_OBJECT (demux, "max-gap-time = %" G_GUINT64_FORMAT, + demux->max_gap_time); + + break; default: break; } @@ -6490,48 +7559,6 @@ gst_matroska_demux_change_state (GstElement * element, return ret; } -static void -gst_matroska_demux_set_property (GObject * object, - guint prop_id, const GValue * value, GParamSpec * pspec) -{ - GstMatroskaDemux *demux; - - g_return_if_fail (GST_IS_MATROSKA_DEMUX (object)); - demux = GST_MATROSKA_DEMUX (object); - - switch (prop_id) { - case PROP_MAX_GAP_TIME: - GST_OBJECT_LOCK (demux); - demux->max_gap_time = g_value_get_uint64 (value); - GST_OBJECT_UNLOCK (demux); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_matroska_demux_get_property (GObject * object, - guint prop_id, GValue * value, GParamSpec * pspec) -{ - GstMatroskaDemux *demux; - - g_return_if_fail (GST_IS_MATROSKA_DEMUX (object)); - demux = GST_MATROSKA_DEMUX (object); - - switch (prop_id) { - case PROP_MAX_GAP_TIME: - GST_OBJECT_LOCK (demux); - g_value_set_uint64 (value, demux->max_gap_time); - GST_OBJECT_UNLOCK (demux); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - gboolean gst_matroska_demux_plugin_init (GstPlugin * plugin) { diff --git a/gst/matroska/matroska-demux.h b/gst/matroska/matroska-demux.h index 2fe248b455..c0623d0fda 100644 --- a/gst/matroska/matroska-demux.h +++ b/gst/matroska/matroska-demux.h @@ -113,6 +113,20 @@ typedef struct _GstMatroskaDemux { /* Cached upstream length (default G_MAXUINT64) */ guint64 cached_length; + guint thumbnail_mode; + gint8 h264_codec; + + gboolean skip_find_next_keyframe; + gboolean keyframe_push_done; + gboolean is_higher_than_FHD; + gboolean has_audio; + guint64 audio_frame_push_ref; + guint64 audio_frame_push_check; + gboolean audio_frame_push_done; + gdouble seek_rate; + gboolean is_rate_changed; + gboolean scan_next_cluster_push; + gboolean is_flushing; } GstMatroskaDemux; typedef struct _GstMatroskaDemuxClass { diff --git a/gst/matroska/matroska-ids.c b/gst/matroska/matroska-ids.c index 3be3d276b2..9860ee52d9 100644 --- a/gst/matroska/matroska-ids.c +++ b/gst/matroska/matroska-ids.c @@ -67,6 +67,31 @@ gst_matroska_track_init_video_context (GstMatroskaTrackContext ** p_context) video_context->colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN; + /* HDR metadata */ + video_context->matrix_coefficients = 2; + video_context->bits_per_channel = 0; + video_context->chroma_subsampling_horz = 0; + video_context->chroma_subsampling_vert = 0; + video_context->cb_subsampling_horz = 0; + video_context->cb_subsampling_vert = 0; + video_context->chroma_siting_horz = 0; + video_context->chroma_siting_vert = 0; + video_context->range = 0; + video_context->transfer_characteristics = 2; + video_context->primaries = 2; + video_context->max_CLL = 0; + video_context->max_FALL = 0; + video_context->primaryR_chromaticityX = 0.0; + video_context->primaryR_chromaticityY = 0.0; + video_context->primaryG_chromaticityX = 0.0; + video_context->primaryG_chromaticityY = 0.0; + video_context->primaryB_chromaticityX = 0.0; + video_context->primaryB_chromaticityY = 0.0; + video_context->white_point_chromaticityX = 0.0; + video_context->white_point_chromaticityY = 0.0; + video_context->luminance_max = 0.0; + video_context->luminance_min = 0.0; + return TRUE; } diff --git a/gst/matroska/matroska-ids.h b/gst/matroska/matroska-ids.h index 9a88010d00..bdd49e1a59 100644 --- a/gst/matroska/matroska-ids.h +++ b/gst/matroska/matroska-ids.h @@ -117,6 +117,11 @@ #define GST_MATROSKA_ID_TRACKTRANSLATECODEC 0x66BF #define GST_MATROSKA_ID_TRACKTRANSLATETRACKID 0x66A5 +/* TracksData */ +#define GST_MATROSKA_ID_TRACKSDATA 0xDA +#define GST_MATROSKA_ID_TRACKSDATAVERSION 0xDB +#define GST_MATROSKA_ID_TRACKSDATASIZE 0xDC +#define GST_MATROSKA_ID_TRACKSDATAPAYLOAD 0xDE /* IDs in the TrackVideo master */ /* NOTE: This one is here only for backward compatibility. @@ -136,6 +141,32 @@ #define GST_MATROSKA_ID_VIDEOSTEREOMODE 0x53B8 #define GST_MATROSKA_ID_VIDEOASPECTRATIOTYPE 0x54B3 #define GST_MATROSKA_ID_VIDEOCOLOURSPACE 0x2EB524 +/* HDR metadata */ +#define GST_MATROSKA_ID_VIDEOCOLOUR 0x55B0 +#define GST_MATROSKA_ID_MATRIXCOEFFICIENTS 0x55B1 +#define GST_MATROSKA_ID_BITSPERCHANNEL 0x55B2 +#define GST_MATROSKA_ID_CHROMASUBSAMPLINGHORZ 0x55B3 +#define GST_MATROSKA_ID_CHROMASUBSAMPLINGVERT 0x55B4 +#define GST_MATROSKA_ID_CBSUBSAMPLINGHORZ 0x55B5 +#define GST_MATROSKA_ID_CBSUBSAMPLINGVERT 0x55B6 +#define GST_MATROSKA_ID_CHROMASITINGHORZ 0x55B7 +#define GST_MATROSKA_ID_CHROMASITINGVERT 0x55B8 +#define GST_MATROSKA_ID_RANGE 0x55B9 +#define GST_MATROSKA_ID_TRANSFERCHARACTERISTICS 0x55BA +#define GST_MATROSKA_ID_PRIMARIES 0x55BB +#define GST_MATROSKA_ID_MAXCLL 0x55BC +#define GST_MATROSKA_ID_MAXFALL 0x55BD +#define GST_MATROSKA_ID_MASTERINGMETADATA 0x55D0 +#define GST_MATROSKA_ID_PRIMARYRCHROMATICITYX 0x55D1 +#define GST_MATROSKA_ID_PRIMARYRCHROMATICITYY 0x55D2 +#define GST_MATROSKA_ID_PRIMARYGCHROMATICITYX 0x55D3 +#define GST_MATROSKA_ID_PRIMARYGCHROMATICITYY 0x55D4 +#define GST_MATROSKA_ID_PRIMARYBCHROMATICITYX 0x55D5 +#define GST_MATROSKA_ID_PRIMARYBCHROMATICITYY 0x55D6 +#define GST_MATROSKA_ID_WHITEPOINTCHROMATICITYX 0x55D7 +#define GST_MATROSKA_ID_WHITEPOINTCHROMATICITYY 0x55D8 +#define GST_MATROSKA_ID_LUMINANCEMAX 0x55D9 +#define GST_MATROSKA_ID_LUMINANCEMIN 0x55DA /* semi-draft */ #define GST_MATROSKA_ID_VIDEOGAMMAVALUE 0x2FB523 @@ -374,6 +405,8 @@ #define GST_MATROSKA_CODEC_ID_AUDIO_EAC3 "A_EAC3" #define GST_MATROSKA_CODEC_ID_AUDIO_TRUEHD "A_TRUEHD" #define GST_MATROSKA_CODEC_ID_AUDIO_DTS "A_DTS" +#define GST_MATROSKA_CODEC_ID_AUDIO_DTS_EXPRESS "A_DTS/EXPRESS" +#define GST_MATROSKA_CODEC_ID_AUDIO_DTS_LOSSLESS "A_DTS/LOSSLESS" #define GST_MATROSKA_CODEC_ID_AUDIO_VORBIS "A_VORBIS" #define GST_MATROSKA_CODEC_ID_AUDIO_FLAC "A_FLAC" /* FIXME: not yet in the spec */ @@ -598,12 +631,38 @@ typedef struct _GstMatroskaTrackVideoContext { GstVideoMultiviewMode multiview_mode; GstVideoMultiviewFlags multiview_flags; + /* HDR metadata */ + guint matrix_coefficients; + guint bits_per_channel; + guint chroma_subsampling_horz; + guint chroma_subsampling_vert; + guint cb_subsampling_horz; + guint cb_subsampling_vert; + guint chroma_siting_horz; + guint chroma_siting_vert; + guint range; + guint transfer_characteristics; + guint primaries; + guint max_CLL; + guint max_FALL; + gdouble primaryR_chromaticityX; + gdouble primaryR_chromaticityY; + gdouble primaryG_chromaticityX; + gdouble primaryG_chromaticityY; + gdouble primaryB_chromaticityX; + gdouble primaryB_chromaticityY; + gdouble white_point_chromaticityX; + gdouble white_point_chromaticityY; + gdouble luminance_max; + gdouble luminance_min; /* QoS */ GstClockTime earliest_time; GstBuffer *dirac_unit; GstVideoColorimetry colorimetry; + + gint valid_color; } GstMatroskaTrackVideoContext; typedef struct _GstMatroskaTrackAudioContext { diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 0d129fca45..17d2fec8ad 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -127,9 +127,6 @@ static GstStaticPadTemplate videosink_templ = "video/x-theora; " "video/x-dirac, " COMMON_VIDEO_CAPS "; " - "video/x-pn-realvideo, " - "rmversion = (int) [1, 4], " - COMMON_VIDEO_CAPS "; " "video/x-vp8, " COMMON_VIDEO_CAPS "; " "video/x-vp9, " @@ -181,8 +178,6 @@ static GstStaticPadTemplate audiosink_templ = "audio/x-tta, " "width = (int) { 8, 16, 24 }, " "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; " - "audio/x-pn-realaudio, " - "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; " "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], " "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], " COMMON_AUDIO_CAPS ";" @@ -1257,48 +1252,6 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) /* can only make it here if preceding case verified it was version 3 */ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3); - } else if (!strcmp (mimetype, "video/x-pn-realvideo")) { - gint rmversion; - const GValue *mdpr_data; - - gst_structure_get_int (structure, "rmversion", &rmversion); - switch (rmversion) { - case 1: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1); - break; - case 2: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2); - break; - case 3: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3); - break; - case 4: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4); - break; - default: - goto refuse_caps; - } - - mdpr_data = gst_structure_get_value (structure, "mdpr_data"); - if (mdpr_data != NULL) { - guint8 *priv_data = NULL; - guint priv_data_size = 0; - - GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); - - priv_data_size = gst_buffer_get_size (codec_data_buf); - priv_data = g_malloc0 (priv_data_size); - - gst_buffer_extract (codec_data_buf, 0, priv_data, -1); - - gst_matroska_mux_free_codec_priv (context); - context->codec_priv = priv_data; - context->codec_priv_size = priv_data_size; - } } else if (strcmp (mimetype, "video/x-prores") == 0) { const gchar *variant; @@ -2038,46 +1991,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) audiocontext->bitdepth = width; gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_TTA); - } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) { - gint raversion; - const GValue *mdpr_data; - - gst_structure_get_int (structure, "raversion", &raversion); - switch (raversion) { - case 1: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4); - break; - case 2: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8); - break; - case 8: - gst_matroska_mux_set_codec_id (context, - GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK); - break; - default: - goto refuse_caps; - } - - mdpr_data = gst_structure_get_value (structure, "mdpr_data"); - if (mdpr_data != NULL) { - guint8 *priv_data = NULL; - guint priv_data_size = 0; - - GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); - - priv_data_size = gst_buffer_get_size (codec_data_buf); - priv_data = g_malloc0 (priv_data_size); - - gst_buffer_extract (codec_data_buf, 0, priv_data, -1); - - gst_matroska_mux_free_codec_priv (context); - - context->codec_priv = priv_data; - context->codec_priv_size = priv_data_size; - } - } else if (!strcmp (mimetype, "audio/x-wma") || !strcmp (mimetype, "audio/x-alaw") || !strcmp (mimetype, "audio/x-mulaw") diff --git a/gst/meson.build b/gst/meson.build index 61df5f5e6e..d0842684b4 100644 --- a/gst/meson.build +++ b/gst/meson.build @@ -20,6 +20,7 @@ subdir('id3demux') subdir('imagefreeze') subdir('interleave') subdir('isomp4') +subdir('isomp4_1_8') subdir('law') subdir('level') subdir('matroska') diff --git a/gst/rtp/gstrtpmp4gpay.c b/gst/rtp/gstrtpmp4gpay.c index 7333b498ed..35f2c8c5e3 100644 --- a/gst/rtp/gstrtpmp4gpay.c +++ b/gst/rtp/gstrtpmp4gpay.c @@ -390,6 +390,7 @@ gst_rtp_mp4g_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps) const GValue *codec_data; const gchar *media_type = NULL; gboolean res; + const gchar *name; rtpmp4gpay = GST_RTP_MP4G_PAY (payload); @@ -400,7 +401,6 @@ gst_rtp_mp4g_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps) GST_LOG_OBJECT (rtpmp4gpay, "got codec_data"); if (G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) { GstBuffer *buffer; - const gchar *name; buffer = gst_value_get_buffer (codec_data); GST_LOG_OBJECT (rtpmp4gpay, "configuring codec_data"); @@ -426,6 +426,23 @@ gst_rtp_mp4g_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps) rtpmp4gpay->config = gst_buffer_copy (buffer); } + } else { + name = gst_structure_get_name (structure); + + if (!strcmp (name, "video/mpeg")) { + rtpmp4gpay->profile = g_strdup ("1"); + + /* fixed rate */ + rtpmp4gpay->rate = 90000; + /* video stream type */ + rtpmp4gpay->streamtype = "4"; + /* no params for video */ + rtpmp4gpay->params = NULL; + /* mode */ + rtpmp4gpay->mode = "generic"; + + media_type = "video"; + } } if (media_type == NULL) goto config_failed; diff --git a/gst/rtp/gstrtpvrawpay.c b/gst/rtp/gstrtpvrawpay.c index d7d66b5516..027d138e90 100644 --- a/gst/rtp/gstrtpvrawpay.c +++ b/gst/rtp/gstrtpvrawpay.c @@ -142,6 +142,7 @@ gst_rtp_vraw_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps) rtpvrawpay = GST_RTP_VRAW_PAY (payload); + gst_video_info_init (&info); if (!gst_video_info_from_caps (&info, caps)) goto invalid_caps; diff --git a/gst/rtpmanager/gstrtpbin.c b/gst/rtpmanager/gstrtpbin.c index ad99c5b85a..5d21cbb97f 100644 --- a/gst/rtpmanager/gstrtpbin.c +++ b/gst/rtpmanager/gstrtpbin.c @@ -204,9 +204,6 @@ static GstStaticPadTemplate rtpbin_send_rtp_src_template = GST_STATIC_CAPS ("application/x-rtp;application/x-srtp") ); -#define GST_RTP_BIN_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTP_BIN, GstRtpBinPrivate)) - #define GST_RTP_BIN_LOCK(bin) g_mutex_lock (&(bin)->priv->bin_lock) #define GST_RTP_BIN_UNLOCK(bin) g_mutex_unlock (&(bin)->priv->bin_lock) @@ -1964,7 +1961,7 @@ static void gst_rtp_bin_release_pad (GstElement * element, GstPad * pad); static void gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message); #define gst_rtp_bin_parent_class parent_class -G_DEFINE_TYPE (GstRtpBin, gst_rtp_bin, GST_TYPE_BIN); +G_DEFINE_TYPE_WITH_PRIVATE (GstRtpBin, gst_rtp_bin, GST_TYPE_BIN); static gboolean _gst_element_accumulator (GSignalInvocationHint * ihint, @@ -2009,8 +2006,6 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass) gstelement_class = (GstElementClass *) klass; gstbin_class = (GstBinClass *) klass; - g_type_class_add_private (klass, sizeof (GstRtpBinPrivate)); - gobject_class->dispose = gst_rtp_bin_dispose; gobject_class->finalize = gst_rtp_bin_finalize; gobject_class->set_property = gst_rtp_bin_set_property; @@ -2719,7 +2714,7 @@ gst_rtp_bin_init (GstRtpBin * rtpbin) { gchar *cname; - rtpbin->priv = GST_RTP_BIN_GET_PRIVATE (rtpbin); + rtpbin->priv = gst_rtp_bin_get_instance_private (rtpbin); g_mutex_init (&rtpbin->priv->bin_lock); g_mutex_init (&rtpbin->priv->dyn_lock); diff --git a/gst/rtpmanager/gstrtpjitterbuffer.c b/gst/rtpmanager/gstrtpjitterbuffer.c index 678210be94..b1199c58ac 100644 --- a/gst/rtpmanager/gstrtpjitterbuffer.c +++ b/gst/rtpmanager/gstrtpjitterbuffer.c @@ -400,10 +400,6 @@ typedef struct guint num_rtx_received; } TimerData; -#define GST_RTP_JITTER_BUFFER_GET_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_RTP_JITTER_BUFFER, \ - GstRtpJitterBufferPrivate)) - static GstStaticPadTemplate gst_rtp_jitter_buffer_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, @@ -436,7 +432,8 @@ GST_STATIC_PAD_TEMPLATE ("src", static guint gst_rtp_jitter_buffer_signals[LAST_SIGNAL] = { 0 }; #define gst_rtp_jitter_buffer_parent_class parent_class -G_DEFINE_TYPE (GstRtpJitterBuffer, gst_rtp_jitter_buffer, GST_TYPE_ELEMENT); +G_DEFINE_TYPE_WITH_PRIVATE (GstRtpJitterBuffer, gst_rtp_jitter_buffer, + GST_TYPE_ELEMENT); /* object overrides */ static void gst_rtp_jitter_buffer_set_property (GObject * object, @@ -516,8 +513,6 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - g_type_class_add_private (klass, sizeof (GstRtpJitterBufferPrivate)); - gobject_class->finalize = gst_rtp_jitter_buffer_finalize; gobject_class->set_property = gst_rtp_jitter_buffer_set_property; @@ -994,7 +989,7 @@ gst_rtp_jitter_buffer_init (GstRtpJitterBuffer * jitterbuffer) { GstRtpJitterBufferPrivate *priv; - priv = GST_RTP_JITTER_BUFFER_GET_PRIVATE (jitterbuffer); + priv = gst_rtp_jitter_buffer_get_instance_private (jitterbuffer); jitterbuffer->priv = priv; priv->latency_ms = DEFAULT_LATENCY_MS; diff --git a/gst/rtpmanager/gstrtpsession.c b/gst/rtpmanager/gstrtpsession.c index 847522c5f1..009e5733e6 100644 --- a/gst/rtpmanager/gstrtpsession.c +++ b/gst/rtpmanager/gstrtpsession.c @@ -251,9 +251,6 @@ enum PROP_RTCP_SYNC_SEND_TIME }; -#define GST_RTP_SESSION_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTP_SESSION, GstRtpSessionPrivate)) - #define GST_RTP_SESSION_LOCK(sess) g_mutex_lock (&(sess)->priv->lock) #define GST_RTP_SESSION_UNLOCK(sess) g_mutex_unlock (&(sess)->priv->lock) @@ -476,7 +473,7 @@ on_notify_stats (RTPSession * session, GParamSpec * spec, } #define gst_rtp_session_parent_class parent_class -G_DEFINE_TYPE (GstRtpSession, gst_rtp_session, GST_TYPE_ELEMENT); +G_DEFINE_TYPE_WITH_PRIVATE (GstRtpSession, gst_rtp_session, GST_TYPE_ELEMENT); static void gst_rtp_session_class_init (GstRtpSessionClass * klass) @@ -487,8 +484,6 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - g_type_class_add_private (klass, sizeof (GstRtpSessionPrivate)); - gobject_class->finalize = gst_rtp_session_finalize; gobject_class->set_property = gst_rtp_session_set_property; gobject_class->get_property = gst_rtp_session_get_property; @@ -804,7 +799,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass) static void gst_rtp_session_init (GstRtpSession * rtpsession) { - rtpsession->priv = GST_RTP_SESSION_GET_PRIVATE (rtpsession); + rtpsession->priv = gst_rtp_session_get_instance_private (rtpsession); g_mutex_init (&rtpsession->priv->lock); g_cond_init (&rtpsession->priv->cond); rtpsession->priv->sysclock = gst_system_clock_obtain (); diff --git a/gst/rtpmanager/rtpstats.h b/gst/rtpmanager/rtpstats.h index b0fbddb485..831a249a06 100644 --- a/gst/rtpmanager/rtpstats.h +++ b/gst/rtpmanager/rtpstats.h @@ -190,7 +190,16 @@ typedef struct { * The default and minimum values of the maximum number of missing packets we tolerate. * These are packets with asequence number bigger than the last seen packet. */ +#if 0 #define RTP_DEF_DROPOUT 3000 +#else +/* Cannot detect the Miracast (WiDi) reset case because the + * RTP_DEF_DROPOUT value is too high. Sometimes a Miracast device + * skips the 500~8000 of the rtp packets, when changing the resolution + * or encoder reset. In this case, rtpjitterbuffer should not wait the rtp + * packets. So the threshold value 500 is used instead of the 3000. */ +#define RTP_DEF_DROPOUT 500 +#endif #define RTP_MIN_DROPOUT 30 /* diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index facdbb443b..5de1f32392 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -87,6 +87,8 @@ #include #include +#include + #include #include #include @@ -308,6 +310,16 @@ enum PROP_MAX_TS_OFFSET, PROP_DEFAULT_VERSION, PROP_BACKCHANNEL, + PROP_SMART_PROPERTIES, + PROP_DRM_HEADER, + PROP_FIRST_PTS, + PROP_LAST +}; + +enum +{ + APP_TYPE_DEFAULT = 0, + APP_TYPE_SKB, }; #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) @@ -397,6 +409,11 @@ typedef struct GstCaps *caps; } PtMapItem; +static gboolean gst_rtspsrc_check_timeout (GstRTSPSrc * src, GstRTSPResult res); +static GstRTSPResult gst_rtspsrc_send_speed (GstRTSPSrc * src); +static void gst_rtspsrc_do_send_speed (GstRTSPSrc * src); + + /* commands we send to out loop to notify it of events */ #define CMD_OPEN (1 << 0) #define CMD_PLAY (1 << 1) @@ -405,9 +422,10 @@ typedef struct #define CMD_WAIT (1 << 4) #define CMD_RECONNECT (1 << 5) #define CMD_LOOP (1 << 6) +#define CMD_SPEED (1 << 7) /* mask for all commands */ -#define CMD_ALL ((CMD_LOOP << 1) - 1) +#define CMD_ALL ((CMD_SPEED << 1) - 1) #define GST_ELEMENT_PROGRESS(el, type, code, text) \ G_STMT_START { \ @@ -736,6 +754,18 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SMART_PROPERTIES, + g_param_spec_string ("smart-properties", "Smart Properties", + "Hold various property values for reply custom query", NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_DRM_HEADER, + g_param_spec_string ("drm-header", "drm-header", + "SKB CAS DRM header", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_FIRST_PTS, + g_param_spec_double ("first-pts", "first-pts", "SKB FIRST PTS", 0.0, + 65536.0, 0.0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** * GstRTSPSrc::tls-database: * @@ -1103,10 +1133,21 @@ gst_rtspsrc_init (GstRTSPSrc * src) g_rec_mutex_init (&src->state_rec_lock); src->state = GST_RTSP_STATE_INVALID; + src->running = FALSE; g_mutex_init (&src->conninfo.send_lock); g_mutex_init (&src->conninfo.recv_lock); + src->smart_prop = NULL; + src->app_type = APP_TYPE_DEFAULT; + src->drm_header = NULL; + src->first_pts = 0.0; + src->remove_drm_header = TRUE; + src->speed_control = FALSE; + src->close_control = FALSE; + src->resumed_playpos = 0; + src->timeout_count = 0; + GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); gst_bin_set_suppressed_flags (GST_BIN (src), GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); @@ -1144,6 +1185,11 @@ gst_rtspsrc_finalize (GObject * object) if (rtspsrc->tls_interaction) g_object_unref (rtspsrc->tls_interaction); + if (rtspsrc->smart_prop) { + gst_structure_free (rtspsrc->smart_prop); + rtspsrc->smart_prop = NULL; + } + /* free locks */ g_rec_mutex_clear (&rtspsrc->stream_rec_lock); g_rec_mutex_clear (&rtspsrc->state_rec_lock); @@ -1238,6 +1284,16 @@ gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout) rtspsrc->ptcp_timeout = NULL; } +static gboolean +gst_rtspsrc_set_smart_properties (GQuark field_id, const GValue * value, + gpointer user_data) +{ + GstRTSPSrc *src = GST_RTSPSRC_CAST (user_data); + + gst_structure_id_set_value (src->smart_prop, field_id, value); + return TRUE; +} + static void gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) @@ -1274,6 +1330,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, break; case PROP_CONNECTION_SPEED: rtspsrc->connection_speed = g_value_get_uint64 (value); + if (rtspsrc->app_type == APP_TYPE_SKB && + (gint64) rtspsrc->connection_speed > 0) + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_SPEED, CMD_LOOP); break; case PROP_NAT_METHOD: rtspsrc->nat_method = g_value_get_enum (value); @@ -1399,6 +1458,41 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_BACKCHANNEL: rtspsrc->backchannel = g_value_get_enum (value); break; + case PROP_SMART_PROPERTIES: + { + GstStructure *s = NULL; + const gchar *maps = g_value_get_string (value); + + s = gst_structure_from_string (maps, NULL); + GST_INFO_OBJECT (rtspsrc, + "passed string is [%s], result structure is [%" GST_PTR_FORMAT "]", + maps, s); + + if (!rtspsrc->smart_prop) + rtspsrc->smart_prop = gst_structure_copy (s); + else + gst_structure_foreach (s, gst_rtspsrc_set_smart_properties, rtspsrc); + + if (rtspsrc->smart_prop) { + const gchar *apptype_string = + gst_structure_get_string (rtspsrc->smart_prop, "app-type"); + if (apptype_string != NULL) { + if (!g_strcmp0 (apptype_string, "SKB")) + rtspsrc->app_type = APP_TYPE_SKB; + } + if (gst_structure_get_clock_time (rtspsrc->smart_prop, "play-offset", + (GstClockTime *) & rtspsrc->resumed_playpos)) { + if (rtspsrc->resumed_playpos > 0) + rtspsrc->resumed_playpos *= GST_MSECOND; + GST_INFO_OBJECT (rtspsrc, + "resumed play offset %" GST_TIME_FORMAT, + GST_TIME_ARGS (rtspsrc->resumed_playpos)); + } else { + GST_INFO_OBJECT (rtspsrc, "fail to parse play-offset"); + } + } + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1560,6 +1654,12 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_BACKCHANNEL: g_value_set_enum (value, rtspsrc->backchannel); break; + case PROP_DRM_HEADER: + g_value_set_string (value, rtspsrc->drm_header); + break; + case PROP_FIRST_PTS: + g_value_set_double (value, rtspsrc->first_pts); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2428,6 +2528,15 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) stop_type = GST_SEEK_TYPE_SET; } + if (src->app_type == APP_TYPE_SKB) { + gst_rtspsrc_get_position (src); + if (src->last_pos == -1) { + GST_INFO_OBJECT (src, "seek is in progress, pos-%" GST_TIME_FORMAT, + GST_TIME_ARGS (src->last_pos)); + return TRUE; + } + } + /* get flush flag */ flush = flags & GST_SEEK_FLAG_FLUSH; skip = flags & GST_SEEK_FLAG_SKIP; @@ -2478,7 +2587,8 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) if (playing) { /* obtain current position in case seek fails */ gst_rtspsrc_get_position (src); - gst_rtspsrc_pause (src, FALSE); + if (src->app_type != APP_TYPE_SKB) + gst_rtspsrc_pause (src, FALSE); } src->skip = skip; @@ -2701,6 +2811,27 @@ gst_rtspsrc_handle_internal_src_query (GstPad * pad, GstObject * parent, return res; } +static gboolean +gst_rtspsrc_copy_smart_properties (GQuark field_id, GValue * value, + gpointer user_data) +{ + GstRTSPSrc *src = GST_RTSPSRC_CAST (user_data); + + const GValue *src_value; + + if (!gst_structure_id_has_field (src->smart_prop, field_id)) { + GST_WARNING_OBJECT (src, "%s field does not exist in smart-properties", + g_quark_to_string (field_id)); + return TRUE; + } + + src_value = gst_structure_id_get_value (src->smart_prop, field_id); + g_value_unset (value); + g_value_init (value, G_VALUE_TYPE (src_value)); + g_value_copy (src_value, value); + return TRUE; +} + /* this query is executed on the ghost source pad exposed on rtspsrc. */ static gboolean gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, @@ -2775,6 +2906,21 @@ gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, } break; } + case GST_QUERY_CUSTOM: + { + GstStructure *s; + + s = gst_query_writable_structure (query); + if (gst_structure_has_name (s, "smart-properties")) { + GST_INFO_OBJECT (src, "got %s query", gst_structure_get_name (s)); + + gst_structure_map_in_place (s, gst_rtspsrc_copy_smart_properties, src); + + res = TRUE; + } else + res = FALSE; + break; + } default: { GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)); @@ -3107,7 +3253,7 @@ request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) static void gst_rtspsrc_do_stream_eos (GstRTSPSrc * src, GstRTSPStream * stream) { - GST_DEBUG_OBJECT (src, "setting stream for session %u to EOS", stream->id); + GST_INFO_OBJECT (src, "setting stream for session %u to EOS", stream->id); if (stream->eos) goto was_eos; @@ -4838,6 +4984,15 @@ gst_rtspsrc_send_keep_alive (GstRTSPSrc * src) return GST_RTSP_OK; } + if (src->app_type == APP_TYPE_SKB) { + if (src->task == NULL || src->state == GST_RTSP_STATE_READY) { + GST_INFO_OBJECT (src, "GST_RTSP_EINTR with src->task is NULL"); + return GST_RTSP_EINTR; + } + gst_rtsp_connection_reset_timeout (src->conninfo.connection); + return GST_RTSP_OK; + } + GST_DEBUG_OBJECT (src, "creating server keep-alive"); /* find a method to use for keep-alive */ @@ -5134,12 +5289,42 @@ gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "doing receive with timeout %ld seconds, %ld usec", tv_timeout.tv_sec, tv_timeout.tv_usec); + if (src->app_type == APP_TYPE_SKB && src->running) { + if (src->remove_drm_header && src->drm_header) { + + GST_INFO_OBJECT (src, + "try to remove drm header information from VOD stream"); + res = + gst_rtsp_connection_remove_drmheader (src->conninfo.connection, + src->drm_header, src->ptcp_timeout); + src->remove_drm_header = FALSE; + switch (res) { + case GST_RTSP_OK: + GST_INFO_OBJECT (src, + "Success to remove drm header information from VOD stream"); + break; + case GST_RTSP_EINTR: + goto interrupt; + default: + goto receive_error; + } + + if (src->conninfo.connection) + gst_rtsp_connection_set_qos_dscp (src->conninfo.connection, 24); + } + } + /* protect the connection with the connection lock so that we can see when * we are finished doing server communication */ res = gst_rtspsrc_connection_receive (src, &src->conninfo, &message, src->ptcp_timeout); + if (gst_rtspsrc_check_timeout (src, res)) { + GST_INFO_OBJECT (src, "GST_RTSP_EINTR with receive timeout"); + goto interrupt; + } + switch (res) { case GST_RTSP_OK: GST_DEBUG_OBJECT (src, "we received a server message"); @@ -5157,7 +5342,11 @@ gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) /* go EOS when the server closed the connection */ goto server_eof; default: - goto receive_error; + if (src->app_type == APP_TYPE_SKB) { + GST_ERROR_OBJECT (src, "error type %d", res); + break; + } else + goto receive_error; } switch (message.type) { @@ -5648,6 +5837,12 @@ gst_rtspsrc_loop (GstRTSPSrc * src) * EOS so the app knows about the error first. */ GST_ELEMENT_FLOW_ERROR (src, ret); gst_rtspsrc_push_event (src, gst_event_new_eos ()); + } else if (src->app_type == APP_TYPE_SKB) { + if (src->task != NULL && + src->connection_speed != DEFAULT_CONNECTION_SPEED) { + GST_INFO_OBJECT (src, "handling CMD_SPEED"); + return FALSE; + } } gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_LOOP); return FALSE; @@ -5839,8 +6034,20 @@ gst_rtsp_src_receive_response (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, { GstRTSPStatusCode thecode; gchar *content_base = NULL; - GstRTSPResult res = gst_rtspsrc_connection_receive (src, conninfo, - response, src->ptcp_timeout); + GstRTSPResult res; + +next: + res = + gst_rtspsrc_connection_receive (src, conninfo, response, + src->ptcp_timeout); + + if ((src->app_type == APP_TYPE_SKB) && (res == GST_RTSP_EINTR)) { + if (!src->speed_control && !src->close_control) { + GST_INFO_OBJECT (src, "receive - GST_RTSP_EINTR, try again"); + gst_rtsp_connection_flush (conninfo->connection, FALSE); + goto next; + } + } if (res < 0) goto receive_error; @@ -5865,7 +6072,6 @@ gst_rtsp_src_receive_response (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, /* get next response */ GST_DEBUG_OBJECT (src, "handle data response message"); gst_rtspsrc_handle_data (src, response); - /* Not a response, receive next message */ return gst_rtsp_src_receive_response (src, conninfo, response, code); default: @@ -5904,13 +6110,23 @@ gst_rtsp_src_receive_response (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, switch (res) { case GST_RTSP_EEOF: return GST_RTSP_EEOF; + case GST_RTSP_EPARSE: + if (src->app_type == APP_TYPE_SKB) { + GST_INFO_OBJECT (src, "handling eparse error %d", res); + return GST_RTSP_OK; + } default: { gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), - ("Could not receive message. (%s)", str)); + if (src->speed_control) { + GST_WARNING_OBJECT (src, "ignore receive error %s", str); + g_free (str); + return GST_RTSP_OK; + } else + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "receive interrupted"); } @@ -5961,7 +6177,15 @@ gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, DEBUG_RTSP (src, request); +resend: res = gst_rtspsrc_connection_send (src, conninfo, request, src->ptcp_timeout); + if ((src->app_type == APP_TYPE_SKB) && (res == GST_RTSP_EINTR)) { + if (!src->speed_control && !src->close_control) { + GST_INFO_OBJECT (src, "send - GST_RTSP_EINTR, try again"); + gst_rtsp_connection_flush (conninfo->connection, FALSE); + goto resend; + } + } if (res < 0) goto send_error; @@ -5989,8 +6213,13 @@ gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), - ("Could not send message. (%s)", str)); + if (src->speed_control) { + GST_WARNING_OBJECT (src, "ignore send error %s", str); + g_free (str); + return GST_RTSP_OK; + } else + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "send interrupted"); } @@ -7443,6 +7672,29 @@ gst_rtspsrc_retrieve_sdp (GstRTSPSrc * src, GstSDPMessage ** sdp, gst_sdp_message_new (sdp); gst_sdp_message_parse_buffer (data, size, *sdp); + if (src->app_type == APP_TYPE_SKB) { + GstSDPMedia *pMedia = &g_array_index ((*sdp)->medias, GstSDPMedia, 0); + if (pMedia->attributes->len > 0) { + + guint i; + for (i = 0; i < pMedia->attributes->len; i++) { + + GstSDPAttribute *pAttr = + &g_array_index (pMedia->attributes, GstSDPAttribute, i); + if (g_strcmp0 (pAttr->key, "drm") == 0) { + src->drm_header = pAttr->value; + GST_INFO_OBJECT (src, "SKB VOD:DRMHeader - %s, size -%zu \n", + src->drm_header, strlen (src->drm_header)); + } + if (g_strcmp0 (pAttr->key, "firstpts") == 0) { + src->first_pts = g_strtod (pAttr->value, NULL); + GST_INFO_OBJECT (src, "SKB VOD:FirstPts - %s -> %f \n", pAttr->value, + src->first_pts); + } + } + } + } + /* clean up any messages */ gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); @@ -7929,7 +8181,19 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async, if (res < 0) goto create_request_failed; + if (src->app_type == APP_TYPE_SKB) { + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_USER_AGENT, + "Zooinnet SDK v1.3.12"); + } + if (src->need_range && src->seekable >= 0.0) { + + if (src->app_type == APP_TYPE_SKB && src->resumed_playpos > 0) { + segment->position = src->resumed_playpos; + src->segment.position = src->resumed_playpos; + src->resumed_playpos = 0; + } + hval = gen_range_header (src, segment); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); @@ -8335,7 +8599,7 @@ gst_rtspsrc_thread (GstRTSPSrc * src) GST_OBJECT_LOCK (src); cmd = src->pending_cmd; if (cmd == CMD_RECONNECT || cmd == CMD_PLAY || cmd == CMD_PAUSE - || cmd == CMD_LOOP || cmd == CMD_OPEN) + || cmd == CMD_LOOP || cmd == CMD_SPEED) src->pending_cmd = CMD_LOOP; else src->pending_cmd = CMD_WAIT; @@ -8352,12 +8616,17 @@ gst_rtspsrc_thread (GstRTSPSrc * src) gst_rtspsrc_open (src, TRUE); break; case CMD_PLAY: - gst_rtspsrc_play (src, &src->segment, TRUE, NULL); + if (gst_rtspsrc_play (src, &src->segment, TRUE, NULL) < 0) { + GST_OBJECT_LOCK (src); + src->pending_cmd = CMD_WAIT; + GST_OBJECT_UNLOCK (src); + } break; case CMD_PAUSE: gst_rtspsrc_pause (src, TRUE); break; case CMD_CLOSE: + src->close_control = FALSE; gst_rtspsrc_close (src, TRUE, FALSE); break; case CMD_LOOP: @@ -8366,6 +8635,9 @@ gst_rtspsrc_thread (GstRTSPSrc * src) case CMD_RECONNECT: gst_rtspsrc_reconnect (src, FALSE); break; + case CMD_SPEED: + gst_rtspsrc_do_send_speed (src); + break; default: break; } @@ -8414,6 +8686,7 @@ static gboolean gst_rtspsrc_stop (GstRTSPSrc * src) { GstTask *task; + gboolean only_close = TRUE; GST_DEBUG_OBJECT (src, "stopping"); @@ -8441,8 +8714,13 @@ gst_rtspsrc_stop (GstRTSPSrc * src) } GST_OBJECT_UNLOCK (src); + if (src->close_control) { + only_close = FALSE; + src->close_control = FALSE; + } + /* ensure synchronously all is closed and clean */ - gst_rtspsrc_close (src, FALSE, TRUE); + gst_rtspsrc_close (src, FALSE, only_close); return TRUE; } @@ -8506,7 +8784,8 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_READY: - gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_CLOSE, CMD_ALL); + rtspsrc->close_control = TRUE; + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_CLOSE, CMD_PAUSE | CMD_SPEED); ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_READY_TO_NULL: @@ -8682,6 +8961,119 @@ gst_rtspsrc_uri_handler_init (gpointer g_iface, gpointer iface_data) iface->set_uri = gst_rtspsrc_uri_set_uri; } +static gboolean +gst_rtspsrc_check_timeout (GstRTSPSrc * src, GstRTSPResult res) +{ + gboolean ret = FALSE; + + if (res == GST_RTSP_ETIMEOUT) { + if (src->state == GST_RTSP_STATE_PLAYING) { + GST_INFO_OBJECT (src, "timeout"); + src->timeout_count++; + if (src->app_type == APP_TYPE_SKB && src->timeout_count > 2) { + ret = TRUE; + } + } + gst_rtsp_connection_reset_timeout (src->conninfo.connection); + } else + src->timeout_count = 0; + + return ret; +} + +static GstRTSPResult +gst_rtspsrc_send_speed (GstRTSPSrc * src) +{ + GstRTSPMessage request = { 0, }, response = { + 0,}; + GstRTSPResult res; + GstRTSPMethod method; + gchar *control = NULL; + gchar hval[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; + gdouble speed; + + src->speed_control = TRUE; + + if (src->state == GST_RTSP_STATE_READY) + method = GST_RTSP_PAUSE; + else if (src->state == GST_RTSP_STATE_PLAYING) + method = GST_RTSP_PLAY; + else + goto error_playing_state; + + if (!src->conninfo.connection || !src->conninfo.connected) + goto no_connection; + + /* construct a control url */ + if (src->control) + control = src->control; + else + control = src->conninfo.url_str; + + if (control == NULL) + goto error_no_url; + + res = gst_rtsp_message_init_request (&request, method, control); + if (res < 0) + goto error_send_speed; + + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_USER_AGENT, + "Zooinnet SDK v1.3.12"); + + speed = (gdouble) src->connection_speed / 10.000000; + g_sprintf (hval, "%06f", speed); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval); + + GST_INFO_OBJECT (src, "connection speed %" G_GUINT64_FORMAT, + src->connection_speed); + res = gst_rtspsrc_try_send (src, &src->conninfo, &request, &response, NULL); + if (res < 0) + goto error_send_speed; + + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + return GST_RTSP_OK; + +error_playing_state: + { + GST_ERROR_OBJECT (src, "Not Playing State"); + return GST_RTSP_OK; + } +no_connection: + { + GST_ERROR_OBJECT (src, "No Connection"); + return GST_RTSP_OK; + } +error_no_url: + { + GST_ERROR_OBJECT (src, "No Control URL"); + return GST_RTSP_OK; + } +error_send_speed: + { + gchar *str = gst_rtsp_strresult (res); + gst_rtsp_message_unset (&request); + GST_ERROR_OBJECT (src, "Could not send speed(%s)", str); + g_free (str); + return res; + } +} + +static void +gst_rtspsrc_do_send_speed (GstRTSPSrc * src) +{ + if (src->app_type == APP_TYPE_SKB) { + + if (src->task != NULL && src->connection_speed != DEFAULT_CONNECTION_SPEED) { + gst_rtspsrc_send_speed (src); + src->speed_control = FALSE; + src->connection_speed = DEFAULT_CONNECTION_SPEED; + } + } + return; +} + typedef struct _RTSPKeyValue { GstRTSPHeaderField field; diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h index 4e5adeff7e..9b8895b645 100644 --- a/gst/rtsp/gstrtspsrc.h +++ b/gst/rtsp/gstrtspsrc.h @@ -291,6 +291,16 @@ struct _GstRTSPSrc { GstRTSPConnInfo conninfo; + GstStructure *smart_prop; + gint app_type; + gchar *drm_header; + gdouble first_pts; + gboolean remove_drm_header; + gboolean speed_control; + gboolean close_control; + guint64 resumed_playpos; + gint timeout_count; + /* a list of RTSP extensions as GstElement */ GstRTSPExtensionList *extensions; diff --git a/gst/udp/gstudpsrc.c b/gst/udp/gstudpsrc.c index fdeca5dadd..1b895374e6 100644 --- a/gst/udp/gstudpsrc.c +++ b/gst/udp/gstudpsrc.c @@ -424,7 +424,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", #define UDP_DEFAULT_URI "udp://"UDP_DEFAULT_MULTICAST_GROUP":"G_STRINGIFY(UDP_DEFAULT_PORT) #define UDP_DEFAULT_CAPS NULL #define UDP_DEFAULT_SOCKET NULL -#define UDP_DEFAULT_BUFFER_SIZE 0 +#define UDP_DEFAULT_BUFFER_SIZE 0 #define UDP_DEFAULT_TIMEOUT 0 #define UDP_DEFAULT_SKIP_FIRST_BYTES 0 #define UDP_DEFAULT_CLOSE_SOCKET TRUE @@ -445,6 +445,7 @@ enum PROP_CAPS, PROP_SOCKET, PROP_BUFFER_SIZE, + PROP_FORCE_BUFFER_SIZE, PROP_TIMEOUT, PROP_SKIP_FIRST_BYTES, PROP_CLOSE_SOCKET, @@ -542,6 +543,12 @@ gst_udpsrc_class_init (GstUDPSrcClass * klass) g_param_spec_int ("buffer-size", "Buffer Size", "Size of the kernel receive buffer in bytes, 0=default", 0, G_MAXINT, UDP_DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_FORCE_BUFFER_SIZE, g_param_spec_int ("force-buffer-size", + "Force Buffer Size", + "FORCE Size of the kernel receive buffer in bytes, 0=default", 0, + G_MAXINT, UDP_DEFAULT_BUFFER_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMEOUT, g_param_spec_uint64 ("timeout", "Timeout", "Post a message after timeout nanoseconds (0 = disabled)", 0, @@ -633,6 +640,7 @@ gst_udpsrc_init (GstUDPSrc * udpsrc) udpsrc->socket = UDP_DEFAULT_SOCKET; udpsrc->multi_iface = g_strdup (UDP_DEFAULT_MULTICAST_IFACE); udpsrc->buffer_size = UDP_DEFAULT_BUFFER_SIZE; + udpsrc->force_buffer_size = UDP_DEFAULT_BUFFER_SIZE; udpsrc->timeout = UDP_DEFAULT_TIMEOUT; udpsrc->skip_first_bytes = UDP_DEFAULT_SKIP_FIRST_BYTES; udpsrc->close_socket = UDP_DEFAULT_CLOSE_SOCKET; @@ -1109,6 +1117,9 @@ gst_udpsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_BUFFER_SIZE: udpsrc->buffer_size = g_value_get_int (value); break; + case PROP_FORCE_BUFFER_SIZE: + udpsrc->force_buffer_size = udpsrc->buffer_size = g_value_get_int (value); + break; case PROP_PORT: udpsrc->port = g_value_get_int (value); g_free (udpsrc->uri); @@ -1401,14 +1412,15 @@ gst_udpsrc_open (GstUDPSrc * src) { gint val = 0; + GError *opt_err = NULL; if (src->buffer_size != 0) { - GError *opt_err = NULL; - + GST_INFO_OBJECT (src, "setting udp buffer of %d bytes", src->buffer_size); + /* set buffer size, Note that on Linux this is typically limited to a - * maximum of around 100K. Also a minimum of 128 bytes is required on - * Linux. */ + * maximum of around 100K. Also a minimum of 128 bytes is required on Linux. + * You can forcefully set the buffer_size by using force-buffer-size property */ if (!g_socket_set_option (src->used_socket, SOL_SOCKET, SO_RCVBUF, src->buffer_size, &opt_err)) { GST_ELEMENT_WARNING (src, RESOURCE, SETTINGS, (NULL), @@ -1429,6 +1441,28 @@ gst_udpsrc_open (GstUDPSrc * src) } else { GST_DEBUG_OBJECT (src, "could not get udp buffer size"); } + + /* the value of the receive buffer is less than the set buffer_size. + * Try to set the buffer_size by using SO_RCVBUFFORCE */ + if (val < src->buffer_size) { + GST_INFO_OBJECT (src, "forcibly setting udp buffer of %d bytes", + src->buffer_size); + if (!g_socket_set_option (src->used_socket, SOL_SOCKET, SO_RCVBUFFORCE, + src->buffer_size, &opt_err)) { + GST_ELEMENT_WARNING (src, RESOURCE, SETTINGS, (NULL), + ("Could not create forcibly a buffer of requested %d bytes: %s", + src->buffer_size, opt_err->message)); + g_error_free (opt_err); + opt_err = NULL; + } else { + if (g_socket_get_option (src->used_socket, SOL_SOCKET, SO_RCVBUF, &val, + NULL)) { + GST_INFO_OBJECT (src, "finally have udp buffer of %d bytes", val); + } else { + GST_DEBUG_OBJECT (src, "could not get udp buffer size"); + } + } + } } g_socket_set_broadcast (src->used_socket, TRUE); diff --git a/gst/udp/gstudpsrc.h b/gst/udp/gstudpsrc.h index 96f844b364..b75ea0a64e 100644 --- a/gst/udp/gstudpsrc.h +++ b/gst/udp/gstudpsrc.h @@ -54,6 +54,7 @@ struct _GstUDPSrc { gint ttl; GstCaps *caps; gint buffer_size; + gint force_buffer_size; guint64 timeout; gint skip_first_bytes; GSocket *socket; diff --git a/gst/wavparse/gstwavparse.c b/gst/wavparse/gstwavparse.c index a81701fca6..9e82581f21 100644 --- a/gst/wavparse/gstwavparse.c +++ b/gst/wavparse/gstwavparse.c @@ -1857,9 +1857,11 @@ gst_wavparse_send_event (GstElement * element, GstEvent * event) } static gboolean -gst_wavparse_have_dts_caps (const GstCaps * caps, GstTypeFindProbability prob) +gst_wavparse_have_dts_caps (const GstCaps * caps, GstTypeFindProbability prob, + gint wavHdFrRate) { GstStructure *s; + gint dtsHdFrRate = 0; s = gst_caps_get_structure (caps, 0); if (!gst_structure_has_name (s, "audio/x-dts")) @@ -1871,14 +1873,22 @@ gst_wavparse_have_dts_caps (const GstCaps * caps, GstTypeFindProbability prob) * to be DTS. */ if (prob > GST_TYPE_FIND_LIKELY) return TRUE; - if (prob <= GST_TYPE_FIND_POSSIBLE) + if (prob < GST_TYPE_FIND_POSSIBLE) return FALSE; /* for maybe, check for at least a valid-looking rate and channels */ if (!gst_structure_has_field (s, "channels")) return FALSE; /* and for extra assurance we could also check the rate from the DTS frame - * against the one in the wav header, but for now let's not do that */ - return gst_structure_has_field (s, "rate"); + * against the one in the wav header */ + if ((gst_structure_get_int (s, "rate", &dtsHdFrRate)) && wavHdFrRate) { + GST_LOG ("DTS header frame rate = %d, Wav header frame rate = %d", + dtsHdFrRate, wavHdFrRate); + if (dtsHdFrRate == wavHdFrRate) + return TRUE; + else + return FALSE; + } else + return FALSE; } static GstTagList * @@ -1917,11 +1927,14 @@ gst_wavparse_add_src_pad (GstWavParse * wav, GstBuffer * buf) if (s && gst_structure_has_name (s, "audio/x-raw") && buf != NULL) { GstTypeFindProbability prob; GstCaps *tf_caps; + gint wavHdFrRate = 0; tf_caps = gst_type_find_helper_for_buffer (GST_OBJECT (wav), buf, &prob); if (tf_caps != NULL) { GST_LOG ("typefind caps = %" GST_PTR_FORMAT ", P=%d", tf_caps, prob); - if (gst_wavparse_have_dts_caps (tf_caps, prob)) { + gst_structure_get_int (s, "rate", &wavHdFrRate); + + if (gst_wavparse_have_dts_caps (tf_caps, prob, wavHdFrRate)) { GST_INFO_OBJECT (wav, "Found DTS marker in file marked as raw PCM"); gst_caps_unref (wav->caps); wav->caps = tf_caps; diff --git a/meson.build b/meson.build index 6e78e86b29..09ad56efff 100644 --- a/meson.build +++ b/meson.build @@ -73,6 +73,7 @@ check_headers = [ ['HAVE_SYS_TIME_H', 'sys/time.h'], ['HAVE_SYS_TYPES_H', 'sys/types.h'], ['HAVE_UNISTD_H', 'unistd.h'], + ['HAVE_V4L2_EXT_H', 'linux/v4l2-ext/videodev2-ext.h'], ] foreach h : check_headers diff --git a/sys/v4l2/Makefile.am b/sys/v4l2/Makefile.am index 3126c11efe..c83009e64b 100644 --- a/sys/v4l2/Makefile.am +++ b/sys/v4l2/Makefile.am @@ -2,6 +2,13 @@ plugin_LTLIBRARIES = libgstvideo4linux2.la include $(top_srcdir)/common/gst-glib-gen.mak +if USE_LINUX_EXT +SCALERSRC_FILES = gstv4l2scalerobject.c \ + gstv4l2scalersrc.c +else +SCALERSRC_FILES = +endif + libgstvideo4linux2_la_SOURCES = gstv4l2.c \ gstv4l2allocator.c \ gstv4l2colorbalance.c \ @@ -25,7 +32,8 @@ libgstvideo4linux2_la_SOURCES = gstv4l2.c \ v4l2-utils.c \ tuner.c \ tunerchannel.c \ - tunernorm.c + tunernorm.c \ + $(SCALERSRC_FILES) libgstvideo4linux2_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ $(GST_BASE_CFLAGS) \ @@ -54,6 +62,8 @@ noinst_HEADERS = \ gstv4l2colorbalance.h \ gstv4l2deviceprovider.h \ gstv4l2object.h \ + gstv4l2scalerobject.h \ + gstv4l2scalersrc.h \ gstv4l2sink.h \ gstv4l2src.h \ gstv4l2radio.h \ diff --git a/sys/v4l2/gstv4l2.c b/sys/v4l2/gstv4l2.c index 2674d9cd34..c0d660a8a4 100644 --- a/sys/v4l2/gstv4l2.c +++ b/sys/v4l2/gstv4l2.c @@ -54,6 +54,7 @@ #include "gstv4l2vp9enc.h" #include "gstv4l2deviceprovider.h" #include "gstv4l2transform.h" +#include "gstv4l2scalersrc.h" /* used in gstv4l2object.c and v4l2_calls.c */ GST_DEBUG_CATEGORY (v4l2_debug); @@ -243,6 +244,10 @@ plugin_init (GstPlugin * plugin) GST_TYPE_V4L2SINK) || !gst_element_register (plugin, "v4l2radio", GST_RANK_NONE, GST_TYPE_V4L2RADIO) || +#ifdef HAVE_LINUX_EXT + !gst_element_register (plugin, "v4l2scalersrc", + GST_RANK_NONE, GST_TYPE_V4L2_SCALER_SRC) || +#endif !gst_device_provider_register (plugin, "v4l2deviceprovider", GST_RANK_PRIMARY, GST_TYPE_V4L2_DEVICE_PROVIDER) /* etc. */ diff --git a/sys/v4l2/gstv4l2allocator.c b/sys/v4l2/gstv4l2allocator.c index 7e0e7c260d..fdbb6f29ee 100644 --- a/sys/v4l2/gstv4l2allocator.c +++ b/sys/v4l2/gstv4l2allocator.c @@ -1226,6 +1226,7 @@ gst_v4l2_allocator_qbuf (GstV4l2Allocator * allocator, GstV4l2Object *obj = allocator->obj; gboolean ret = TRUE; gint i; + gint64 start = 0, end = 0; g_return_val_if_fail (g_atomic_int_get (&allocator->active), FALSE); @@ -1242,6 +1243,8 @@ gst_v4l2_allocator_qbuf (GstV4l2Allocator * allocator, for (i = 0; i < group->n_mem; i++) gst_memory_ref (group->mem[i]); + start = g_get_monotonic_time (); + if (obj->ioctl (obj->video_fd, VIDIOC_QBUF, &group->buffer) < 0) { GST_ERROR_OBJECT (allocator, "failed queueing buffer %i: %s", group->buffer.index, g_strerror (errno)); @@ -1259,6 +1262,11 @@ gst_v4l2_allocator_qbuf (GstV4l2Allocator * allocator, goto done; } + end = g_get_monotonic_time (); + GST_TRACE_OBJECT (allocator, + "log=VIDIOC_QBUF, term=%lld, time=%lld", + end - start, g_get_monotonic_time ()); + GST_LOG_OBJECT (allocator, "queued buffer %i (flags 0x%X)", group->buffer.index, group->buffer.flags); @@ -1280,6 +1288,7 @@ gst_v4l2_allocator_dqbuf (GstV4l2Allocator * allocator, struct v4l2_buffer buffer = { 0 }; struct v4l2_plane planes[VIDEO_MAX_PLANES] = { {0} }; gint i; + gint64 start = 0, end = 0; GstV4l2MemoryGroup *group = NULL; @@ -1293,9 +1302,16 @@ gst_v4l2_allocator_dqbuf (GstV4l2Allocator * allocator, buffer.m.planes = planes; } + start = g_get_monotonic_time (); + if (obj->ioctl (obj->video_fd, VIDIOC_DQBUF, &buffer) < 0) goto error; + end = g_get_monotonic_time (); + GST_TRACE_OBJECT (allocator, + "log=VIDIOC_DQBUF, term=%lld, time=%lld", + end - start, g_get_monotonic_time ()); + group = allocator->groups[buffer.index]; if (!IS_QUEUED (group->buffer)) { diff --git a/sys/v4l2/gstv4l2bufferpool.c b/sys/v4l2/gstv4l2bufferpool.c index 5a92dbb258..a3efc22e5b 100644 --- a/sys/v4l2/gstv4l2bufferpool.c +++ b/sys/v4l2/gstv4l2bufferpool.c @@ -987,14 +987,22 @@ static GstFlowReturn gst_v4l2_buffer_pool_poll (GstV4l2BufferPool * pool) { gint ret; + gint64 start = 0, end = 0; /* In RW mode there is no queue, hence no need to wait while the queue is * empty */ if (pool->obj->mode != GST_V4L2_IO_RW) { + start = g_get_monotonic_time (); + GST_OBJECT_LOCK (pool); while (pool->empty) g_cond_wait (&pool->empty_cond, GST_OBJECT_GET_LOCK (pool)); GST_OBJECT_UNLOCK (pool); + + end = g_get_monotonic_time (); + GST_TRACE_OBJECT (pool, + "log=POOL_EMPTY, term=%lld, time=%lld", + end - start, g_get_monotonic_time ()); } if (!pool->can_poll_device) @@ -1003,7 +1011,15 @@ gst_v4l2_buffer_pool_poll (GstV4l2BufferPool * pool) GST_LOG_OBJECT (pool, "polling device"); again: + start = g_get_monotonic_time (); + ret = gst_poll_wait (pool->poll, GST_CLOCK_TIME_NONE); + + end = g_get_monotonic_time (); + GST_TRACE_OBJECT (pool, + "log=POLL_WAIT, term=%lld, time=%lld", + end - start, g_get_monotonic_time ()); + if (G_UNLIKELY (ret < 0)) { switch (errno) { case EBUSY: diff --git a/sys/v4l2/gstv4l2object.c b/sys/v4l2/gstv4l2object.c index 124c778c62..8e90508bbe 100644 --- a/sys/v4l2/gstv4l2object.c +++ b/sys/v4l2/gstv4l2object.c @@ -42,6 +42,7 @@ #include "gst/gst-i18n-plugin.h" #include +#include GST_DEBUG_CATEGORY_EXTERN (v4l2_debug); #define GST_CAT_DEFAULT v4l2_debug @@ -2880,7 +2881,8 @@ gst_v4l2_object_is_dmabuf_supported (GstV4l2Object * v4l2object) .flags = O_CLOEXEC | O_RDWR, }; - if (v4l2object->fmtdesc->flags & V4L2_FMT_FLAG_EMULATED) { + if (v4l2object->fmtdesc && + v4l2object->fmtdesc->flags & V4L2_FMT_FLAG_EMULATED) { GST_WARNING_OBJECT (v4l2object->dbg_obj, "libv4l2 converter detected, disabling DMABuf"); ret = FALSE; @@ -4164,6 +4166,7 @@ gst_v4l2_object_probe_caps (GstV4l2Object * v4l2object, GstCaps * filter) GstCaps *ret; GSList *walk; GSList *formats; + gboolean use_dmabuf = FALSE; formats = gst_v4l2_object_get_format_list (v4l2object); @@ -4188,6 +4191,11 @@ gst_v4l2_object_probe_caps (GstV4l2Object * v4l2object, GstCaps * filter) } } + if ((v4l2object->req_mode == GST_V4L2_IO_AUTO + || v4l2object->req_mode == GST_V4L2_IO_DMABUF) + && gst_v4l2_object_is_dmabuf_supported (v4l2object)) + use_dmabuf = TRUE; + for (walk = formats; walk; walk = walk->next) { struct v4l2_fmtdesc *format; GstStructure *template; @@ -4209,6 +4217,12 @@ gst_v4l2_object_probe_caps (GstV4l2Object * v4l2object, GstCaps * filter) GstCaps *format_caps = gst_caps_new_empty (); gst_caps_append_structure (format_caps, gst_structure_copy (template)); + /* If we can use dmabuf, duplicate structure with DMABuf caps feature */ + if (use_dmabuf) { + gst_caps_append_structure (format_caps, gst_structure_copy (template)); + gst_caps_set_features (format_caps, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_DMABUF, NULL)); + } if (!gst_caps_can_intersect (format_caps, filter)) { gst_caps_unref (format_caps); @@ -4221,8 +4235,15 @@ gst_v4l2_object_probe_caps (GstV4l2Object * v4l2object, GstCaps * filter) tmp = gst_v4l2_object_probe_caps_for_format (v4l2object, format->pixelformat, template); - if (tmp) + if (tmp) { + if (use_dmabuf) { + GstCaps *caps_dmabuf = gst_caps_copy (tmp); + gst_caps_set_features (caps_dmabuf, 0, + gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_DMABUF, NULL)); + gst_caps_append (ret, caps_dmabuf); + } gst_caps_append (ret, tmp); + } gst_structure_free (template); } diff --git a/sys/v4l2/gstv4l2scalerobject.c b/sys/v4l2/gstv4l2scalerobject.c new file mode 100644 index 0000000000..db8e522716 --- /dev/null +++ b/sys/v4l2/gstv4l2scalerobject.c @@ -0,0 +1,586 @@ +/* GStreamer + * + * Copyright (C) 2001-2002 Ronald Bultje + * 2006 Edgard Lima + * 2019 LG Electronics, Inc. + * + * gstv4l2scalerobject.c: base class for LG's V4L2 scaler element + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. This library is distributed in the hope + * that it will be useful, but WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Library General Public License for more details. + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gstv4l2scalerobject.h" +#include "gst/gst-i18n-plugin.h" + +GST_DEBUG_CATEGORY_EXTERN (v4l2_debug); +#define GST_CAT_DEFAULT v4l2_debug + +/* Support for 32bit off_t, this wrapper is casting off_t to gint64 */ +#ifdef HAVE_LIBV4L2 +#if SIZEOF_OFF_T < 8 + +static gpointer +v4l2_mmap_wrapper (gpointer start, gsize length, gint prot, gint flags, gint fd, + off_t offset) +{ + return v4l2_mmap (start, length, prot, flags, fd, (gint64) offset); +} + +#define v4l2_mmap v4l2_mmap_wrapper + +#endif /* SIZEOF_OFF_T < 8 */ +#endif /* HAVE_LIBV4L2 */ + +GstV4l2ScalerObject * +gst_v4l2_scaler_object_new (GstElement * element, + GstObject * debug_object, + enum v4l2_buf_type type, + const char *default_device, + GstV4l2GetInOutFunction get_in_out_func, + GstV4l2SetInOutFunction set_in_out_func, + GstV4l2UpdateFpsFunction update_fps_func) +{ + GstV4l2ScalerObject *v4l2scalerobject; + GstV4l2Object *v4l2object; + + /* + * some default values + */ + v4l2scalerobject = g_new0 (GstV4l2ScalerObject, 1); + + v4l2object = (GstV4l2Object *) v4l2scalerobject; + + v4l2object->type = type; + v4l2object->formats = NULL; + + v4l2object->element = element; + v4l2object->dbg_obj = debug_object; + v4l2object->get_in_out_func = get_in_out_func; + v4l2object->set_in_out_func = set_in_out_func; + v4l2object->update_fps_func = update_fps_func; + + v4l2object->video_fd = -1; + v4l2object->active = FALSE; + v4l2object->videodev = g_strdup (default_device); + + v4l2object->norms = NULL; + v4l2object->channels = NULL; + v4l2object->colors = NULL; + + v4l2object->keep_aspect = TRUE; + + v4l2object->n_v4l2_planes = 0; + + v4l2object->no_initial_format = FALSE; + + /* We now disable libv4l2 by default, but have an env to enable it. */ +#ifdef HAVE_LIBV4L2 + if (g_getenv ("GST_V4L2_USE_LIBV4L2")) { + v4l2object->fd_open = v4l2_fd_open; + v4l2object->close = v4l2_close; + v4l2object->dup = v4l2_dup; + v4l2object->ioctl = v4l2_ioctl; + v4l2object->read = v4l2_read; + v4l2object->mmap = v4l2_mmap; + v4l2object->munmap = v4l2_munmap; + } else +#endif + { + v4l2object->fd_open = NULL; + v4l2object->close = close; + v4l2object->dup = dup; + v4l2object->ioctl = ioctl; + v4l2object->read = read; + v4l2object->mmap = mmap; + v4l2object->munmap = munmap; + } + + v4l2scalerobject->vdec_index = 0; + v4l2scalerobject->max_width = 0; + v4l2scalerobject->max_height = 0; + v4l2scalerobject->scalable = TRUE; + v4l2scalerobject->destination_caps = NULL; + v4l2scalerobject->vdo_fd = -1; + + return v4l2scalerobject; +} + +static gboolean gst_v4l2_scaler_object_clear_format_list (GstV4l2ScalerObject * + v4l2scalerobject); + +void +gst_v4l2_scaler_object_destroy (GstV4l2ScalerObject * v4l2scalerobject) +{ + GstV4l2Object *v4l2object; + + g_return_if_fail (v4l2scalerobject != NULL); + + v4l2object = (GstV4l2Object *) v4l2scalerobject; + + g_free (v4l2object->videodev); + + g_free (v4l2object->channel); + + if (v4l2object->formats) { + gst_v4l2_scaler_object_clear_format_list (v4l2scalerobject); + } + + if (v4l2object->probed_caps) { + gst_caps_unref (v4l2object->probed_caps); + } + + if (v4l2object->extra_controls) { + gst_structure_free (v4l2object->extra_controls); + } + + if (v4l2scalerobject->destination_caps) + gst_caps_unref (v4l2scalerobject->destination_caps); + + g_free (v4l2scalerobject); +} + +static gboolean +gst_v4l2_scaler_object_clear_format_list (GstV4l2ScalerObject * + v4l2scalerobject) +{ + GstV4l2Object *v4l2object = (GstV4l2Object *) v4l2scalerobject; + + g_slist_foreach (v4l2object->formats, (GFunc) g_free, NULL); + g_slist_free (v4l2object->formats); + v4l2object->formats = NULL; + + return TRUE; +} + +GstCaps * +gst_v4l2_scaler_object_get_caps (GstV4l2ScalerObject * v4l2scalerobject, + GstCaps * filter) +{ + GstCaps *ret, *tmp; + GstV4l2Object *v4l2object = (GstV4l2Object *) v4l2scalerobject; + + if (v4l2object->probed_caps == NULL) + v4l2object->probed_caps = gst_v4l2_object_probe_caps (v4l2object, NULL); + + if (v4l2scalerobject->destination_caps) { + tmp = gst_caps_intersect_full (v4l2scalerobject->destination_caps, + v4l2object->probed_caps, GST_CAPS_INTERSECT_FIRST); + } else { + tmp = gst_caps_ref (v4l2object->probed_caps); + } + + if (filter) { + ret = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); + } else { + ret = gst_caps_ref (tmp); + } + + gst_caps_unref (tmp); + return ret; +} + +static gboolean +gst_v4l2_scaler_object_is_dmabuf_supported (GstV4l2ScalerObject * + v4l2scalerobject) +{ + GstV4l2Object *v4l2object = (GstV4l2Object *) v4l2scalerobject; + gboolean ret = TRUE; + struct v4l2_exportbuffer expbuf = { + .type = v4l2object->type, + .index = -1, + .plane = -1, + .flags = O_CLOEXEC | O_RDWR, + }; + + if (v4l2object->fmtdesc && + v4l2object->fmtdesc->flags & V4L2_FMT_FLAG_EMULATED) { + GST_WARNING_OBJECT (v4l2object->dbg_obj, + "libv4l2 converter detected, disabling DMABuf"); + ret = FALSE; + } + + /* Expected to fail, but ENOTTY tells us that it is not implemented. */ + v4l2object->ioctl (v4l2object->video_fd, VIDIOC_EXPBUF, &expbuf); + if (errno == ENOTTY) + ret = FALSE; + + return ret; +} + +static void +gst_v4l2_scaler_get_driver_min_buffers (GstV4l2ScalerObject * v4l2scalerobject) +{ + GstV4l2Object *v4l2object = (GstV4l2Object *) v4l2scalerobject; + struct v4l2_control control = { 0, }; + + g_return_if_fail (GST_V4L2_IS_OPEN (v4l2object)); + + if (V4L2_TYPE_IS_OUTPUT (v4l2object->type)) + control.id = V4L2_CID_MIN_BUFFERS_FOR_OUTPUT; + else + control.id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE; + + if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) == 0) { + GST_DEBUG_OBJECT (v4l2object->dbg_obj, + "driver requires a minimum of %d buffers", control.value); + v4l2object->min_buffers = control.value; + } else { + v4l2object->min_buffers = 0; + } +} + +static gboolean +gst_v4l2_scaler_object_setup_pool (GstV4l2ScalerObject * v4l2scalerobject, + GstCaps * caps) +{ + GstV4l2Object *v4l2object = (GstV4l2Object *) v4l2scalerobject; + GstV4l2IOMode mode; + + GST_DEBUG_OBJECT (v4l2object->dbg_obj, "initializing the %s system", + V4L2_TYPE_IS_OUTPUT (v4l2object->type) ? "output" : "capture"); + + GST_V4L2_CHECK_OPEN (v4l2object); + GST_V4L2_CHECK_NOT_ACTIVE (v4l2object); + + /* find transport */ + mode = v4l2object->req_mode; + + if (v4l2object->device_caps & V4L2_CAP_READWRITE) { + if (v4l2object->req_mode == GST_V4L2_IO_AUTO) + mode = GST_V4L2_IO_RW; + } else if (v4l2object->req_mode == GST_V4L2_IO_RW) + goto method_not_supported; + + if (v4l2object->device_caps & V4L2_CAP_STREAMING) { + if (v4l2object->req_mode == GST_V4L2_IO_AUTO) { + if (!V4L2_TYPE_IS_OUTPUT (v4l2object->type) && + gst_v4l2_scaler_object_is_dmabuf_supported (v4l2scalerobject)) { + mode = GST_V4L2_IO_DMABUF; + } else { + mode = GST_V4L2_IO_MMAP; + } + } + } else if (v4l2object->req_mode == GST_V4L2_IO_MMAP) + goto method_not_supported; + + /* if still no transport selected, error out */ + if (mode == GST_V4L2_IO_AUTO) + goto no_supported_capture_method; + + GST_INFO_OBJECT (v4l2object->dbg_obj, "accessing buffers via mode %d", mode); + v4l2object->mode = mode; + + /* If min_buffers is not set, the driver either does not support the control or + it has not been asked yet via propose_allocation/decide_allocation. */ + if (!v4l2object->min_buffers) + gst_v4l2_scaler_get_driver_min_buffers (v4l2scalerobject); + + /* Map the buffers */ + GST_LOG_OBJECT (v4l2object->dbg_obj, "initiating buffer pool"); + + if (!(v4l2object->pool = gst_v4l2_buffer_pool_new (v4l2object, caps))) + goto buffer_pool_new_failed; + + GST_V4L2_SET_ACTIVE (v4l2object); + + return TRUE; + + /* ERRORS */ +buffer_pool_new_failed: + { + GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, + (_("Could not map buffers from device '%s'"), + v4l2object->videodev), + ("Failed to create buffer pool: %s", g_strerror (errno))); + return FALSE; + } +method_not_supported: + { + GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, + (_("The driver of device '%s' does not support the IO method %d"), + v4l2object->videodev, mode), (NULL)); + return FALSE; + } +no_supported_capture_method: + { + GST_ELEMENT_ERROR (v4l2object->element, RESOURCE, READ, + (_("The driver of device '%s' does not support any known IO " + "method."), v4l2object->videodev), (NULL)); + return FALSE; + } +} + +gboolean +gst_v4l2_scaler_object_decide_allocation (GstV4l2ScalerObject * + v4l2scalerobject, GstQuery * query) +{ + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalerobject; + GstCaps *caps; + GstBufferPool *pool = NULL, *other_pool = NULL; + GstStructure *config; + guint size, min, max, own_min = 0, own_max = 0; + gboolean update; + gboolean has_video_meta; + gboolean can_share_own_pool = FALSE, pushing_from_our_pool = FALSE; + GstAllocator *allocator = NULL; + GstAllocationParams params = { 0 }; + + GST_DEBUG_OBJECT (obj->dbg_obj, "decide allocation"); + + g_return_val_if_fail (obj->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + obj->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, FALSE); + + gst_query_parse_allocation (query, &caps, NULL); + + if (obj->pool == NULL) { + if (!gst_v4l2_scaler_object_setup_pool (v4l2scalerobject, caps)) + goto pool_failed; + } + + if (gst_query_get_n_allocation_params (query) > 0) + gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + update = TRUE; + } else { + pool = NULL; + min = max = 0; + size = 0; + update = FALSE; + } + + GST_DEBUG_OBJECT (obj->dbg_obj, "allocation: size:%u min:%u max:%u pool:%" + GST_PTR_FORMAT, size, min, max, pool); + + has_video_meta = + gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + can_share_own_pool = (has_video_meta || !obj->need_video_meta); + + gst_v4l2_scaler_get_driver_min_buffers (v4l2scalerobject); + /* We can't share our own pool, if it exceed V4L2 capacity */ + if (min + obj->min_buffers + 1 > VIDEO_MAX_FRAME) + can_share_own_pool = FALSE; + + /* select a pool */ + switch (obj->mode) { + case GST_V4L2_IO_RW: + if (pool) { + /* in READ/WRITE mode, prefer a downstream pool because our own pool + * doesn't help much, we have to write to it as well */ + GST_DEBUG_OBJECT (obj->dbg_obj, + "read/write mode: using downstream pool"); + /* use the bigest size, when we use our own pool we can't really do any + * other size than what the hardware gives us but for downstream pools + * we can try */ + size = MAX (size, obj->info.size); + } else if (can_share_own_pool) { + /* no downstream pool, use our own then */ + GST_DEBUG_OBJECT (obj->dbg_obj, + "read/write mode: no downstream pool, using our own"); + pool = gst_object_ref (obj->pool); + size = obj->info.size; + pushing_from_our_pool = TRUE; + } + break; + + case GST_V4L2_IO_USERPTR: + case GST_V4L2_IO_DMABUF_IMPORT: + /* in importing mode, prefer our own pool, and pass the other pool to + * our own, so it can serve itself */ + if (pool == NULL) + goto no_downstream_pool; + gst_v4l2_buffer_pool_set_other_pool (GST_V4L2_BUFFER_POOL (obj->pool), + pool); + other_pool = pool; + gst_object_unref (pool); + pool = gst_object_ref (obj->pool); + size = obj->info.size; + break; + + case GST_V4L2_IO_MMAP: + case GST_V4L2_IO_DMABUF: + /* in streaming mode, prefer our own pool */ + /* Check if we can use it ... */ + if (can_share_own_pool) { + if (pool) + gst_object_unref (pool); + pool = gst_object_ref (obj->pool); + size = obj->info.size; + GST_DEBUG_OBJECT (obj->dbg_obj, + "streaming mode: using our own pool %" GST_PTR_FORMAT, pool); + pushing_from_our_pool = TRUE; + } else if (pool) { + GST_DEBUG_OBJECT (obj->dbg_obj, + "streaming mode: copying to downstream pool %" GST_PTR_FORMAT, + pool); + } else { + GST_DEBUG_OBJECT (obj->dbg_obj, + "streaming mode: no usable pool, copying to generic pool"); + size = MAX (size, obj->info.size); + } + break; + case GST_V4L2_IO_AUTO: + default: + GST_WARNING_OBJECT (obj->dbg_obj, "unhandled mode"); + break; + } + + if (size == 0) + goto no_size; + + /* We are going to use the minimum buffer size reported by the driver, and + * limit max to this. */ + own_min = MAX (obj->min_buffers, GST_V4L2_MIN_BUFFERS); + own_max = own_min; + min = own_min; + max = own_max; + if (pushing_from_our_pool) { + GST_DEBUG_OBJECT (obj->dbg_obj, + "force to set min_buffers to %d, max_buffers to %d", min, max); + if (!update) { + gst_v4l2_buffer_pool_copy_at_threshold (GST_V4L2_BUFFER_POOL (pool), + TRUE); + } else { + gst_v4l2_buffer_pool_copy_at_threshold (GST_V4L2_BUFFER_POOL (pool), + FALSE); + } + } + + /* Request a bigger max, if one was suggested but it's too small */ + if (max != 0) + max = MAX (min, max); + + /* First step, configure our own pool */ + config = gst_buffer_pool_get_config (obj->pool); + + if (obj->need_video_meta || has_video_meta) { + GST_DEBUG_OBJECT (obj->dbg_obj, "activate Video Meta"); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + } + + gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); + gst_buffer_pool_config_set_params (config, caps, size, own_min, own_max); + + GST_DEBUG_OBJECT (obj->dbg_obj, "setting own pool config to %" + GST_PTR_FORMAT, config); + + /* Our pool often need to adjust the value */ + if (!gst_buffer_pool_set_config (obj->pool, config)) { + config = gst_buffer_pool_get_config (obj->pool); + + GST_DEBUG_OBJECT (obj->dbg_obj, "own pool config changed to %" + GST_PTR_FORMAT, config); + + /* our pool will adjust the maximum buffer, which we are fine with */ + if (!gst_buffer_pool_set_config (obj->pool, config)) + goto config_failed; + } + + /* Now configure the other pool if different */ + if (obj->pool != pool) + other_pool = pool; + + if (other_pool) { + config = gst_buffer_pool_get_config (other_pool); + gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); + gst_buffer_pool_config_set_params (config, caps, size, min, max); + + GST_DEBUG_OBJECT (obj->dbg_obj, "setting other pool config to %" + GST_PTR_FORMAT, config); + + /* if downstream supports video metadata, add this to the pool config */ + if (has_video_meta) { + GST_DEBUG_OBJECT (obj->dbg_obj, "activate Video Meta"); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + } + + if (!gst_buffer_pool_set_config (other_pool, config)) { + config = gst_buffer_pool_get_config (other_pool); + + if (!gst_buffer_pool_config_validate_params (config, caps, size, min, + max)) { + gst_structure_free (config); + goto config_failed; + } + + if (!gst_buffer_pool_set_config (other_pool, config)) + goto config_failed; + } + } + + if (pool) { + /* For simplicity, simply read back the active configuration, so our base + * class get the right information */ + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, NULL, &size, &min, &max); + gst_structure_free (config); + } + + if (update) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + if (allocator) + gst_object_unref (allocator); + + if (pool) + gst_object_unref (pool); + + return TRUE; + +pool_failed: + { + /* setup_pool already send the error */ + goto cleanup; + } +config_failed: + { + GST_ELEMENT_ERROR (obj->element, RESOURCE, SETTINGS, + (_("Failed to configure internal buffer pool.")), (NULL)); + goto cleanup; + } +no_size: + { + GST_ELEMENT_ERROR (obj->element, RESOURCE, SETTINGS, + (_("Video device did not suggest any buffer size.")), (NULL)); + goto cleanup; + } +cleanup: + { + if (allocator) + gst_object_unref (allocator); + + if (pool) + gst_object_unref (pool); + return FALSE; + } +no_downstream_pool: + { + GST_ELEMENT_ERROR (obj->element, RESOURCE, SETTINGS, + (_("No downstream pool to import from.")), + ("When importing DMABUF or USERPTR, we need a pool to import from")); + return FALSE; + } +} diff --git a/sys/v4l2/gstv4l2scalerobject.h b/sys/v4l2/gstv4l2scalerobject.h new file mode 100644 index 0000000000..5fbd721466 --- /dev/null +++ b/sys/v4l2/gstv4l2scalerobject.h @@ -0,0 +1,67 @@ +/* GStreamer + * + * Copyright (C) 2001-2002 Ronald Bultje + * 2006 Edgard Lima + * 2019 LG Electronics, Inc. + * + * gstv4l2scalerobject.h: base class for LG's V4L2 scaler elements + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_V4L2_SCALER_OBJECT_H__ +#define __GST_V4L2_SCALER_OBJECT_H__ + +#include + +typedef struct _GstV4l2ScalerObject GstV4l2ScalerObject; +typedef struct _GstV4l2ScalerObjectClassHelper GstV4l2ScalerObjectClassHelper; + +struct _GstV4l2ScalerObject { + GstV4l2Object parent; + + gint vdec_index; + guint max_width; + guint max_height; + gboolean scalable; + GstCaps *destination_caps; + gint vdo_fd; +}; + +struct _GstV4l2ScalerObjectClassHelper { + GstV4l2ObjectClassHelper parent; +}; + +GType gst_v4l2_scaler_object_get_type (void); + +/* create/destroy */ +GstV4l2ScalerObject* gst_v4l2_scaler_object_new (GstElement * element, + GstObject * dbg_obj, + enum v4l2_buf_type type, + const char * default_device, + GstV4l2GetInOutFunction get_in_out_func, + GstV4l2SetInOutFunction set_in_out_func, + GstV4l2UpdateFpsFunction update_fps_func); + +void gst_v4l2_scaler_object_destroy (GstV4l2ScalerObject * v4l2scalerobject); + +GstCaps* gst_v4l2_scaler_object_get_caps (GstV4l2ScalerObject * v4l2scalerobject, GstCaps * filter); + +gboolean gst_v4l2_scaler_object_decide_allocation (GstV4l2ScalerObject * v4l2scalerobject, + GstQuery * query); +G_END_DECLS + +#endif /* __GST_V4L2_SCALER_OBJECT_H__ */ diff --git a/sys/v4l2/gstv4l2scalersrc.c b/sys/v4l2/gstv4l2scalersrc.c new file mode 100644 index 0000000000..fc3cc808fd --- /dev/null +++ b/sys/v4l2/gstv4l2scalersrc.c @@ -0,0 +1,1304 @@ +/* GStreamer + * + * Copyright (C) 2001-2002 Ronald Bultje + * 2006 Edgard Lima + * 2019 LG Electronics, Inc. + * + * gstv4l2scalersrc.c: Video4Linux2 scaler source element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-v4l2scalersrc + * + * v4l2scalersrc is for capturing video from scaler v4l2 devices, it provides + * scaled video output. + * + * The v4l2scalersrc has a dependency on the media pipeline and it developed for + * graphic playback and can not be used for other purposes. Therefore, we do + * not recommend expanding or folking this plugin in anyway. + * Final goal is to write the scaler plugin as a gstv4l2transform type instead of + * using this v4l2scalersrc plugin. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ext/videodev2.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gstv4l2scalersrc.h" + +#include "gst/gst-i18n-plugin.h" + +GST_DEBUG_CATEGORY (v4l2scalersrc_debug); +#define GST_CAT_DEFAULT v4l2scalersrc_debug + +#define DEFAULT_PROP_DEVICE V4L2_EXT_DEV_PATH_GPSCALER + +enum +{ + PROP_0, + V4L2_STD_OBJECT_PROPS, + PROP_VDEC_INDEX, + PROP_MAX_WIDTH, + PROP_MAX_HEIGHT, + PROP_SCALABLE, + PROP_CAPS, + PROP_LAST +}; + +/* signals and args */ +enum +{ + SIGNAL_PRE_SET_FORMAT, + LAST_SIGNAL +}; + +static guint gst_v4l2_signals[LAST_SIGNAL] = { 0 }; + +static void gst_v4l2_scaler_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +#define gst_v4l2_scaler_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstV4l2ScalerSrc, gst_v4l2_scaler_src, + GST_TYPE_PUSH_SRC, G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, + gst_v4l2_scaler_src_uri_handler_init)); + +static void gst_v4l2_scaler_src_finalize (GstV4l2ScalerSrc * v4l2scalersrc); + +/* element methods */ +static GstStateChangeReturn gst_v4l2_scaler_src_change_state (GstElement * + element, GstStateChange transition); + +/* basesrc methods */ +static gboolean gst_v4l2_scaler_src_start (GstBaseSrc * src); +static gboolean gst_v4l2_scaler_src_unlock (GstBaseSrc * src); +static gboolean gst_v4l2_scaler_src_unlock_stop (GstBaseSrc * src); +static gboolean gst_v4l2_scaler_src_stop (GstBaseSrc * src); +static GstCaps *gst_v4l2_scaler_src_get_caps (GstBaseSrc * src, + GstCaps * filter); +static gboolean gst_v4l2_scaler_src_query (GstBaseSrc * bsrc, GstQuery * query); +static gboolean gst_v4l2_scaler_src_decide_allocation (GstBaseSrc * src, + GstQuery * query); +static GstFlowReturn gst_v4l2_scaler_src_create (GstPushSrc * src, + GstBuffer ** out); +static GstCaps *gst_v4l2_scaler_src_fixate (GstBaseSrc * basesrc, + GstCaps * caps, GstStructure * pref_s); +static gboolean gst_v4l2_scaler_src_negotiate (GstBaseSrc * basesrc); + +static void gst_v4l2_scaler_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_v4l2_scaler_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +gst_v4l2_scaler_src_class_init (GstV4l2ScalerSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstBaseSrcClass *basesrc_class; + GstPushSrcClass *pushsrc_class; + + gobject_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + basesrc_class = GST_BASE_SRC_CLASS (klass); + pushsrc_class = GST_PUSH_SRC_CLASS (klass); + + gobject_class->finalize = (GObjectFinalizeFunc) gst_v4l2_scaler_src_finalize; + gobject_class->set_property = gst_v4l2_scaler_src_set_property; + gobject_class->get_property = gst_v4l2_scaler_src_get_property; + + element_class->change_state = gst_v4l2_scaler_src_change_state; + + gst_v4l2_object_install_properties_helper (gobject_class, + DEFAULT_PROP_DEVICE); + + g_object_class_install_property (gobject_class, PROP_VDEC_INDEX, + g_param_spec_int ("vdec-index", "VDEC index", + "VDEC instance number", 0, + 7, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_MAX_WIDTH, + g_param_spec_int ("max-width", "Max frame size", + "Max width of the frame", 0, + 1920, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_MAX_HEIGHT, + g_param_spec_int ("max-height", "Max frame size", + "Max height of the frame", 0, + 1080, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); + + g_object_class_install_property (gobject_class, PROP_SCALABLE, + g_param_spec_boolean ("scalable", "Scalable", + "Able to scale", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_CAPS, + g_param_spec_boxed ("caps", "src caps", + "The caps of srcpad. It is used to notify and configure as a proper " + "destination window size to the pipeline", GST_TYPE_CAPS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstV4l2ScalerSrc::prepare-format: + * @v4l2scalersrc: the v4l2scalersrc instance + * @fd: the file descriptor of the current device + * @caps: the caps of the format being set + * + * This signal gets emitted before calling the v4l2 VIDIOC_S_FMT ioctl + * (set format). This allows for any custom configuration of the device to + * happen prior to the format being set. + * This is mostly useful for UVC H264 encoding cameras which need the H264 + * Probe & Commit to happen prior to the normal Probe & Commit. + */ + gst_v4l2_signals[SIGNAL_PRE_SET_FORMAT] = g_signal_new ("prepare-format", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, GST_TYPE_CAPS); + + gst_element_class_set_static_metadata (element_class, + "Video (video4linux2) Source", "Source/Video", + "Reads frames from a Video4Linux2 device", + "Edgard Lima , " + "Stefan Kost "); + + gst_element_class_add_pad_template + (element_class, + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_v4l2_object_get_all_caps ())); + + basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_get_caps); + basesrc_class->start = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_start); + basesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_unlock); + basesrc_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_unlock_stop); + basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_stop); + basesrc_class->query = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_query); + basesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_negotiate); + basesrc_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_decide_allocation); + + pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_v4l2_scaler_src_create); + + klass->v4l2_class_devices = NULL; + + GST_DEBUG_CATEGORY_INIT (v4l2scalersrc_debug, "v4l2scalersrc", 0, + "V4L2 scaler source element"); +} + +static void +gst_v4l2_scaler_src_init (GstV4l2ScalerSrc * v4l2scalersrc) +{ + GstV4l2Object *v4l2object; + /* fixme: give an update_fps_function */ + v4l2scalersrc->v4l2scalerobject = + gst_v4l2_scaler_object_new (GST_ELEMENT (v4l2scalersrc), + GST_OBJECT (GST_BASE_SRC_PAD (v4l2scalersrc)), + V4L2_BUF_TYPE_VIDEO_CAPTURE, DEFAULT_PROP_DEVICE, gst_v4l2_get_input, + gst_v4l2_set_input, NULL); + + v4l2object = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + /* Avoid the slow probes */ + v4l2object->skip_try_fmt_probes = TRUE; + + gst_base_src_set_format (GST_BASE_SRC (v4l2scalersrc), GST_FORMAT_TIME); +} + + +static void +gst_v4l2_scaler_src_finalize (GstV4l2ScalerSrc * v4l2scalersrc) +{ + gst_v4l2_scaler_object_destroy (v4l2scalersrc->v4l2scalerobject); + + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (v4l2scalersrc)); +} + + +static void +gst_v4l2_scaler_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (object); + + if (!gst_v4l2_object_set_property_helper ( + (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject, + prop_id, value, pspec)) { + switch (prop_id) { + case PROP_VDEC_INDEX:{ + v4l2scalersrc->v4l2scalerobject->vdec_index = g_value_get_int (value); + break; + } + case PROP_MAX_WIDTH:{ + v4l2scalersrc->v4l2scalerobject->max_width = g_value_get_int (value); + break; + } + case PROP_MAX_HEIGHT:{ + v4l2scalersrc->v4l2scalerobject->max_height = g_value_get_int (value); + break; + } + case PROP_SCALABLE:{ + v4l2scalersrc->v4l2scalerobject->scalable = g_value_get_boolean (value); + break; + } + case PROP_CAPS:{ + GstCaps *new_caps; + GstCaps *old_caps; + const GstCaps *new_caps_val = gst_value_get_caps (value); + + if (new_caps_val == NULL) { + new_caps = gst_caps_new_any (); + } else { + new_caps = (GstCaps *) new_caps_val; + gst_caps_ref (new_caps); + } + + GST_OBJECT_LOCK (v4l2scalersrc); + old_caps = v4l2scalersrc->v4l2scalerobject->destination_caps; + v4l2scalersrc->v4l2scalerobject->destination_caps = new_caps; + GST_OBJECT_UNLOCK (v4l2scalersrc); + + if (old_caps) + gst_caps_unref (old_caps); + + GST_DEBUG_OBJECT (v4l2scalersrc, "set new caps %" GST_PTR_FORMAT, + new_caps); + + gst_pad_mark_reconfigure (GST_BASE_SRC_PAD (v4l2scalersrc)); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + } +} + +static void +gst_v4l2_scaler_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (object); + + if (!gst_v4l2_object_get_property_helper ( + (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject, + prop_id, value, pspec)) { + switch (prop_id) { + case PROP_VDEC_INDEX:{ + g_value_set_int (value, v4l2scalersrc->v4l2scalerobject->vdec_index); + break; + } + case PROP_MAX_WIDTH:{ + g_value_set_int (value, v4l2scalersrc->v4l2scalerobject->max_width); + break; + } + case PROP_MAX_HEIGHT:{ + g_value_set_int (value, v4l2scalersrc->v4l2scalerobject->max_height); + break; + } + case PROP_SCALABLE:{ + g_value_set_boolean (value, v4l2scalersrc->v4l2scalerobject->scalable); + break; + } + case PROP_CAPS: + GST_OBJECT_LOCK (v4l2scalersrc); + gst_value_set_caps (value, + v4l2scalersrc->v4l2scalerobject->destination_caps); + GST_OBJECT_UNLOCK (v4l2scalersrc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + } +} + +struct PreferedCapsInfo +{ + gint width; + gint height; + gint fps_n; + gint fps_d; +}; + +static gboolean +gst_vl42_src_fixate_fields (GQuark field_id, GValue * value, gpointer user_data) +{ + GstStructure *s = user_data; + + if (field_id == g_quark_from_string ("interlace-mode")) + return TRUE; + + if (field_id == g_quark_from_string ("colorimetry")) + return TRUE; + + gst_structure_fixate_field (s, g_quark_to_string (field_id)); + + return TRUE; +} + +static void +gst_v4l2_src_fixate_struct_with_preference (GstStructure * s, + struct PreferedCapsInfo *pref) +{ + if (gst_structure_has_field (s, "width")) + gst_structure_fixate_field_nearest_int (s, "width", pref->width); + + if (gst_structure_has_field (s, "height")) + gst_structure_fixate_field_nearest_int (s, "height", pref->height); + + if (gst_structure_has_field (s, "framerate")) + gst_structure_fixate_field_nearest_fraction (s, "framerate", pref->fps_n, + pref->fps_d); + + /* Finally, fixate everything else except the interlace-mode and colorimetry + * which still need further negotiation as it wasn't probed */ + gst_structure_map_in_place (s, gst_vl42_src_fixate_fields, s); +} + +static void +gst_v4l2_src_parse_fixed_struct (GstStructure * s, + gint * width, gint * height, gint * fps_n, gint * fps_d) +{ + if (gst_structure_has_field (s, "width") && width) + gst_structure_get_int (s, "width", width); + + if (gst_structure_has_field (s, "height") && height) + gst_structure_get_int (s, "height", height); + + if (gst_structure_has_field (s, "framerate") && fps_n && fps_d) + gst_structure_get_fraction (s, "framerate", fps_n, fps_d); +} + +/* TODO Consider framerate */ +static gint +gst_v4l2_scaler_src_fixed_caps_compare (GstCaps * caps_a, GstCaps * caps_b, + struct PreferedCapsInfo *pref) +{ + GstStructure *a, *b; + gint aw = G_MAXINT, ah = G_MAXINT, ad = G_MAXINT; + gint bw = G_MAXINT, bh = G_MAXINT, bd = G_MAXINT; + gint ret; + + a = gst_caps_get_structure (caps_a, 0); + b = gst_caps_get_structure (caps_b, 0); + + gst_v4l2_src_parse_fixed_struct (a, &aw, &ah, NULL, NULL); + gst_v4l2_src_parse_fixed_struct (b, &bw, &bh, NULL, NULL); + + /* When both are smaller then pref, just append to the end */ + if ((bw < pref->width || bh < pref->height) + && (aw < pref->width || ah < pref->height)) { + ret = 1; + goto done; + } + + /* If a is smaller then pref and not b, then a goes after b */ + if (aw < pref->width || ah < pref->height) { + ret = 1; + goto done; + } + + /* If b is smaller then pref and not a, then a goes before b */ + if (bw < pref->width || bh < pref->height) { + ret = -1; + goto done; + } + + /* Both are larger or equal to the preference, prefer the smallest */ + ad = MAX (1, aw - pref->width) * MAX (1, ah - pref->height); + bd = MAX (1, bw - pref->width) * MAX (1, bh - pref->height); + + /* Adjust slightly in case width/height matched the preference */ + if (aw == pref->width) + ad -= 1; + + if (ah == pref->height) + ad -= 1; + + if (bw == pref->width) + bd -= 1; + + if (bh == pref->height) + bd -= 1; + + /* If the choices are equivalent, maintain the order */ + if (ad == bd) + ret = 1; + else + ret = ad - bd; + +done: + GST_TRACE ("Placing %ix%i (%s) %s %ix%i (%s)", aw, ah, + gst_structure_get_string (a, "format"), ret > 0 ? "after" : "before", bw, + bh, gst_structure_get_string (b, "format")); + return ret; +} + +static gboolean +gst_v4l2_scaler_src_set_format (GstV4l2ScalerSrc * v4l2scalersrc, + GstCaps * caps, GstV4l2Error * error) +{ + GstV4l2Object *obj; + + obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + /* make sure we stop capturing and dealloc buffers */ + if (!gst_v4l2_object_stop (obj)) + return FALSE; + + g_signal_emit (v4l2scalersrc, gst_v4l2_signals[SIGNAL_PRE_SET_FORMAT], 0, + obj->video_fd, caps); + + return gst_v4l2_object_set_format (obj, caps, error); +} + +static GstCaps * +gst_v4l2_scaler_src_fixate (GstBaseSrc * basesrc, GstCaps * caps, + GstStructure * pref_s) +{ + /* Let's prefer a good resolutiion as of today's standard. */ + struct PreferedCapsInfo pref = { + 3840, 2160, 120, 1 + }; + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (basesrc); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + GList *caps_list = NULL; + GstStructure *s; + gint i = G_MAXINT; + GstV4l2Error error = GST_V4L2_ERROR_INIT; + GstCaps *fcaps = NULL; + + GST_DEBUG_OBJECT (basesrc, "fixating caps %" GST_PTR_FORMAT, caps); + + /* We consider the first structure from peercaps to be a preference. This is + * useful for matching a reported native display, or simply to avoid + * transformation to happen downstream. */ + if (pref_s) { + pref_s = gst_structure_copy (pref_s); + gst_v4l2_src_fixate_struct_with_preference (pref_s, &pref); + gst_v4l2_src_parse_fixed_struct (pref_s, &pref.width, &pref.height, + &pref.fps_n, &pref.fps_d); + gst_structure_free (pref_s); + } + + GST_DEBUG_OBJECT (basesrc, "Prefered size %ix%i", pref.width, pref.height); + + /* Sort the structures to get the caps that is nearest to our preferences, + * first, Use single struct caps for sorting so we preserver the features. */ + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstCaps *tmp = gst_caps_copy_nth (caps, i); + + s = gst_caps_get_structure (tmp, 0); + gst_v4l2_src_fixate_struct_with_preference (s, &pref); + caps_list = g_list_insert_sorted_with_data (caps_list, tmp, + (GCompareDataFunc) gst_v4l2_scaler_src_fixed_caps_compare, &pref); + } + + gst_caps_unref (caps); + caps = gst_caps_new_empty (); + + while (caps_list) { + GstCaps *tmp = caps_list->data; + caps_list = g_list_delete_link (caps_list, caps_list); + gst_caps_append (caps, tmp); + } + + GST_DEBUG_OBJECT (basesrc, "sorted and normalized caps %" GST_PTR_FORMAT, + caps); + + /* Each structure in the caps has been fixated, except for the + * interlace-mode and colorimetry. Now normalize the caps so we can + * enumerate the possibilities */ + caps = gst_caps_normalize (caps); + + for (i = 0; i < gst_caps_get_size (caps); ++i) { + gst_v4l2_clear_error (&error); + if (fcaps) + gst_caps_unref (fcaps); + + fcaps = gst_caps_copy_nth (caps, i); + + if (GST_V4L2_IS_ACTIVE (obj)) { + /* try hard to avoid TRY_FMT since some UVC camera just crash when this + * is called at run-time. */ + if (gst_v4l2_object_caps_is_subset (obj, fcaps)) { + gst_caps_unref (fcaps); + fcaps = gst_v4l2_object_get_current_caps (obj); + break; + } + + /* Just check if the format is acceptable, once we know + * no buffers should be outstanding we try S_FMT. + * + * Basesrc will do an allocation query that + * should indirectly reclaim buffers, after that we can + * set the format and then configure our pool */ + if (gst_v4l2_object_try_format (obj, fcaps, &error)) { + /* make sure the caps changed before doing anything */ + if (gst_v4l2_object_caps_equal (obj, fcaps)) + break; + + v4l2scalersrc->renegotiation_adjust = v4l2scalersrc->offset + 1; + v4l2scalersrc->pending_set_fmt = TRUE; + break; + } + } else { + if (gst_v4l2_scaler_src_set_format (v4l2scalersrc, fcaps, &error)) + break; + } + + /* Only EIVAL make sense, report any other errors, this way we don't keep + * probing if the device got disconnected, or if it's firmware stopped + * responding */ + if (error.error->code != GST_RESOURCE_ERROR_SETTINGS) { + i = G_MAXINT; + break; + } + } + + if (i >= gst_caps_get_size (caps)) { + gst_v4l2_error (v4l2scalersrc, &error); + if (fcaps) + gst_caps_unref (fcaps); + gst_caps_unref (caps); + return NULL; + } + + gst_caps_unref (caps); + + GST_DEBUG_OBJECT (basesrc, "fixated caps %" GST_PTR_FORMAT, fcaps); + + return fcaps; +} + +static gboolean +gst_v4l2_scaler_src_negotiate (GstBaseSrc * basesrc) +{ + GstCaps *thiscaps; + GstCaps *caps = NULL; + GstCaps *peercaps = NULL; + gboolean result = FALSE; + + /* first see what is possible on our source pad */ + thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); + 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 without a filter as we'll filter ourselves later on */ + peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); + GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps); + if (peercaps && !gst_caps_is_any (peercaps)) { + /* Prefer the first caps we are compatible with that the peer proposed */ + caps = gst_caps_intersect_full (peercaps, thiscaps, + GST_CAPS_INTERSECT_FIRST); + + GST_DEBUG_OBJECT (basesrc, "intersect: %" GST_PTR_FORMAT, caps); + + gst_caps_unref (thiscaps); + } else { + /* no peer or peer have ANY caps, work with our own caps then */ + caps = thiscaps; + } + + if (caps) { + /* now fixate */ + if (!gst_caps_is_empty (caps)) { + GstStructure *pref = NULL; + + if (peercaps && !gst_caps_is_any (peercaps)) + pref = gst_caps_get_structure (peercaps, 0); + + caps = gst_v4l2_scaler_src_fixate (basesrc, caps, pref); + + /* Fixating may fail as we now set the selected format */ + if (!caps) { + result = FALSE; + goto done; + } + + 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 */ + result = gst_base_src_set_caps (basesrc, caps); + } + } + gst_caps_unref (caps); + } + +done: + if (peercaps) + gst_caps_unref (peercaps); + + return result; + +no_nego_needed: + { + GST_DEBUG_OBJECT (basesrc, "no negotiation needed"); + if (thiscaps) + gst_caps_unref (thiscaps); + return TRUE; + } +} + +static GstCaps * +gst_v4l2_scaler_src_get_caps (GstBaseSrc * src, GstCaps * filter) +{ + GstV4l2ScalerSrc *v4l2scalersrc; + GstV4l2Object *obj; + GstCaps *ret; + + v4l2scalersrc = gst_v4l2_scaler_src (src); + obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + if (!GST_V4L2_IS_OPEN (obj)) { + GstCaps *templ_caps = + gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (v4l2scalersrc)); + + GST_OBJECT_LOCK (v4l2scalersrc); + if (!v4l2scalersrc->v4l2scalerobject->destination_caps) { + GST_OBJECT_UNLOCK (v4l2scalersrc); + return templ_caps; + } + + ret = gst_caps_intersect_full (templ_caps, + v4l2scalersrc->v4l2scalerobject->destination_caps, + GST_CAPS_INTERSECT_FIRST); + GST_OBJECT_UNLOCK (v4l2scalersrc); + + gst_caps_unref (templ_caps); + return ret; + } + + GST_OBJECT_LOCK (v4l2scalersrc); + ret = + gst_v4l2_scaler_object_get_caps (v4l2scalersrc->v4l2scalerobject, filter); + GST_OBJECT_UNLOCK (v4l2scalersrc); + + return ret; +} + +static gboolean +gst_v4l2_scaler_src_decide_allocation (GstBaseSrc * bsrc, GstQuery * query) +{ + GstV4l2ScalerSrc *src = gst_v4l2_scaler_src (bsrc); + GstV4l2Object *obj = (GstV4l2Object *) src->v4l2scalerobject; + gboolean ret = TRUE; + + if (src->pending_set_fmt) { + GstCaps *caps = gst_pad_get_current_caps (GST_BASE_SRC_PAD (bsrc)); + GstV4l2Error error = GST_V4L2_ERROR_INIT; + + caps = gst_caps_make_writable (caps); + if (!(ret = gst_v4l2_scaler_src_set_format (src, caps, &error))) + gst_v4l2_error (src, &error); + + gst_caps_unref (caps); + src->pending_set_fmt = FALSE; + } else if (gst_buffer_pool_is_active (obj->pool)) { + /* Trick basesrc into not deactivating the active pool. Renegotiating here + * would otherwise turn off and on the camera. */ + GstAllocator *allocator; + GstAllocationParams params; + GstBufferPool *pool; + + gst_base_src_get_allocator (bsrc, &allocator, ¶ms); + pool = gst_base_src_get_buffer_pool (bsrc); + + if (gst_query_get_n_allocation_params (query)) + gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); + else + gst_query_add_allocation_param (query, allocator, ¶ms); + + if (gst_query_get_n_allocation_pools (query)) + gst_query_set_nth_allocation_pool (query, 0, pool, obj->info.size, 1, 0); + else + gst_query_add_allocation_pool (query, pool, obj->info.size, 1, 0); + + if (pool) + gst_object_unref (pool); + if (allocator) + gst_object_unref (allocator); + + return GST_BASE_SRC_CLASS (parent_class)->decide_allocation (bsrc, query); + } + + if (ret) { + ret = + gst_v4l2_scaler_object_decide_allocation (src->v4l2scalerobject, query); + if (ret) + ret = GST_BASE_SRC_CLASS (parent_class)->decide_allocation (bsrc, query); + } + + if (ret) { + if (!gst_buffer_pool_set_active (obj->pool, TRUE)) + goto activate_failed; + } + + return ret; + +activate_failed: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + (_("Failed to allocate required memory.")), + ("Buffer pool activation failed")); + return FALSE; + } +} + +static gboolean +gst_v4l2_scaler_src_query (GstBaseSrc * bsrc, GstQuery * query) +{ + GstV4l2ScalerSrc *src; + GstV4l2Object *obj; + gboolean res = FALSE; + + src = gst_v4l2_scaler_src (bsrc); + obj = (GstV4l2Object *) src->v4l2scalerobject; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY:{ + GstClockTime min_latency, max_latency; + guint32 fps_n, fps_d; + guint num_buffers = 0; + + /* device must be open */ + if (!GST_V4L2_IS_OPEN (obj)) { + GST_WARNING_OBJECT (src, + "Can't give latency since device isn't open !"); + goto done; + } + + fps_n = GST_V4L2_FPS_N (obj); + fps_d = GST_V4L2_FPS_D (obj); + + /* we must have a framerate */ + if (fps_n <= 0 || fps_d <= 0) { + GST_WARNING_OBJECT (src, + "Can't give latency since framerate isn't fixated !"); + goto done; + } + + /* min latency is the time to capture one frame */ + min_latency = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); + + /* max latency is total duration of the frame buffer */ + if (obj->pool != NULL) + num_buffers = GST_V4L2_BUFFER_POOL_CAST (obj->pool)->max_latency; + + if (num_buffers == 0) + max_latency = -1; + else + max_latency = num_buffers * min_latency; + + GST_DEBUG_OBJECT (bsrc, + "report latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + + /* we are always live, the min latency is 1 frame and the max latency is + * the complete buffer of frames. */ + gst_query_set_latency (query, TRUE, min_latency, max_latency); + + res = TRUE; + break; + } + default: + res = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); + break; + } + +done: + + return res; +} + +/* start and stop are not symmetric -- start will open the device, but not start + * capture. it's setcaps that will start capture, which is called via basesrc's + * negotiate method. stop will both stop capture and close the device. + */ +static gboolean +gst_v4l2_scaler_src_start (GstBaseSrc * src) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (src); + + v4l2scalersrc->offset = 0; + v4l2scalersrc->renegotiation_adjust = 0; + + /* activate settings for first frame */ + v4l2scalersrc->ctrl_time = 0; + gst_object_sync_values (GST_OBJECT (src), v4l2scalersrc->ctrl_time); + + v4l2scalersrc->has_bad_timestamp = FALSE; + v4l2scalersrc->last_timestamp = 0; + + return TRUE; +} + +static gboolean +gst_v4l2_scaler_src_unlock (GstBaseSrc * src) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (src); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + return gst_v4l2_object_unlock (obj); +} + +static gboolean +gst_v4l2_scaler_src_unlock_stop (GstBaseSrc * src) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (src); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + v4l2scalersrc->last_timestamp = 0; + + return gst_v4l2_object_unlock_stop (obj); +} + +static gboolean +gst_v4l2_scaler_src_stop (GstBaseSrc * src) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (src); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + if (GST_V4L2_IS_ACTIVE (obj)) { + if (!gst_v4l2_object_stop (obj)) + return FALSE; + } + + v4l2scalersrc->pending_set_fmt = FALSE; + + return TRUE; +} + +static gboolean +gst_v4l2_scaler_src_connect_vdo_to_vdec (GstV4l2ScalerSrc * src, + guint control_id) +{ + gint fd = -1; + struct v4l2_ext_controls ext_controls; + struct v4l2_ext_control ext_control; + struct v4l2_ext_vdec_vdo_connection vdo_con; + + if (control_id == V4L2_CID_EXT_VDO_VDEC_CONNECTING) { + fd = open (V4L2_EXT_DEV_PATH_VDOGAV, O_RDWR); + if (fd < 0) { + GST_WARNING_OBJECT (src, "Could not open device '%s'", strerror (errno)); + return FALSE; + } + src->v4l2scalerobject->vdo_fd = fd; + } else if (control_id == V4L2_CID_EXT_VDO_VDEC_DISCONNECTING) { + fd = src->v4l2scalerobject->vdo_fd; + } else { + GST_WARNING_OBJECT (src, "Invalid control_id %d", control_id); + return FALSE; + } + + memset (&ext_controls, 0, sizeof (struct v4l2_ext_controls)); + memset (&ext_control, 0, sizeof (struct v4l2_ext_control)); + memset (&vdo_con, 0, sizeof (struct v4l2_ext_vdec_vdo_connection)); + + vdo_con.vdo_port = 3; //vdo port number + vdo_con.vdec_port = src->v4l2scalerobject->vdec_index; //vdec port number + ext_controls.ctrl_class = V4L2_CTRL_CLASS_USER; + ext_controls.count = 1; + ext_controls.controls = &ext_control; + ext_controls.controls->id = control_id; + ext_controls.controls->ptr = (void *) &vdo_con; + + if (ioctl (fd, VIDIOC_S_EXT_CTRLS, &ext_controls) < 0) { + GST_WARNING_OBJECT (src, "Failed to connect vdo to vdec '%s'", + strerror (errno)); + return FALSE; + } + + if (control_id == V4L2_CID_EXT_VDO_VDEC_DISCONNECTING) + close (fd); + + return TRUE; +} + +static gboolean +gst_v4l2_scaler_src_set_max_frame_size (GstV4l2ScalerSrc * src, gint max_width, + gint max_height) +{ + GstV4l2Object *obj = (GstV4l2Object *) src->v4l2scalerobject; + struct v4l2_control ctrl_arg; + + ctrl_arg.id = V4L2_CID_EXT_GPSCALER_MAX_FRAME_SIZE; + ctrl_arg.value = max_width << 16 | max_height; + + if (ioctl (obj->video_fd, VIDIOC_S_CTRL, &ctrl_arg) < 0) { + GST_WARNING_OBJECT (src, "Failed to set max frame size '%s'", + strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static GstStateChangeReturn +gst_v4l2_scaler_src_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (element); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* open the device */ + if (!gst_v4l2_object_open (obj)) + return GST_STATE_CHANGE_FAILURE; + + if (v4l2scalersrc->v4l2scalerobject->scalable) { + /* connect video decoder with vdo(video decoder output) dedicated for + * graphic rendering */ + if (!gst_v4l2_scaler_src_connect_vdo_to_vdec (v4l2scalersrc, + V4L2_CID_EXT_VDO_VDEC_CONNECTING)) { + gst_v4l2_object_close (obj); + return GST_STATE_CHANGE_FAILURE; + } + + if (v4l2scalersrc->v4l2scalerobject->max_width > 0 + && v4l2scalersrc->v4l2scalerobject->max_height > 0) { + GST_DEBUG_OBJECT (v4l2scalersrc, "set maximum framesize to width %u, " + "height %u", v4l2scalersrc->v4l2scalerobject->max_width, + v4l2scalersrc->v4l2scalerobject->max_height); + if (!gst_v4l2_scaler_src_set_max_frame_size (v4l2scalersrc, + v4l2scalersrc->v4l2scalerobject->max_width, + v4l2scalersrc->v4l2scalerobject->max_height)) { + GST_WARNING_OBJECT (v4l2scalersrc, + "failed to set maximum framesize"); + gst_v4l2_object_close (obj); + return GST_STATE_CHANGE_FAILURE; + } + } + } + + /* call gst_v4l2_set_input */ + if (!obj->set_in_out_func (obj, + v4l2scalersrc->v4l2scalerobject->vdec_index)) { + gst_v4l2_object_close (obj); + return GST_STATE_CHANGE_FAILURE; + } + + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + /* close the device */ + if (!gst_v4l2_object_close (obj)) { + return GST_STATE_CHANGE_FAILURE; + } + + if (v4l2scalersrc->v4l2scalerobject->scalable) { + /* disconnect vdo to vdec */ + gst_v4l2_scaler_src_connect_vdo_to_vdec (v4l2scalersrc, + V4L2_CID_EXT_VDO_VDEC_DISCONNECTING); + } + + break; + default: + break; + } + + return ret; +} + +static GstFlowReturn +gst_v4l2_scaler_src_create (GstPushSrc * src, GstBuffer ** buf) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (src); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + GstV4l2BufferPool *pool = GST_V4L2_BUFFER_POOL_CAST (obj->pool); + GstFlowReturn ret; + GstClock *clock; + GstClockTime abs_time, base_time, timestamp, duration; + GstClockTime delay; + GstMessage *qos_msg; + + do { + ret = GST_BASE_SRC_CLASS (parent_class)->alloc (GST_BASE_SRC (src), 0, + obj->info.size, buf); + + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto alloc_failed; + + ret = gst_v4l2_buffer_pool_process (pool, buf); + + } while (ret == GST_V4L2_FLOW_CORRUPTED_BUFFER); + + if (G_UNLIKELY (ret != GST_FLOW_OK)) + goto error; + + timestamp = GST_BUFFER_TIMESTAMP (*buf); + duration = obj->duration; + + /* timestamps, LOCK to get clock and base time. */ + /* FIXME: element clock and base_time is rarely changing */ + GST_OBJECT_LOCK (v4l2scalersrc); + if ((clock = GST_ELEMENT_CLOCK (v4l2scalersrc))) { + /* we have a clock, get base time and ref clock */ + base_time = GST_ELEMENT (v4l2scalersrc)->base_time; + gst_object_ref (clock); + } else { + /* no clock, can't set timestamps */ + base_time = GST_CLOCK_TIME_NONE; + } + GST_OBJECT_UNLOCK (v4l2scalersrc); + + /* sample pipeline clock */ + if (clock) { + abs_time = gst_clock_get_time (clock); + gst_object_unref (clock); + } else { + abs_time = GST_CLOCK_TIME_NONE; + } + +retry: + if (!v4l2scalersrc->has_bad_timestamp && timestamp != GST_CLOCK_TIME_NONE) { + struct timespec now; + GstClockTime gstnow; + + /* v4l2 specs say to use the system time although many drivers switched to + * the more desirable monotonic time. We first try to use the monotonic time + * and see how that goes */ + clock_gettime (CLOCK_MONOTONIC, &now); + gstnow = GST_TIMESPEC_TO_TIME (now); + + if (timestamp > gstnow || (gstnow - timestamp) > (10 * GST_SECOND)) { + GTimeVal now; + + /* very large diff, fall back to system time */ + g_get_current_time (&now); + gstnow = GST_TIMEVAL_TO_TIME (now); + } + + /* Detect buggy drivers here, and stop using their timestamp. Failing any + * of these condition would imply a very buggy driver: + * - Timestamp in the future + * - Timestamp is going backward compare to last seen timestamp + * - Timestamp is jumping forward for less then a frame duration + * - Delay is bigger then the actual timestamp + * */ + if (timestamp > gstnow) { + GST_WARNING_OBJECT (v4l2scalersrc, + "Timestamp in the future detected, ignoring driver timestamps"); + v4l2scalersrc->has_bad_timestamp = TRUE; + goto retry; + } + + if (v4l2scalersrc->last_timestamp > timestamp) { + GST_WARNING_OBJECT (v4l2scalersrc, + "Timestamp going backward, ignoring driver timestamps"); + v4l2scalersrc->has_bad_timestamp = TRUE; + goto retry; + } + + delay = gstnow - timestamp; + + if (delay > timestamp) { + GST_WARNING_OBJECT (v4l2scalersrc, + "Timestamp does not correlate with any clock, ignoring driver timestamps"); + v4l2scalersrc->has_bad_timestamp = TRUE; + goto retry; + } + + /* Save last timestamp for sanity checks */ + v4l2scalersrc->last_timestamp = timestamp; + + GST_DEBUG_OBJECT (v4l2scalersrc, + "ts: %" GST_TIME_FORMAT " now %" GST_TIME_FORMAT " delay %" + GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), GST_TIME_ARGS (gstnow), + GST_TIME_ARGS (delay)); + } else { + /* we assume 1 frame latency otherwise */ + if (GST_CLOCK_TIME_IS_VALID (duration)) + delay = duration; + else + delay = 0; + } + + /* set buffer metadata */ + + if (G_LIKELY (abs_time != GST_CLOCK_TIME_NONE)) { + /* the time now is the time of the clock minus the base time */ + timestamp = abs_time - base_time; + + /* adjust for delay in the device */ + if (timestamp > delay) + timestamp -= delay; + else + timestamp = 0; + } else { + timestamp = GST_CLOCK_TIME_NONE; + } + + /* activate settings for next frame */ + if (GST_CLOCK_TIME_IS_VALID (duration)) { + v4l2scalersrc->ctrl_time += duration; + } else { + /* this is not very good (as it should be the next timestamp), + * still good enough for linear fades (as long as it is not -1) + */ + v4l2scalersrc->ctrl_time = timestamp; + } + gst_object_sync_values (GST_OBJECT (src), v4l2scalersrc->ctrl_time); + + GST_INFO_OBJECT (src, "sync to %" GST_TIME_FORMAT " out ts %" GST_TIME_FORMAT, + GST_TIME_ARGS (v4l2scalersrc->ctrl_time), GST_TIME_ARGS (timestamp)); + + /* use generated offset values only if there are not already valid ones + * set by the v4l2 device */ + if (!GST_BUFFER_OFFSET_IS_VALID (*buf) + || !GST_BUFFER_OFFSET_END_IS_VALID (*buf)) { + GST_BUFFER_OFFSET (*buf) = v4l2scalersrc->offset++; + GST_BUFFER_OFFSET_END (*buf) = v4l2scalersrc->offset; + } else { + /* adjust raw v4l2 device sequence, will restart at null in case of renegotiation + * (streamoff/streamon) */ + GST_BUFFER_OFFSET (*buf) += v4l2scalersrc->renegotiation_adjust; + GST_BUFFER_OFFSET_END (*buf) += v4l2scalersrc->renegotiation_adjust; + /* check for frame loss with given (from v4l2 device) buffer offset */ + if ((v4l2scalersrc->offset != 0) + && (GST_BUFFER_OFFSET (*buf) != (v4l2scalersrc->offset + 1))) { + guint64 lost_frame_count = + GST_BUFFER_OFFSET (*buf) - v4l2scalersrc->offset - 1; + GST_WARNING_OBJECT (v4l2scalersrc, + "lost frames detected: count = %" G_GUINT64_FORMAT " - ts: %" + GST_TIME_FORMAT, lost_frame_count, GST_TIME_ARGS (timestamp)); + + qos_msg = gst_message_new_qos (GST_OBJECT_CAST (v4l2scalersrc), TRUE, + GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, timestamp, + GST_CLOCK_TIME_IS_VALID (duration) ? lost_frame_count * + duration : GST_CLOCK_TIME_NONE); + gst_element_post_message (GST_ELEMENT_CAST (v4l2scalersrc), qos_msg); + + } + v4l2scalersrc->offset = GST_BUFFER_OFFSET (*buf); + } + + GST_BUFFER_TIMESTAMP (*buf) = timestamp; + GST_BUFFER_DURATION (*buf) = duration; + + return ret; + + /* ERROR */ +alloc_failed: + { + if (ret != GST_FLOW_FLUSHING) + GST_ELEMENT_ERROR (src, RESOURCE, NO_SPACE_LEFT, + ("Failed to allocate a buffer"), (NULL)); + return ret; + } +error: + { + gst_buffer_replace (buf, NULL); + if (ret == GST_V4L2_FLOW_LAST_BUFFER) { + GST_ELEMENT_ERROR (src, RESOURCE, FAILED, + ("Driver returned a buffer with no payload, this most likely " + "indicate a bug in the driver."), (NULL)); + ret = GST_FLOW_ERROR; + } else { + GST_DEBUG_OBJECT (src, "error processing buffer %d (%s)", ret, + gst_flow_get_name (ret)); + } + return ret; + } +} + + +/* GstURIHandler interface */ +static GstURIType +gst_v4l2_scaler_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_v4l2_scaler_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "v4l2", NULL }; + + return protocols; +} + +static gchar * +gst_v4l2_scaler_src_uri_get_uri (GstURIHandler * handler) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (handler); + GstV4l2Object *obj = (GstV4l2Object *) v4l2scalersrc->v4l2scalerobject; + + if (obj->videodev != NULL) { + return g_strdup_printf ("v4l2://%s", obj->videodev); + } + + return g_strdup ("v4l2://"); +} + +static gboolean +gst_v4l2_scaler_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstV4l2ScalerSrc *v4l2scalersrc = gst_v4l2_scaler_src (handler); + const gchar *device = DEFAULT_PROP_DEVICE; + + if (strcmp (uri, "v4l2://") != 0) { + device = uri + 7; + } + g_object_set (v4l2scalersrc, "device", device, NULL); + + return TRUE; +} + + +static void +gst_v4l2_scaler_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_v4l2_scaler_src_uri_get_type; + iface->get_protocols = gst_v4l2_scaler_src_uri_get_protocols; + iface->get_uri = gst_v4l2_scaler_src_uri_get_uri; + iface->set_uri = gst_v4l2_scaler_src_uri_set_uri; +} diff --git a/sys/v4l2/gstv4l2scalersrc.h b/sys/v4l2/gstv4l2scalersrc.h new file mode 100644 index 0000000000..0cb2f9188a --- /dev/null +++ b/sys/v4l2/gstv4l2scalersrc.h @@ -0,0 +1,84 @@ +/* GStreamer + * + * Copyright (C) 2001-2002 Ronald Bultje + * 2006 Edgard Lima + * 2019 LG Electronics, Inc. + * + * gstv4l2scalersrc.h: V4L2 scaler source element + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_V4L2_SCALER_SRC_H__ +#define __GST_V4L2_SCALER_SRC_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_V4L2_SCALER_SRC \ + (gst_v4l2_scaler_src_get_type()) +#define gst_v4l2_scaler_src(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_V4L2_SCALER_SRC,GstV4l2ScalerSrc)) +#define gst_v4l2_scaler_src_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_V4L2_SCALER_SRC,GstV4l2ScalerSrcClass)) +#define GST_IS_V4L2_SCALER_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4L2_SCALER_SRC)) +#define GST_IS_V4L2_SCALER_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4L2_SCALER_SRC)) + +typedef struct _GstV4l2ScalerSrc GstV4l2ScalerSrc; +typedef struct _GstV4l2ScalerSrcClass GstV4l2ScalerSrcClass; + +/** + * GstV4l2ScalerSrc: + * + * Opaque object. + */ +struct _GstV4l2ScalerSrc +{ + GstPushSrc pushsrc; + + /*< private >*/ + GstV4l2ScalerObject * v4l2scalerobject; + + guint64 offset; + + /* offset adjust after renegotiation */ + guint64 renegotiation_adjust; + + GstClockTime ctrl_time; + + gboolean pending_set_fmt; + + /* Timestamp sanity check */ + GstClockTime last_timestamp; + gboolean has_bad_timestamp; +}; + +struct _GstV4l2ScalerSrcClass +{ + GstPushSrcClass parent_class; + + GList *v4l2_class_devices; +}; + +GType gst_v4l2_scaler_src_get_type (void); + +G_END_DECLS + +#endif /* __GST_V4L2_SCALER_SRC_H__ */ diff --git a/sys/v4l2/gstv4l2src.c b/sys/v4l2/gstv4l2src.c index 88c813ff80..df50397943 100644 --- a/sys/v4l2/gstv4l2src.c +++ b/sys/v4l2/gstv4l2src.c @@ -308,13 +308,17 @@ gst_v4l2_src_parse_fixed_struct (GstStructure * s, /* TODO Consider framerate */ static gint -gst_v4l2src_fixed_caps_compare (GstStructure * a, GstStructure * b, +gst_v4l2src_fixed_caps_compare (GstCaps * caps_a, GstCaps * caps_b, struct PreferedCapsInfo *pref) { + GstStructure *a, *b; gint aw = G_MAXINT, ah = G_MAXINT, ad = G_MAXINT; gint bw = G_MAXINT, bh = G_MAXINT, bd = G_MAXINT; gint ret; + a = gst_caps_get_structure (caps_a, 0); + b = gst_caps_get_structure (caps_b, 0); + gst_v4l2_src_parse_fixed_struct (a, &aw, &ah, NULL, NULL); gst_v4l2_src_parse_fixed_struct (b, &bw, &bh, NULL, NULL); @@ -402,8 +406,6 @@ gst_v4l2src_fixate (GstBaseSrc * basesrc, GstCaps * caps, GstStructure * pref_s) GST_DEBUG_OBJECT (basesrc, "fixating caps %" GST_PTR_FORMAT, caps); - caps = gst_caps_make_writable (caps); - /* We consider the first structure from peercaps to be a preference. This is * useful for matching a reported native display, or simply to avoid * transformation to happen downstream. */ @@ -418,17 +420,24 @@ gst_v4l2src_fixate (GstBaseSrc * basesrc, GstCaps * caps, GstStructure * pref_s) GST_DEBUG_OBJECT (basesrc, "Prefered size %ix%i", pref.width, pref.height); /* Sort the structures to get the caps that is nearest to our preferences, - * first */ - while ((s = gst_caps_steal_structure (caps, 0))) { + * first. Use single struct caps for sorting so we preserve the features. */ + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstCaps *tmp = gst_caps_copy_nth (caps, i); + + s = gst_caps_get_structure (tmp, 0); gst_v4l2_src_fixate_struct_with_preference (s, &pref); - caps_list = g_list_insert_sorted_with_data (caps_list, s, + + caps_list = g_list_insert_sorted_with_data (caps_list, tmp, (GCompareDataFunc) gst_v4l2src_fixed_caps_compare, &pref); } + gst_caps_unref (caps); + caps = gst_caps_new_empty (); + while (caps_list) { - s = caps_list->data; + GstCaps *tmp = caps_list->data; caps_list = g_list_delete_link (caps_list, caps_list); - gst_caps_append_structure (caps, s); + gst_caps_append (caps, tmp); } GST_DEBUG_OBJECT (basesrc, "sorted and normalized caps %" GST_PTR_FORMAT, diff --git a/sys/v4l2/meson.build b/sys/v4l2/meson.build index 83ecc02ab4..fcc330213e 100644 --- a/sys/v4l2/meson.build +++ b/sys/v4l2/meson.build @@ -25,6 +25,26 @@ v4l2_sources = [ 'tunernorm.c' ] +have_v4l2_ext = true +message('Checking headers needed for v4l2scalersrc plugin...') +foreach hdr : ['linux/v4l2-ext/videodev2-ext.h'] + if have_v4l2_ext + if not cc.has_header(hdr) + have_v4l2_ext = false + endif + endif +endforeach + +if have_v4l2_ext + cdata.set('HAVE_LINUX_EXT', true) + v4l2_sources += [ + 'gstv4l2scalerobject.c', + 'gstv4l2scalersrc.c', + ] +else + cdata.set('HAVE_LINUX_EXT', false) +endif + cdata.set('GST_V4L2_ENABLE_PROBE', get_option('v4l2-probe')) if cc.has_header('linux/videodev2.h') or cc.has_header('sys/videodev2.h') or cc.has_header('sys/videoio.h') diff --git a/tests/check/elements/qtdemux.c b/tests/check/elements/qtdemux.c index 56b66933e3..0f9c61afb3 100644 --- a/tests/check/elements/qtdemux.c +++ b/tests/check/elements/qtdemux.c @@ -21,6 +21,7 @@ */ #include "qtdemux.h" +#include typedef struct { @@ -139,6 +140,474 @@ GST_START_TEST (test_qtdemux_input_gap) GST_END_TEST; +typedef struct +{ + GstPad *sinkpad; + GstPad *pending_pad; + GstEventType *expected_events; + guint step; + guint total_step; + guint expected_num_srcpad; + guint num_srcpad; +} ReconfigTestData; + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static gboolean +_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + gst_event_unref (event); + return TRUE; +} + +static GstFlowReturn +_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) +{ + gst_buffer_unref (buffer); + return GST_FLOW_OK; +} + +static GstPadProbeReturn +qtdemux_block_for_reconfig (GstPad * pad, GstPadProbeInfo * info, + ReconfigTestData * data) +{ + fail_unless (data->pending_pad); + fail_unless (data->pending_pad == pad); + + GST_DEBUG_OBJECT (pad, "Unblock pad"); + + if (gst_pad_is_linked (data->sinkpad)) { + GstPad *peer = gst_pad_get_peer (data->sinkpad); + fail_unless (peer); + gst_pad_unlink (peer, data->sinkpad); + } + + fail_unless (gst_pad_link (data->pending_pad, data->sinkpad) == + GST_PAD_LINK_OK); + data->pending_pad = NULL; + + return GST_PAD_PROBE_REMOVE; +} + +static GstPadProbeReturn +qtdemux_probe_for_reconfig (GstPad * pad, GstPadProbeInfo * info, + ReconfigTestData * data) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + GstEventType expected = data->expected_events[data->step]; + + GST_DEBUG ("Got event %p %s", event, GST_EVENT_TYPE_NAME (event)); + + fail_unless (GST_EVENT_TYPE (event) == expected); + data->step++; + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS && data->step < data->total_step) { + /* If current EOS is for draining, there should be pending srcpad */ + fail_unless (data->pending_pad != NULL); + } + + return GST_PAD_PROBE_OK; +} + +static void +qtdemux_pad_added_cb_for_reconfig (GstElement * element, GstPad * pad, + ReconfigTestData * data) +{ + data->num_srcpad++; + + fail_unless (data->num_srcpad <= data->expected_num_srcpad); + fail_unless (data->pending_pad == NULL); + + GST_DEBUG_OBJECT (pad, "New pad added"); + + data->pending_pad = pad; + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) qtdemux_block_for_reconfig, data, NULL); + + if (!data->sinkpad) { + GstPad *sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink"); + + gst_pad_set_event_function (sinkpad, _sink_event); + gst_pad_set_chain_function (sinkpad, _sink_chain); + + gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) qtdemux_probe_for_reconfig, data, NULL); + gst_pad_set_active (sinkpad, TRUE); + data->sinkpad = sinkpad; + } +} + +GST_START_TEST (test_qtdemux_duplicated_moov) +{ + GstElement *qtdemux; + GstPad *sinkpad; + ReconfigTestData data = { 0, }; + GstBuffer *inbuf; + GstSegment segment; + GstEvent *event; + GstEventType expected[] = { + GST_EVENT_STREAM_START, + GST_EVENT_CAPS, + GST_EVENT_SEGMENT, + GST_EVENT_TAG, + GST_EVENT_TAG, + GST_EVENT_EOS + }; + + data.expected_events = expected; + data.expected_num_srcpad = 1; + data.total_step = G_N_ELEMENTS (expected);; + + /* The goal of this test is to check that qtdemux can properly handle + * duplicated moov without redundant events and pad exposing + * + * Testing step + * - Push events stream-start and segment to qtdemux + * - Push init and media data + * - Push the same init and media data again + * + * Expected behaviour + * - Expose srcpad only once + * - No additional downstream events when the second init and media data is + * pushed to qtdemux + */ + + qtdemux = gst_element_factory_make ("qtdemux", NULL); + gst_element_set_state (qtdemux, GST_STATE_PLAYING); + sinkpad = gst_element_get_static_pad (qtdemux, "sink"); + + /* We'll want to know when the source pad is added */ + g_signal_connect (qtdemux, "pad-added", (GCallback) + qtdemux_pad_added_cb_for_reconfig, &data); + + /* Send the initial STREAM_START and segment (TIME) event */ + event = gst_event_new_stream_start ("TEST"); + GST_DEBUG ("Pushing stream-start event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + gst_segment_init (&segment, GST_FORMAT_TIME); + event = gst_event_new_segment (&segment); + GST_DEBUG ("Pushing segment event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + + /* Feed the init buffer, should create the source pad */ + inbuf = gst_buffer_new_and_alloc (init_mp4_len); + gst_buffer_fill (inbuf, 0, init_mp4, init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_DEBUG ("Pushing moov buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + fail_unless_equals_int (data.num_srcpad, 1); + + /* Now send the moof and mdat of the first fragment */ + inbuf = gst_buffer_new_and_alloc (seg_1_m4f_len); + gst_buffer_fill (inbuf, 0, seg_1_m4f, seg_1_m4f_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_DEBUG ("Pushing moof and mdat buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + + /* Resend the init, moof and mdat, no additional event and pad are expected */ + inbuf = gst_buffer_new_and_alloc (init_mp4_len); + gst_buffer_fill (inbuf, 0, init_mp4, init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DISCONT); + GST_DEBUG ("Pushing moov buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + fail_unless_equals_int (data.num_srcpad, 1); + + inbuf = gst_buffer_new_and_alloc (seg_1_m4f_len); + gst_buffer_fill (inbuf, 0, seg_1_m4f, seg_1_m4f_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = init_mp4_len; + GST_DEBUG ("Pushing moof and mdat buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_unless (gst_pad_send_event (sinkpad, gst_event_new_eos ()) == TRUE); + fail_unless_equals_int (data.step, data.total_step); + fail_unless (data.pending_pad == NULL); + + gst_object_unref (sinkpad); + gst_pad_set_active (data.sinkpad, FALSE); + gst_object_unref (data.sinkpad); + gst_element_set_state (qtdemux, GST_STATE_NULL); + gst_object_unref (qtdemux); +} + +GST_END_TEST; + +GST_START_TEST (test_qtdemux_stream_change) +{ + GstElement *qtdemux; + GstPad *sinkpad; + ReconfigTestData data = { 0, }; + GstBuffer *inbuf; + GstSegment segment; + GstEvent *event; + const gchar *upstream_id; + const gchar *stream_id = NULL; + gchar *expected_stream_id = NULL; + guint track_id; + GstEventType expected[] = { + /* 1st group */ + GST_EVENT_STREAM_START, + GST_EVENT_CAPS, + GST_EVENT_SEGMENT, + GST_EVENT_TAG, + GST_EVENT_TAG, + /* 2nd group (track-id change without upstream stream-start) */ + GST_EVENT_EOS, + GST_EVENT_STREAM_START, + GST_EVENT_CAPS, + GST_EVENT_SEGMENT, + GST_EVENT_TAG, + GST_EVENT_TAG, + /* 3rd group (no track-id change with upstream stream-start) */ + GST_EVENT_EOS, + GST_EVENT_STREAM_START, + GST_EVENT_CAPS, + GST_EVENT_SEGMENT, + GST_EVENT_TAG, + GST_EVENT_TAG, + /* last group (track-id change with upstream stream-start) */ + GST_EVENT_EOS, + GST_EVENT_STREAM_START, + GST_EVENT_CAPS, + GST_EVENT_SEGMENT, + GST_EVENT_TAG, + GST_EVENT_TAG, + GST_EVENT_EOS + }; + + data.expected_events = expected; + data.expected_num_srcpad = 4; + data.total_step = G_N_ELEMENTS (expected); + + /* The goal of this test is to check that qtdemux can properly handle + * stream change regardless of track-id change. + * This test is simulating DASH bitrate switching (for both playbin and plabyin3) + * and period-change for playbin3 + * + * NOTE: During bitrate switching in DASH, track-id might be changed + * NOTE: stream change with new stream-start to qtdemux is playbin3 specific behaviour, + * because playbin configures new demux per period and existing demux never ever get + * new stream-start again. + * + * Testing step + * [GROUP 1] + * - Push events stream-start and segment to qtdemux + * - Push init and media data to qtdemux + * [GROUP 2] + * - Push different (track-id change) init and media data to qtdemux + * without new downstream sticky events to qtdemux + * [GROUP 3] + * - Push events stream-start and segment to qtdemux again + * - Push the init and media data which are the same as GROUP 2 + * [GROUP 4] + * - Push events stream-start and segment to qtdemux again + * - Push different (track-id change) init and media data to qtdemux + * + * Expected behaviour + * - Demux exposes srcpad four times, per test GROUP, regardless of track-id change + * - Whenever exposing new pads, downstream sticky events should be detected + * at demux srcpad + */ + + qtdemux = gst_element_factory_make ("qtdemux", NULL); + gst_element_set_state (qtdemux, GST_STATE_PLAYING); + sinkpad = gst_element_get_static_pad (qtdemux, "sink"); + + /* We'll want to know when the source pad is added */ + g_signal_connect (qtdemux, "pad-added", (GCallback) + qtdemux_pad_added_cb_for_reconfig, &data); + + /*************** + * TEST GROUP 1 + * (track-id: 2) + **************/ + /* Send the initial STREAM_START and segment (TIME) event */ + upstream_id = "TEST-GROUP-1"; + track_id = 2; + expected_stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id); + event = gst_event_new_stream_start (upstream_id); + GST_DEBUG ("Pushing stream-start event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + gst_segment_init (&segment, GST_FORMAT_TIME); + event = gst_event_new_segment (&segment); + GST_DEBUG ("Pushing segment event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + + /* Feed the init buffer, should create the source pad */ + inbuf = gst_buffer_new_and_alloc (init_mp4_len); + gst_buffer_fill (inbuf, 0, init_mp4, init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_DEBUG ("Pushing moov buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + fail_unless_equals_int (data.num_srcpad, 1); + + /* Check stream-id */ + event = gst_pad_get_sticky_event (data.sinkpad, GST_EVENT_STREAM_START, 0); + fail_unless (event != NULL); + gst_event_parse_stream_start (event, &stream_id); + fail_unless_equals_string (stream_id, expected_stream_id); + g_free (expected_stream_id); + gst_event_unref (event); + + /* Now send the moof and mdat of the first fragment */ + inbuf = gst_buffer_new_and_alloc (seg_1_m4f_len); + gst_buffer_fill (inbuf, 0, seg_1_m4f, seg_1_m4f_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = init_mp4_len; + GST_DEBUG ("Pushing moof and mdat buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + + + /*************** + * TEST GROUP 2 + * (track-id: 1) + * - track-id change without new upstream stream-start event + **************/ + /* Resend the init */ + inbuf = gst_buffer_new_and_alloc (BBB_32k_init_mp4_len); + gst_buffer_fill (inbuf, 0, BBB_32k_init_mp4, BBB_32k_init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DISCONT); + GST_DEBUG ("Pushing moov buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + /* new srcpad should be exposed */ + fail_unless_equals_int (data.num_srcpad, 2); + + /* Check stream-id */ + upstream_id = "TEST-GROUP-1"; /* upstream-id does not changed from GROUP 1 */ + track_id = 1; /* track-id is changed from 2 to 1 */ + expected_stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id); + event = gst_pad_get_sticky_event (data.sinkpad, GST_EVENT_STREAM_START, 0); + fail_unless (event != NULL); + gst_event_parse_stream_start (event, &stream_id); + fail_unless_equals_string (stream_id, expected_stream_id); + g_free (expected_stream_id); + gst_event_unref (event); + + /* push the moof and mdat again */ + inbuf = gst_buffer_new_and_alloc (BBB_32k_1_mp4_len); + gst_buffer_fill (inbuf, 0, BBB_32k_1_mp4, BBB_32k_1_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = BBB_32k_init_mp4_len; + GST_DEBUG ("Pushing moof and mdat buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + + /*************** + * TEST GROUP 3 + * (track-id: 1) + * - Push new stream-start and segment to qtdemux + * - Reuse init and media data of GROUP 2 (no track-id change) + **************/ + /* Send STREAM_START and segment (TIME) event */ + upstream_id = "TEST-GROUP-3"; + track_id = 1; + expected_stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id); + event = gst_event_new_stream_start (upstream_id); + GST_DEBUG ("Pushing stream-start event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + gst_segment_init (&segment, GST_FORMAT_TIME); + event = gst_event_new_segment (&segment); + GST_DEBUG ("Pushing segment event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + + /* Resend the init */ + inbuf = gst_buffer_new_and_alloc (BBB_32k_init_mp4_len); + gst_buffer_fill (inbuf, 0, BBB_32k_init_mp4, BBB_32k_init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DISCONT); + GST_DEBUG ("Pushing moov buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + /* new srcpad should be exposed */ + fail_unless_equals_int (data.num_srcpad, 3); + + /* Check stream-id */ + event = gst_pad_get_sticky_event (data.sinkpad, GST_EVENT_STREAM_START, 0); + fail_unless (event != NULL); + gst_event_parse_stream_start (event, &stream_id); + fail_unless_equals_string (stream_id, expected_stream_id); + g_free (expected_stream_id); + gst_event_unref (event); + + /* push the moof and mdat again */ + inbuf = gst_buffer_new_and_alloc (BBB_32k_1_mp4_len); + gst_buffer_fill (inbuf, 0, BBB_32k_1_mp4, BBB_32k_1_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = BBB_32k_init_mp4_len; + GST_DEBUG ("Pushing moof and mdat buffer"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + + /*************** + * TEST GROUP 4 + * (track-id: 2) + * - Push new stream-start and segment to qtdemux + * - track-id change from 1 to 2 + **************/ + /* Send STREAM_START and segment (TIME) event */ + upstream_id = "TEST-GROUP-4"; + track_id = 2; + expected_stream_id = g_strdup_printf ("%s/%03u", upstream_id, track_id); + event = gst_event_new_stream_start (upstream_id); + GST_DEBUG ("Pushing stream-start event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + gst_segment_init (&segment, GST_FORMAT_TIME); + event = gst_event_new_segment (&segment); + GST_DEBUG ("Pushing segment event"); + fail_unless (gst_pad_send_event (sinkpad, event) == TRUE); + + /* Resend the init */ + inbuf = gst_buffer_new_and_alloc (init_mp4_len); + gst_buffer_fill (inbuf, 0, init_mp4, init_mp4_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = 0; + GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DISCONT); + GST_DEBUG ("Pushing moov buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_if (data.sinkpad == NULL); + /* new srcpad should be exposed */ + fail_unless_equals_int (data.num_srcpad, 4); + + /* Check stream-id */ + event = gst_pad_get_sticky_event (data.sinkpad, GST_EVENT_STREAM_START, 0); + fail_unless (event != NULL); + gst_event_parse_stream_start (event, &stream_id); + fail_unless_equals_string (stream_id, expected_stream_id); + g_free (expected_stream_id); + gst_event_unref (event); + + /* push the moof and mdat again */ + inbuf = gst_buffer_new_and_alloc (seg_1_m4f_len); + gst_buffer_fill (inbuf, 0, seg_1_m4f, seg_1_m4f_len); + GST_BUFFER_PTS (inbuf) = 0; + GST_BUFFER_OFFSET (inbuf) = init_mp4_len; + GST_DEBUG ("Pushing moof and mdat buffer again"); + fail_unless (gst_pad_chain (sinkpad, inbuf) == GST_FLOW_OK); + fail_unless (gst_pad_send_event (sinkpad, gst_event_new_eos ()) == TRUE); + fail_unless_equals_int (data.step, data.total_step); + fail_unless (data.pending_pad == NULL); + + gst_object_unref (sinkpad); + gst_pad_set_active (data.sinkpad, FALSE); + gst_object_unref (data.sinkpad); + gst_element_set_state (qtdemux, GST_STATE_NULL); + gst_object_unref (qtdemux); +} + +GST_END_TEST; + static Suite * qtdemux_suite (void) { @@ -147,6 +616,8 @@ qtdemux_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_qtdemux_input_gap); + tcase_add_test (tc_chain, test_qtdemux_duplicated_moov); + tcase_add_test (tc_chain, test_qtdemux_stream_change); return s; } diff --git a/tests/check/elements/qtdemux.h b/tests/check/elements/qtdemux.h index b8425f0337..17ca0fa0c2 100644 --- a/tests/check/elements/qtdemux.h +++ b/tests/check/elements/qtdemux.h @@ -4249,3 +4249,789 @@ static const guint seg_1_sample_sizes[] = { /* in timescale */ GstClockTime seg_1_sample_duration = 1024; guint32 seg_1_timescale = 44100; + + +/* Fragments taken from http://dash.akamaized.net/dash264/TestCases/5c/nomor/4_1a.mpd + * + * Audio stream (aac) + * Header + first Fragments + */ + +/* http://dash.akamaized.net/dash264/TestCases/5c/nomor/BBB_32k_init.mp4 */ +static const guint8 BBB_32k_init_mp4[] = { + 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x35, + 0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x35, 0x64, 0x61, 0x73, 0x68, + 0x00, 0x00, 0x00, 0x08, 0x66, 0x72, 0x65, 0x65, 0x00, 0x00, 0x00, 0x3c, + 0x66, 0x72, 0x65, 0x65, 0x49, 0x73, 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, + 0x20, 0x46, 0x69, 0x6c, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x50, 0x41, 0x43, + 0x20, 0x30, 0x2e, 0x35, 0x2e, 0x31, 0x2d, 0x44, 0x45, 0x56, 0x2d, 0x72, + 0x65, 0x76, 0x34, 0x37, 0x33, 0x36, 0x4d, 0x00, 0x00, 0x00, 0x02, 0xac, + 0x6d, 0x6f, 0x6f, 0x76, 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x38, 0x6d, 0x76, 0x65, 0x78, + 0x00, 0x00, 0x00, 0x10, 0x6d, 0x65, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x10, 0x50, 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x9e, 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c, + 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0xce, 0x60, 0xc8, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3a, 0x6d, 0x64, 0x69, 0x61, + 0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x6f, 0x75, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x6d, 0x69, 0x6e, + 0x66, 0x00, 0x00, 0x00, 0x10, 0x73, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, + 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, + 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa9, 0x73, 0x74, 0x62, + 0x6c, 0x00, 0x00, 0x00, 0x5d, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4d, 0x6d, 0x70, 0x34, + 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0xbb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x65, 0x73, 0x64, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1b, 0x00, 0x01, 0x00, 0x04, 0x13, + 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x7d, + 0x02, 0x05, 0x04, 0xeb, 0x09, 0x88, 0x00, 0x06, 0x01, 0x02, 0x00, 0x00, + 0x00, 0x10, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, + 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x75, 0x64, + 0x74, 0x61, 0x00, 0x00, 0x00, 0x5a, 0x6d, 0x65, 0x74, 0x61, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x69, 0x72, 0x61, 0x70, + 0x70, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2d, 0x69, 0x6c, 0x73, 0x74, 0x00, 0x00, 0x00, 0x25, 0xa9, + 0x74, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x1d, 0x64, 0x61, 0x74, 0x61, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x35, + 0x35, 0x2e, 0x31, 0x36, 0x2e, 0x31, 0x30, 0x31 +}; + +static const guint BBB_32k_init_mp4_len = 776; + +/* http://dash.akamaized.net/dash264/TestCases/5c/nomor/BBB_32k_1.mp4 */ +static const guint8 BBB_32k_1_mp4[] = { + 0x00, 0x00, 0x00, 0x18, 0x73, 0x74, 0x79, 0x70, 0x6d, 0x73, 0x64, 0x68, + 0x00, 0x00, 0x00, 0x00, 0x6d, 0x73, 0x64, 0x68, 0x6d, 0x73, 0x69, 0x78, + 0x00, 0x00, 0x00, 0x2c, 0x73, 0x69, 0x64, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xbb, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0xa3, + 0x00, 0x01, 0x70, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x6d, 0x6f, 0x6f, 0x66, 0x00, 0x00, 0x00, 0x10, 0x6d, 0x66, 0x68, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf8, + 0x74, 0x72, 0x61, 0x66, 0x00, 0x00, 0x00, 0x14, 0x74, 0x66, 0x68, 0x64, + 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x74, 0x66, 0x64, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x74, 0x72, 0x75, 0x6e, + 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x01, 0x18, + 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0xc7, + 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xda, 0x00, 0x00, 0x00, 0xcb, + 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, 0xc4, + 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, 0x00, 0xbf, + 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa9, + 0x00, 0x00, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x9a, + 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x9a, + 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xa0, + 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0xa7, + 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x9f, + 0x00, 0x00, 0x00, 0xaf, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x00, 0x00, 0xa8, + 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xa3, + 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0xac, + 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, 0x00, 0xb1, + 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xa4, + 0x00, 0x00, 0x00, 0xaf, 0x00, 0x00, 0x1f, 0x93, 0x6d, 0x64, 0x61, 0x74, + 0x01, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xf5, 0x20, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x60, 0x06, 0xf8, 0x91, 0x0a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x5a, 0x5e, 0x01, 0x30, 0x34, 0x03, 0xe9, 0x6a, 0xcb, 0x34, 0xd3, 0x87, + 0xc3, 0xff, 0xfd, 0x12, 0xb9, 0x3c, 0x5b, 0xc6, 0x34, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x3e, 0x1d, 0x72, 0x48, 0xff, 0x51, 0xa7, 0xee, 0x4b, + 0x3f, 0xf9, 0x25, 0x66, 0x02, 0xeb, 0x2c, 0x42, 0x4b, 0x42, 0xff, 0x00, + 0x1f, 0xfc, 0x00, 0xfa, 0xa7, 0xaf, 0xd3, 0xb6, 0x97, 0xa8, 0x37, 0xb1, + 0x08, 0x52, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd2, 0xf0, 0x01, 0x00, 0x34, 0x2d, 0xa8, 0x95, 0x39, + 0xad, 0xa1, 0x39, 0xc6, 0xa9, 0x75, 0x09, 0x96, 0x54, 0x2a, 0xc2, 0x02, + 0xcd, 0x59, 0x45, 0xa6, 0x97, 0xf8, 0x00, 0x7a, 0x25, 0x29, 0x37, 0xc4, + 0x06, 0x0a, 0xef, 0x57, 0x48, 0x91, 0x76, 0x8b, 0xfc, 0x9e, 0x7e, 0xff, + 0x24, 0x47, 0x63, 0xe2, 0x7f, 0x27, 0xfe, 0xa9, 0x1e, 0xac, 0x67, 0x87, + 0xfe, 0x0f, 0x65, 0xc9, 0x1a, 0x31, 0x85, 0x8a, 0x72, 0xc4, 0xab, 0xb7, + 0x9c, 0xfb, 0x07, 0xf3, 0xfb, 0x7a, 0xef, 0x0e, 0xd2, 0xdf, 0x9b, 0x03, + 0xba, 0xbc, 0x29, 0xa4, 0xbf, 0x4f, 0x5e, 0x4c, 0xfd, 0xfd, 0x93, 0x5d, + 0x21, 0x0b, 0xa1, 0x72, 0x55, 0xe9, 0x1b, 0xde, 0xe7, 0x66, 0xcd, 0xa2, + 0x54, 0xdd, 0xc4, 0xd7, 0xaf, 0xff, 0xff, 0x47, 0x48, 0xd3, 0xf2, 0x09, + 0x67, 0xa0, 0xd3, 0xc1, 0xd3, 0xfb, 0x67, 0x21, 0x3b, 0x1d, 0x64, 0x2b, + 0xaa, 0x0a, 0xa4, 0x97, 0x40, 0xea, 0xe9, 0xcf, 0xd3, 0x94, 0x8d, 0x3c, + 0x11, 0xe9, 0xfc, 0x4a, 0x58, 0x2e, 0x6f, 0xca, 0x0d, 0x66, 0x9e, 0x73, + 0x71, 0xac, 0x59, 0x2b, 0xa2, 0x8e, 0x20, 0x6f, 0x18, 0xd0, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x4a, 0x7c, 0x3a, 0xe4, 0x91, 0xfe, 0xa3, 0x4f, 0xdc, + 0x96, 0x7f, 0xf2, 0x4a, 0xcc, 0x05, 0xd6, 0x58, 0x84, 0x96, 0x85, 0xfe, + 0x00, 0x3f, 0xf8, 0x01, 0xf5, 0x4f, 0x5f, 0xa7, 0x6d, 0x2f, 0x50, 0xe0, + 0x01, 0x0c, 0x34, 0x14, 0x90, 0x7b, 0x3c, 0x14, 0x85, 0x04, 0x61, 0xc0, + 0x48, 0x2e, 0x24, 0x11, 0x88, 0xc2, 0x9b, 0xdd, 0xdc, 0x34, 0xb2, 0x82, + 0xd9, 0x29, 0x69, 0x88, 0x29, 0x6b, 0x09, 0xa4, 0xca, 0xec, 0xe8, 0xa6, + 0xfa, 0x43, 0x9c, 0xfa, 0x33, 0x28, 0x0d, 0x08, 0x77, 0xad, 0xb2, 0x35, + 0x01, 0x1b, 0x1d, 0xea, 0x2a, 0xbd, 0x19, 0xc5, 0x14, 0x62, 0x8c, 0x0d, + 0x69, 0xd6, 0xd8, 0xd5, 0x16, 0x0e, 0xc0, 0xf1, 0x38, 0xcc, 0xcd, 0x70, + 0x40, 0x01, 0xbb, 0x05, 0xb2, 0x0e, 0xa7, 0x82, 0x61, 0x2d, 0xb6, 0x59, + 0xd2, 0x60, 0x35, 0x76, 0xea, 0x73, 0xc0, 0x00, 0xd5, 0x6f, 0xe8, 0x66, + 0x2d, 0x88, 0x47, 0xf5, 0x93, 0x80, 0x10, 0x97, 0x0b, 0xe4, 0x59, 0xed, + 0xeb, 0xa3, 0x00, 0xe3, 0x5a, 0xc8, 0x79, 0xe0, 0x8c, 0xe0, 0x01, 0x96, + 0xb3, 0x05, 0x12, 0x52, 0xb5, 0xf0, 0xbc, 0xc4, 0xe8, 0xc6, 0xcc, 0xd1, + 0x10, 0x2e, 0x0d, 0x7c, 0x91, 0xe5, 0xf2, 0xc1, 0x7b, 0xeb, 0x6c, 0x66, + 0x66, 0x64, 0xbc, 0x8e, 0x77, 0x18, 0x67, 0x1a, 0x4d, 0x37, 0x0d, 0x67, + 0x77, 0x8b, 0xa1, 0xc9, 0x90, 0xde, 0x27, 0x5c, 0xe1, 0xa0, 0xcf, 0x4f, + 0x34, 0x02, 0xd0, 0x98, 0x49, 0x2a, 0xcb, 0x2c, 0xfd, 0x7e, 0x6f, 0xff, + 0xaa, 0xbe, 0x6a, 0xd8, 0xe8, 0x4b, 0x85, 0x3c, 0x7f, 0xf0, 0x47, 0x36, + 0x18, 0xe6, 0xbc, 0x3a, 0x9c, 0x37, 0x8f, 0xb2, 0x19, 0x9d, 0x5e, 0xb6, + 0x0a, 0xcb, 0x0e, 0xfe, 0x00, 0xde, 0x15, 0xa0, 0x50, 0x03, 0x7d, 0x7d, + 0x86, 0xc9, 0x5c, 0xac, 0x7a, 0xcc, 0x8b, 0x88, 0x54, 0xb5, 0x17, 0x73, + 0x64, 0xd7, 0xc0, 0xbb, 0x69, 0x98, 0xa1, 0xc0, 0x01, 0x08, 0x34, 0x10, + 0x90, 0x7b, 0x0d, 0x19, 0xcf, 0x02, 0x40, 0xb8, 0x98, 0x2e, 0x12, 0x13, + 0x85, 0x4a, 0x52, 0xab, 0x96, 0x04, 0x4a, 0xc2, 0x6b, 0x1a, 0x55, 0x2d, + 0x32, 0xd5, 0x6b, 0x05, 0x8e, 0x3d, 0xe8, 0x61, 0xe2, 0xd0, 0x32, 0x8a, + 0xc2, 0x34, 0x50, 0x26, 0x9f, 0x2a, 0xda, 0xe6, 0xc8, 0x4b, 0xe2, 0x83, + 0x4f, 0x34, 0x48, 0xed, 0xeb, 0x84, 0x01, 0x50, 0x20, 0x11, 0xa4, 0x54, + 0xb1, 0x79, 0x62, 0xe9, 0x53, 0x6f, 0x7a, 0xff, 0xc5, 0x52, 0xe0, 0x11, + 0x8e, 0x35, 0xbb, 0x6f, 0xc6, 0xd1, 0xb5, 0x67, 0xaf, 0xc6, 0xfd, 0xdc, + 0x49, 0x41, 0x81, 0x95, 0x84, 0x88, 0x55, 0x25, 0x13, 0x4d, 0xdc, 0x76, + 0x39, 0x66, 0x40, 0xa2, 0xd5, 0x57, 0x42, 0x9d, 0x14, 0xc6, 0x28, 0xad, + 0x4f, 0xa9, 0xc8, 0xa2, 0x71, 0xbc, 0x33, 0xa9, 0x12, 0x66, 0x67, 0x01, + 0x7a, 0xcf, 0x5f, 0xf1, 0xe1, 0xab, 0x16, 0x80, 0xb0, 0xd4, 0xe1, 0x1f, + 0xe1, 0xd2, 0xcb, 0xf1, 0x37, 0x9b, 0x53, 0x71, 0x96, 0x6e, 0x57, 0x23, + 0xdb, 0x9c, 0x66, 0x54, 0x00, 0x04, 0x6d, 0xad, 0xda, 0x0c, 0xe7, 0x96, + 0x9a, 0x49, 0x01, 0x7d, 0xbf, 0x96, 0x63, 0x2c, 0xeb, 0x79, 0x33, 0x74, + 0x21, 0x58, 0x8d, 0xeb, 0x35, 0x10, 0xdc, 0x58, 0x2e, 0xd9, 0x9d, 0x5b, + 0x37, 0x7a, 0xf9, 0x80, 0x06, 0xf0, 0xdd, 0x02, 0x82, 0x2e, 0xed, 0x97, + 0xd7, 0x25, 0x30, 0xf8, 0x0c, 0x31, 0x7b, 0xcd, 0x9d, 0xcc, 0x7b, 0xdb, + 0xf7, 0xa6, 0xef, 0x9f, 0xbd, 0xeb, 0x79, 0x28, 0x00, 0x0e, 0x01, 0x08, + 0x34, 0x08, 0x8c, 0x7b, 0x23, 0x0a, 0xc3, 0x2c, 0x60, 0xb8, 0x50, 0x4a, + 0x24, 0x18, 0x05, 0xd9, 0x8d, 0xca, 0x15, 0x20, 0x2d, 0x49, 0x59, 0xa4, + 0xb2, 0x63, 0x54, 0x15, 0xa4, 0xa0, 0x2a, 0xb4, 0x99, 0x19, 0x26, 0x59, + 0x32, 0xd9, 0x6b, 0x2b, 0xad, 0x91, 0x50, 0x2c, 0xb0, 0x28, 0xc4, 0x4f, + 0x01, 0x47, 0x41, 0x85, 0x5f, 0x04, 0x0a, 0xed, 0x1c, 0xe6, 0xea, 0x9d, + 0x11, 0xf9, 0xe0, 0x0b, 0x56, 0x3c, 0xce, 0x66, 0x51, 0x0a, 0xcf, 0xc3, + 0x7b, 0x5e, 0x56, 0xa6, 0x0b, 0xbb, 0x10, 0x65, 0x2a, 0xe3, 0x75, 0x1f, + 0x23, 0xd3, 0x73, 0x66, 0x72, 0x30, 0xbc, 0xb1, 0xef, 0x76, 0xeb, 0x31, + 0xc8, 0x45, 0x69, 0xf8, 0x7e, 0xb1, 0x73, 0x71, 0x9e, 0x86, 0x0a, 0x48, + 0x29, 0x86, 0x20, 0x64, 0x9a, 0x5e, 0xbe, 0xfb, 0x87, 0x78, 0x62, 0x95, + 0x10, 0x70, 0x51, 0x1a, 0xfa, 0xfb, 0xf3, 0x3b, 0x66, 0x8a, 0x18, 0xd9, + 0x6f, 0xbf, 0xe9, 0x81, 0xc1, 0x2f, 0xab, 0x5e, 0x8e, 0x12, 0x25, 0x41, + 0xb0, 0x95, 0xf7, 0x55, 0x71, 0x96, 0x48, 0xcc, 0x7a, 0xcb, 0xab, 0xa1, + 0xdf, 0xf4, 0x55, 0x35, 0x20, 0x51, 0x9c, 0x77, 0x8c, 0xcf, 0x57, 0x1e, + 0xf5, 0x4d, 0xdf, 0x7b, 0x26, 0x27, 0x53, 0x78, 0x56, 0x81, 0x51, 0x16, + 0x50, 0x61, 0x6b, 0x43, 0xe0, 0x10, 0xb8, 0xce, 0xe2, 0x55, 0xb0, 0xb3, + 0x46, 0x5f, 0x75, 0xdf, 0x7d, 0xd8, 0xc8, 0xc0, 0x07, 0x01, 0x08, 0x34, + 0x19, 0x76, 0x18, 0x2d, 0x85, 0xda, 0x88, 0x30, 0x98, 0x3b, 0x36, 0x04, + 0x16, 0xdc, 0xd6, 0x22, 0x4a, 0xb2, 0x13, 0x00, 0x8a, 0x19, 0xa3, 0x41, + 0xf8, 0x7f, 0x3b, 0xd6, 0x6c, 0x3d, 0x1c, 0xb2, 0x59, 0x5b, 0x39, 0xa4, + 0xb0, 0x2e, 0x06, 0x29, 0xe1, 0x32, 0xa8, 0xf2, 0xd6, 0xb8, 0x6d, 0xb5, + 0x8a, 0x50, 0xdc, 0xa1, 0x20, 0xb4, 0xf5, 0x8f, 0x9d, 0x43, 0x8a, 0xca, + 0x26, 0x81, 0xa7, 0x9d, 0x53, 0x44, 0xd8, 0xcb, 0xaa, 0xd1, 0x9a, 0x63, + 0x2a, 0xab, 0x88, 0xc2, 0x73, 0x94, 0xee, 0xa8, 0xd6, 0xf2, 0x7d, 0x6f, + 0x7d, 0xa1, 0x73, 0x73, 0x15, 0xc8, 0xae, 0x4e, 0xa6, 0x9e, 0xd9, 0xc2, + 0x2a, 0x6a, 0x6b, 0x47, 0xe0, 0xb3, 0x34, 0x63, 0x2d, 0x5c, 0x2e, 0x57, + 0x2d, 0x48, 0xd1, 0xab, 0xe0, 0x6a, 0xeb, 0x7f, 0x6f, 0xa6, 0xc7, 0x3b, + 0x24, 0x21, 0xad, 0xf6, 0x49, 0x3c, 0xf3, 0xbc, 0x93, 0xab, 0x59, 0x33, + 0x3b, 0x27, 0x8a, 0xd5, 0x3b, 0x39, 0x07, 0xa8, 0x3e, 0xab, 0xe6, 0x7e, + 0xbb, 0x78, 0x5b, 0x99, 0xef, 0x89, 0x81, 0x75, 0x55, 0xb4, 0x41, 0x12, + 0x6e, 0x44, 0x10, 0x58, 0xbe, 0x7a, 0x61, 0x24, 0x15, 0x2f, 0x72, 0xb2, + 0xd0, 0x53, 0x00, 0xb4, 0xdc, 0x13, 0x4d, 0x4e, 0x37, 0x85, 0x7d, 0x7e, + 0xf7, 0x59, 0x00, 0x37, 0x84, 0x68, 0x16, 0x11, 0x5d, 0xa8, 0x0e, 0x0d, + 0xcc, 0xec, 0x37, 0x5b, 0x38, 0x79, 0x8e, 0xd9, 0x3f, 0xcb, 0x4d, 0x96, + 0xc8, 0xb0, 0x00, 0x70, 0x01, 0x08, 0x34, 0x08, 0x8c, 0x7b, 0x0c, 0x98, + 0x86, 0xe6, 0x62, 0xa0, 0x94, 0xcc, 0x29, 0xda, 0x87, 0x36, 0x33, 0x4c, + 0xd4, 0x2a, 0xa2, 0xd1, 0x05, 0x83, 0x16, 0x31, 0x15, 0x75, 0xfe, 0xa9, + 0x3a, 0x43, 0x07, 0x6a, 0x69, 0xa2, 0x83, 0xd6, 0xf3, 0x33, 0xd2, 0x62, + 0x2a, 0x9c, 0x3d, 0xdb, 0xff, 0xb1, 0xf7, 0x0c, 0x26, 0xd9, 0xd5, 0x63, + 0xc0, 0xe8, 0x78, 0x3d, 0xc1, 0xb6, 0x62, 0xf4, 0x1e, 0xdb, 0xbb, 0x7b, + 0x8a, 0x8a, 0xd6, 0xe4, 0x7f, 0xaf, 0x1d, 0x32, 0xe6, 0x12, 0x82, 0x24, + 0x86, 0x58, 0x74, 0x74, 0x7b, 0x0c, 0x23, 0x3a, 0x8c, 0x09, 0x16, 0xe9, + 0xf3, 0x24, 0x00, 0x00, 0x45, 0x03, 0xfe, 0x89, 0xa0, 0xd9, 0xa2, 0x1c, + 0x0c, 0x00, 0x01, 0x0a, 0x12, 0x91, 0x9b, 0xf3, 0xdf, 0x4c, 0x8c, 0x65, + 0x01, 0x08, 0x6e, 0x3a, 0xa7, 0x31, 0x2a, 0xf1, 0x98, 0x77, 0x31, 0x18, + 0x00, 0x1a, 0x79, 0x4d, 0x6e, 0xbb, 0x7e, 0x9c, 0xd2, 0x5b, 0x99, 0x44, + 0x42, 0xc9, 0xb1, 0xbe, 0x5a, 0xa6, 0xe4, 0xb6, 0x21, 0xa6, 0xec, 0x26, + 0x62, 0xa9, 0x88, 0x99, 0x4e, 0xdb, 0x9b, 0xe9, 0xe8, 0xc6, 0x4d, 0x5c, + 0xd6, 0xe3, 0x74, 0xa1, 0x10, 0x4a, 0x23, 0x59, 0xdd, 0xe1, 0x7f, 0x1d, + 0x4e, 0x40, 0x03, 0x78, 0x3e, 0x81, 0x61, 0x17, 0xd7, 0x08, 0x4c, 0xc8, + 0x31, 0xc8, 0x7b, 0x74, 0xdc, 0x7d, 0xfd, 0xc9, 0x89, 0x9e, 0x0e, 0xed, + 0x20, 0x87, 0x01, 0x08, 0x34, 0x11, 0x72, 0x44, 0x14, 0xb5, 0x14, 0x19, + 0xe8, 0x66, 0x40, 0xb1, 0x89, 0x65, 0x17, 0x97, 0x60, 0xb0, 0xc4, 0x85, + 0x11, 0xdf, 0x32, 0xfd, 0x3b, 0xf1, 0x44, 0x3d, 0xf5, 0x1e, 0x7f, 0xfd, + 0x3f, 0x7d, 0xc6, 0x77, 0xa7, 0x1e, 0x07, 0x4f, 0x3a, 0x7f, 0xb2, 0xe6, + 0x49, 0x3e, 0x9c, 0xd2, 0x1b, 0x3f, 0x25, 0xe8, 0x6e, 0xe2, 0x0a, 0x59, + 0x59, 0x65, 0xd6, 0xe7, 0x33, 0x1b, 0x17, 0x86, 0x55, 0x8c, 0x93, 0x12, + 0x2a, 0x71, 0x9a, 0x4e, 0xcc, 0x75, 0xfb, 0x0e, 0xe3, 0xa3, 0x8e, 0x41, + 0x5b, 0x3b, 0xde, 0xb6, 0xe2, 0x29, 0x37, 0x50, 0xe7, 0x45, 0x19, 0xc6, + 0x37, 0xd3, 0xee, 0xd9, 0x55, 0xdd, 0xcb, 0x53, 0xe5, 0x7f, 0x20, 0x0f, + 0x6e, 0xad, 0xec, 0xce, 0x11, 0x64, 0x79, 0x08, 0x6e, 0x14, 0x88, 0x8e, + 0xbc, 0x05, 0x2c, 0x1e, 0xe9, 0xf1, 0xef, 0x68, 0xaf, 0x2c, 0xb2, 0x55, + 0x8c, 0x5b, 0x00, 0x57, 0xda, 0xab, 0xcd, 0x0d, 0xa7, 0xdd, 0xde, 0xf5, + 0x6c, 0x7b, 0x9c, 0x61, 0x9b, 0x03, 0xbd, 0x20, 0xa9, 0xb6, 0x41, 0x25, + 0x03, 0x4c, 0x20, 0x89, 0xde, 0x0a, 0x13, 0xac, 0xbf, 0xff, 0x0d, 0x3a, + 0x18, 0xee, 0xee, 0xef, 0xbc, 0x27, 0x40, 0xb2, 0x8c, 0xd6, 0x9b, 0x5b, + 0x90, 0x0f, 0x5c, 0x91, 0x34, 0xcb, 0xe8, 0x38, 0x30, 0x69, 0xe0, 0xa4, + 0xf1, 0x00, 0x68, 0xd9, 0x03, 0x07, 0x01, 0x0c, 0x34, 0x10, 0xb4, 0x77, + 0x4b, 0x09, 0xcc, 0x86, 0x50, 0x90, 0xcc, 0x20, 0x56, 0x65, 0x78, 0x24, + 0x05, 0x59, 0x02, 0x2c, 0x00, 0x47, 0x61, 0x7e, 0xec, 0x30, 0x9a, 0x84, + 0xd5, 0xca, 0xd1, 0x76, 0x9c, 0x27, 0x27, 0x9e, 0x2f, 0x81, 0xa7, 0x3c, + 0x2f, 0xd2, 0x4b, 0xdd, 0xf7, 0x1c, 0xf4, 0xac, 0x18, 0xd6, 0x19, 0x76, + 0x1d, 0xa6, 0xda, 0x5d, 0xe2, 0x48, 0xaa, 0xcb, 0x88, 0x89, 0x90, 0xbf, + 0xe7, 0xdd, 0x70, 0xd4, 0xca, 0x89, 0xb9, 0xaa, 0x5b, 0x16, 0x5a, 0xaf, + 0x81, 0xca, 0xbb, 0x83, 0x39, 0x4f, 0x17, 0x66, 0x22, 0xd5, 0x94, 0x61, + 0xd1, 0xa7, 0x9c, 0x30, 0x4f, 0xf6, 0x4e, 0xa2, 0xc7, 0x38, 0x52, 0x4f, + 0x9f, 0x96, 0xdc, 0x5d, 0x56, 0x98, 0x1b, 0xd7, 0xe8, 0x8a, 0x5c, 0xb7, + 0x1a, 0xf7, 0x22, 0x59, 0x4d, 0x34, 0x6f, 0x1b, 0x8e, 0x82, 0x89, 0xca, + 0x35, 0x9c, 0xa4, 0x0d, 0xaf, 0x48, 0xf5, 0xc4, 0xec, 0xd6, 0x7c, 0x97, + 0x25, 0x40, 0xc5, 0xcb, 0x95, 0x40, 0xa5, 0x80, 0x7f, 0x73, 0xb0, 0x31, + 0x55, 0x9d, 0x62, 0xb9, 0x59, 0x7e, 0x09, 0xfd, 0xd1, 0x8f, 0x40, 0x0d, + 0xe1, 0x1a, 0x05, 0x94, 0x76, 0xe3, 0x35, 0x72, 0xba, 0x1d, 0x86, 0x31, + 0x95, 0x36, 0x22, 0xdf, 0xe5, 0x9e, 0xd9, 0x9d, 0xa1, 0x2d, 0x80, 0x1c, + 0x01, 0x0c, 0x34, 0x11, 0x76, 0x19, 0x59, 0x0d, 0xc2, 0x8b, 0x21, 0x18, + 0x6c, 0xc5, 0x6e, 0xa2, 0x08, 0xcb, 0x41, 0x02, 0x0b, 0x05, 0xaa, 0x2f, + 0x73, 0xe5, 0x3a, 0xe7, 0x9f, 0xab, 0xf0, 0xbc, 0xd3, 0x47, 0x1b, 0xdd, + 0x8f, 0xe1, 0x93, 0x15, 0xa7, 0xe0, 0xf4, 0x33, 0xe5, 0xea, 0x48, 0x9b, + 0x9b, 0xcf, 0x2d, 0x6f, 0xc6, 0xe5, 0x99, 0x9d, 0x27, 0x06, 0x55, 0x51, + 0xab, 0x3a, 0x48, 0x6a, 0x71, 0x3d, 0x53, 0x46, 0xcc, 0x82, 0xf2, 0xde, + 0x37, 0xdb, 0xfd, 0xf3, 0xf5, 0x7f, 0x99, 0x84, 0xd5, 0xad, 0x1f, 0xc1, + 0xb9, 0x19, 0x79, 0xef, 0xeb, 0xa2, 0x9d, 0x0b, 0x28, 0xc6, 0x77, 0x06, + 0x15, 0x85, 0xc9, 0x39, 0xbf, 0xed, 0xbe, 0xdd, 0x25, 0xa2, 0x31, 0xa3, + 0x69, 0x06, 0x10, 0x42, 0xb7, 0x53, 0x83, 0x11, 0x02, 0xa6, 0x42, 0xe8, + 0x73, 0x66, 0x70, 0x40, 0xef, 0x9b, 0xba, 0xea, 0x1b, 0xec, 0x6b, 0xd9, + 0x60, 0xf2, 0x04, 0x6c, 0x53, 0xbe, 0x39, 0x72, 0x11, 0x40, 0x7a, 0x9c, + 0x8c, 0x2b, 0x03, 0x3e, 0x58, 0xbf, 0x87, 0x93, 0x2b, 0x4d, 0x80, 0xbd, + 0x6e, 0xbb, 0x9b, 0x1c, 0x40, 0x00, 0x0d, 0xe1, 0x5a, 0x07, 0x55, 0x45, + 0x1d, 0x75, 0xd0, 0xf8, 0x05, 0x99, 0x17, 0x3c, 0xe3, 0x96, 0x89, 0xd8, + 0x58, 0xb5, 0xfc, 0x9e, 0x73, 0x57, 0x82, 0x60, 0x1c, 0x01, 0x08, 0x34, + 0x08, 0x8c, 0x7b, 0x22, 0xb5, 0x14, 0x62, 0x6d, 0x55, 0xbc, 0xb5, 0x1d, + 0xe5, 0xc6, 0x74, 0x60, 0x16, 0x16, 0x80, 0x2f, 0xc4, 0x63, 0x25, 0xbb, + 0x6f, 0x71, 0xbe, 0xa1, 0xb2, 0x9e, 0xf5, 0xbd, 0x5b, 0xff, 0x3e, 0xbc, + 0x84, 0xa2, 0x86, 0x35, 0x1b, 0xd8, 0xa1, 0x02, 0x8a, 0xad, 0x99, 0xba, + 0x4d, 0xb3, 0x3e, 0xec, 0xd4, 0x6e, 0x33, 0xaa, 0xc1, 0x29, 0xae, 0xaf, + 0xa7, 0xb1, 0x11, 0x1f, 0xa7, 0x7f, 0xfb, 0xba, 0x42, 0x12, 0x81, 0x18, + 0x81, 0x58, 0xed, 0xfe, 0xfd, 0x31, 0x3b, 0x69, 0x75, 0xf1, 0xea, 0xf6, + 0xc6, 0xce, 0xe6, 0x2b, 0xff, 0x64, 0x75, 0x7e, 0x87, 0x61, 0x18, 0x8d, + 0x5d, 0xcc, 0x3b, 0xef, 0x42, 0x76, 0xca, 0x6a, 0x51, 0xba, 0x37, 0x39, + 0x2c, 0x78, 0x3f, 0x4d, 0x92, 0x85, 0x99, 0xfc, 0x5e, 0x88, 0xbe, 0xc6, + 0x01, 0x84, 0x00, 0x5d, 0xde, 0xa4, 0xa6, 0x68, 0x0d, 0x24, 0x0a, 0x3b, + 0xa1, 0x20, 0x1a, 0xf5, 0xef, 0x64, 0xfa, 0x36, 0x45, 0x50, 0x55, 0x60, + 0x39, 0x66, 0xde, 0x4c, 0x9c, 0xbe, 0xc5, 0xb7, 0x58, 0x0a, 0xdf, 0x45, + 0x5c, 0xff, 0x67, 0x97, 0xd2, 0xb9, 0x60, 0x6f, 0x08, 0xd0, 0x2a, 0xa3, + 0xb7, 0x03, 0xaa, 0xf9, 0xa1, 0x3b, 0x0e, 0xf2, 0x2c, 0xad, 0xcb, 0x35, + 0xb6, 0xce, 0xfa, 0xcd, 0x9a, 0x4d, 0x80, 0xe0, 0x01, 0x0e, 0x34, 0x08, + 0x94, 0xb7, 0x3a, 0xb0, 0xcc, 0xa8, 0x2b, 0xb6, 0x30, 0x06, 0xb9, 0x52, + 0xea, 0xc6, 0x68, 0x2d, 0x80, 0xe1, 0x68, 0x7a, 0x62, 0xd7, 0x76, 0x24, + 0xa6, 0xce, 0x76, 0xe4, 0xc0, 0x42, 0x94, 0x40, 0xbc, 0xa4, 0x34, 0x40, + 0x89, 0xc2, 0x24, 0xbc, 0x0c, 0x29, 0xb9, 0xd7, 0xe0, 0xb7, 0x5e, 0x51, + 0x75, 0x82, 0x3f, 0x47, 0x00, 0x5a, 0x27, 0x12, 0xe3, 0x64, 0x22, 0xb3, + 0xdd, 0xd5, 0xea, 0x9b, 0x88, 0x10, 0x22, 0x42, 0x26, 0x67, 0x5f, 0xd3, + 0x79, 0xc6, 0xe0, 0xab, 0xe8, 0xee, 0xe3, 0x8a, 0xc2, 0x60, 0xab, 0xcf, + 0xc7, 0x52, 0x31, 0xce, 0xed, 0x60, 0x8a, 0x6a, 0xaf, 0x8f, 0x0c, 0xcc, + 0x5d, 0x28, 0x26, 0xa9, 0x15, 0x02, 0xa2, 0x2c, 0x5d, 0x1b, 0x5e, 0x50, + 0x5e, 0x7e, 0xc2, 0x89, 0xd0, 0xa9, 0x4e, 0x8b, 0x2a, 0x45, 0x93, 0x6e, + 0xd1, 0x9d, 0x4e, 0x99, 0xa8, 0xe9, 0xd4, 0xaf, 0x1d, 0xac, 0x34, 0x20, + 0x12, 0x2f, 0xad, 0x1c, 0xf1, 0x03, 0x78, 0x46, 0x81, 0x55, 0x1d, 0xf5, + 0xc5, 0x8d, 0x3c, 0x03, 0x39, 0x0f, 0x2c, 0x61, 0x35, 0x8e, 0xd3, 0xf9, + 0xa2, 0xc9, 0x85, 0xac, 0x00, 0x07, 0x01, 0x0e, 0x34, 0x19, 0x76, 0x15, + 0x7a, 0x0d, 0x48, 0x66, 0x30, 0x65, 0x0d, 0xd6, 0xea, 0x33, 0x5c, 0x36, + 0x04, 0xa8, 0xb2, 0x73, 0xa0, 0x01, 0xb9, 0xb0, 0x01, 0x51, 0x4d, 0x59, + 0xb6, 0x58, 0x0f, 0x92, 0x95, 0x50, 0x00, 0xea, 0x44, 0x43, 0x35, 0x87, + 0x4f, 0x46, 0x7a, 0xbf, 0x0f, 0xd9, 0xe1, 0xce, 0x3a, 0xd8, 0x86, 0xb5, + 0x9e, 0x69, 0xaa, 0x99, 0xd4, 0x52, 0xd3, 0x8f, 0x76, 0xb5, 0x6d, 0xcf, + 0x67, 0x8f, 0xde, 0xaa, 0x22, 0x6d, 0x18, 0x51, 0x34, 0x84, 0xf5, 0x7d, + 0xbe, 0x18, 0xc4, 0xd5, 0xc9, 0x5e, 0x65, 0xd9, 0x29, 0x23, 0x05, 0x75, + 0xbf, 0xd5, 0x5c, 0xd8, 0xd2, 0xd9, 0x4a, 0x98, 0x5a, 0x25, 0xf5, 0xf5, + 0x61, 0x2b, 0x48, 0x5c, 0xad, 0x84, 0x6e, 0x34, 0xdc, 0xa1, 0x84, 0xab, + 0x59, 0x51, 0xa9, 0x9b, 0x9f, 0xfe, 0x1d, 0x26, 0x39, 0xa3, 0xa0, 0x64, + 0xea, 0x79, 0xfc, 0xa2, 0xd1, 0x04, 0xe9, 0x24, 0xf5, 0x92, 0x8c, 0xfe, + 0x05, 0x6c, 0x05, 0xd1, 0x6f, 0xf4, 0x76, 0xf1, 0x81, 0xbc, 0x23, 0x40, + 0xaa, 0x8f, 0xdb, 0x7b, 0x09, 0xe6, 0x11, 0x1c, 0x87, 0x6c, 0xbb, 0xfb, + 0x98, 0x83, 0x5b, 0xb1, 0xdc, 0xa4, 0x69, 0x34, 0x03, 0x07, 0x01, 0x06, + 0x34, 0x19, 0x4e, 0x26, 0x4a, 0x91, 0x04, 0xc1, 0x41, 0x19, 0xc4, 0x66, + 0x1b, 0xde, 0x54, 0x78, 0x2a, 0xc1, 0x96, 0x80, 0x2c, 0x00, 0x00, 0xa8, + 0x8c, 0xdc, 0x9f, 0xb7, 0xef, 0x7a, 0x4c, 0x76, 0xdb, 0x48, 0xca, 0x4f, + 0xfe, 0x7c, 0x0a, 0x27, 0x40, 0x0b, 0xe1, 0xbe, 0x5f, 0xd0, 0x0b, 0x38, + 0xc8, 0xea, 0x15, 0xef, 0x63, 0x42, 0x12, 0x54, 0x31, 0x22, 0x73, 0x8e, + 0xbc, 0xc0, 0x86, 0xfe, 0x89, 0x8a, 0x0a, 0x21, 0x4a, 0x49, 0xa7, 0x2d, + 0xa0, 0xc9, 0x35, 0x91, 0x32, 0xf1, 0x12, 0x45, 0xc5, 0xf0, 0x04, 0xf1, + 0xaa, 0xde, 0xa8, 0x04, 0x4c, 0x57, 0xef, 0xae, 0x81, 0xdc, 0xa0, 0x8c, + 0xe3, 0xbc, 0xd2, 0x97, 0x85, 0xe8, 0x61, 0x08, 0xf4, 0xaa, 0xd6, 0x5d, + 0x4f, 0x67, 0x2e, 0xb2, 0xaa, 0x8d, 0xb2, 0xf2, 0xf0, 0x38, 0x99, 0xab, + 0x15, 0x84, 0x7b, 0xd0, 0xc7, 0x5d, 0xd8, 0xba, 0xc2, 0xaf, 0x5f, 0x60, + 0x0d, 0x7d, 0x7e, 0xc0, 0x37, 0x87, 0x68, 0x31, 0x54, 0x7f, 0x71, 0x8e, + 0xeb, 0x11, 0xee, 0x8a, 0xa0, 0xfa, 0x63, 0xd0, 0xfe, 0xfe, 0xd4, 0x5d, + 0xfd, 0xac, 0x27, 0xa7, 0xf3, 0x35, 0xd3, 0xd6, 0x01, 0x00, 0x70, 0x01, + 0x0a, 0x34, 0x10, 0x90, 0xb5, 0x6a, 0x14, 0xce, 0x14, 0xde, 0x37, 0xc8, + 0x16, 0x60, 0x15, 0xa4, 0x02, 0xd8, 0x1d, 0x08, 0x17, 0x96, 0x29, 0xf1, + 0xa9, 0x2a, 0xe2, 0x35, 0xfa, 0xc4, 0x9b, 0x54, 0x06, 0x87, 0xfa, 0xd7, + 0x9c, 0x63, 0x52, 0x81, 0x50, 0xf0, 0x20, 0xc1, 0x25, 0x53, 0xb8, 0xb4, + 0xa3, 0xbf, 0xe8, 0xc5, 0xef, 0x38, 0x81, 0x4b, 0xdd, 0xf6, 0x74, 0x5c, + 0xaa, 0xfd, 0x33, 0xfe, 0x60, 0x00, 0xdd, 0x66, 0x6a, 0x33, 0x16, 0xe3, + 0xdb, 0xf3, 0x98, 0x5a, 0x53, 0xe7, 0x5d, 0xdb, 0xa5, 0x57, 0x73, 0xfa, + 0xb4, 0xc4, 0x3c, 0xa7, 0x6d, 0x9d, 0xcb, 0x33, 0x28, 0x31, 0xcf, 0xfc, + 0xcb, 0x61, 0x25, 0xb8, 0x65, 0x59, 0x76, 0x19, 0xb9, 0x1c, 0x91, 0xe6, + 0x55, 0x12, 0x3b, 0xac, 0xc8, 0x15, 0xb4, 0xa9, 0x7e, 0xab, 0xec, 0x98, + 0x0a, 0x46, 0x6a, 0x9c, 0x1c, 0x8c, 0xaa, 0x34, 0xfa, 0x53, 0xa1, 0x59, + 0xa5, 0x92, 0xe4, 0x09, 0xad, 0x6b, 0xcf, 0x58, 0x0d, 0xe1, 0x3a, 0x04, + 0x54, 0x7f, 0x5b, 0xb1, 0x33, 0x1e, 0x9c, 0x76, 0x14, 0xa9, 0x15, 0x25, + 0x9b, 0x8d, 0x0e, 0xd2, 0xbd, 0xb5, 0x6a, 0x86, 0xc0, 0x1c, 0x01, 0x08, + 0x34, 0x19, 0x76, 0x16, 0x1a, 0x19, 0x4e, 0x6c, 0x36, 0x73, 0x4c, 0x1c, + 0xe1, 0x0b, 0xc0, 0x65, 0x96, 0x11, 0x14, 0x00, 0x24, 0x28, 0x07, 0xbf, + 0x34, 0xdd, 0x3c, 0x94, 0x26, 0xcd, 0x53, 0xbd, 0x78, 0x5a, 0xf5, 0x5c, + 0x06, 0x08, 0x8a, 0x0d, 0x2f, 0x6a, 0x41, 0xa8, 0x2d, 0x28, 0xb3, 0x43, + 0xbc, 0xb4, 0x9a, 0x06, 0xb3, 0x97, 0x2c, 0xa8, 0x1f, 0x76, 0xe5, 0x4e, + 0xef, 0x64, 0xf4, 0x42, 0xe6, 0x02, 0x16, 0x55, 0x2d, 0x3f, 0x1f, 0x5b, + 0x20, 0xbc, 0xb5, 0xce, 0x26, 0x0b, 0x4b, 0x79, 0x82, 0x7d, 0x37, 0x13, + 0xc7, 0x1b, 0xbf, 0xc7, 0x7c, 0x5a, 0x06, 0xda, 0xf5, 0x36, 0x18, 0xd1, + 0x57, 0x6b, 0x9f, 0x41, 0xe6, 0xea, 0x14, 0xa0, 0xad, 0xf2, 0xa1, 0x79, + 0x2e, 0x9b, 0x05, 0xb4, 0x27, 0xa9, 0x90, 0xae, 0xba, 0x7a, 0x5b, 0x78, + 0xc0, 0x5b, 0xae, 0xa6, 0xff, 0xb4, 0x03, 0x78, 0x5e, 0x83, 0x15, 0x48, + 0xf7, 0x4f, 0x2a, 0xec, 0x46, 0xad, 0x53, 0x83, 0x81, 0xc8, 0x76, 0xbb, + 0xc4, 0x39, 0x4d, 0xb3, 0x34, 0x7d, 0xb3, 0x76, 0x04, 0x07, 0x01, 0x0e, + 0x34, 0x08, 0xb0, 0x79, 0x5a, 0xb0, 0x4a, 0xdc, 0x0f, 0x0c, 0x60, 0x16, + 0x78, 0xb8, 0x2c, 0x21, 0x0a, 0x03, 0x94, 0x50, 0x6c, 0xb3, 0x89, 0xa0, + 0xc0, 0xd9, 0x26, 0x41, 0x10, 0xc0, 0x52, 0xb6, 0xdd, 0xe3, 0x06, 0x9c, + 0xdb, 0x5b, 0xf8, 0xbd, 0x6b, 0xd9, 0x0b, 0x94, 0xe7, 0xcf, 0xe2, 0xf7, + 0xcc, 0x76, 0xd9, 0x49, 0xab, 0x89, 0xcf, 0x10, 0xbc, 0xfc, 0x3f, 0x36, + 0x42, 0x43, 0x2b, 0x80, 0x35, 0xd9, 0xdf, 0xd3, 0x90, 0x1d, 0x7c, 0x7b, + 0xfd, 0x96, 0x21, 0x4a, 0xd6, 0x62, 0x6d, 0x11, 0x8f, 0xb7, 0x70, 0xca, + 0x81, 0x2c, 0x75, 0x4f, 0x6c, 0x48, 0x2c, 0x5c, 0xd5, 0xc1, 0x49, 0xea, + 0x55, 0x84, 0xcc, 0xe6, 0xac, 0x58, 0x8a, 0xdf, 0x56, 0x00, 0x15, 0xb6, + 0xbb, 0x2d, 0xe3, 0xd9, 0x2b, 0x2a, 0x12, 0x9f, 0xf2, 0x9a, 0x8b, 0xd6, + 0x1a, 0x95, 0x00, 0x29, 0x5f, 0x54, 0xc0, 0xde, 0x11, 0xa0, 0x45, 0x48, + 0xed, 0xcd, 0xca, 0xda, 0x87, 0x1c, 0x87, 0xad, 0xac, 0xba, 0xda, 0x34, + 0x1f, 0x59, 0xae, 0xb6, 0x08, 0x60, 0x01, 0xc0, 0x01, 0x04, 0x34, 0x08, + 0x70, 0x36, 0x44, 0x05, 0x62, 0x28, 0x18, 0x7a, 0x73, 0x8e, 0xe0, 0x16, + 0x12, 0x88, 0x21, 0x64, 0xec, 0x65, 0x9a, 0xe0, 0x60, 0xd0, 0xd2, 0xc8, + 0xcb, 0xc6, 0x51, 0xc5, 0xa2, 0xeb, 0x18, 0x43, 0x73, 0x88, 0x91, 0xb5, + 0xa8, 0x25, 0x0f, 0x07, 0x46, 0x34, 0x4e, 0xf2, 0x5f, 0x7f, 0x96, 0x2c, + 0x0b, 0xb5, 0x65, 0xdf, 0xf0, 0x89, 0xb6, 0xbd, 0xdf, 0xed, 0xd1, 0x00, + 0x16, 0x02, 0xc9, 0xf7, 0x63, 0xe1, 0xc3, 0x94, 0x85, 0x77, 0x71, 0xf7, + 0xe8, 0x04, 0x73, 0xcf, 0x76, 0x34, 0x95, 0xd5, 0x74, 0x90, 0xa2, 0xd5, + 0x55, 0xc2, 0xa7, 0x19, 0x4a, 0x81, 0x3a, 0x71, 0xe5, 0xc7, 0xff, 0xfd, + 0x46, 0x1a, 0xea, 0x4a, 0xdf, 0xdf, 0xf1, 0xe2, 0x27, 0xa1, 0xca, 0xd0, + 0xc3, 0xe1, 0xfd, 0xf5, 0xf8, 0xc8, 0xd6, 0xb5, 0x4b, 0x76, 0xa4, 0xda, + 0xe3, 0x4f, 0x7d, 0xb3, 0x53, 0xbf, 0xd2, 0x8d, 0x64, 0xc2, 0x00, 0x37, + 0x84, 0x68, 0x15, 0x52, 0x1d, 0xc0, 0x6d, 0x72, 0xe8, 0x76, 0x14, 0xb1, + 0xcd, 0x65, 0xa6, 0xc9, 0x60, 0xaa, 0xeb, 0x08, 0xdb, 0x30, 0x00, 0x70, + 0x01, 0x0e, 0x34, 0x08, 0x90, 0x69, 0xb2, 0x0c, 0x84, 0x63, 0x59, 0x99, + 0xdd, 0x4e, 0x5c, 0xea, 0x81, 0xc3, 0x05, 0x17, 0x46, 0x80, 0x04, 0xbb, + 0x4a, 0xc4, 0x55, 0x5e, 0x89, 0x04, 0x42, 0x94, 0x08, 0x12, 0x2b, 0xb7, + 0x29, 0x51, 0x59, 0x5b, 0xa5, 0x9c, 0x3d, 0x27, 0xf6, 0xee, 0x81, 0x9d, + 0xb1, 0x9a, 0xce, 0x77, 0xf4, 0x15, 0x01, 0x9e, 0x2b, 0xc6, 0xab, 0x53, + 0x89, 0xaa, 0x23, 0x6e, 0x3d, 0xa5, 0x45, 0xac, 0xce, 0xc8, 0x8a, 0x59, + 0x68, 0xb7, 0x6f, 0xcd, 0xda, 0x80, 0x4f, 0x1f, 0xbe, 0xcd, 0x55, 0x08, + 0x89, 0xad, 0x1d, 0x36, 0x74, 0x5c, 0x69, 0xe8, 0xe9, 0x96, 0xb2, 0x6a, + 0xeb, 0x7e, 0x04, 0x80, 0x5a, 0x5e, 0xd1, 0x9b, 0x28, 0x05, 0x32, 0xab, + 0x2b, 0x05, 0x68, 0x7c, 0x71, 0xb2, 0xd9, 0xcb, 0xf7, 0x55, 0xed, 0x70, + 0x0c, 0x4a, 0xc2, 0x5c, 0x7f, 0xd7, 0xf9, 0xcf, 0x8a, 0x16, 0x28, 0x4e, + 0xa4, 0x6f, 0xd7, 0x29, 0xa1, 0x1d, 0x5f, 0x99, 0x25, 0xac, 0x49, 0x4a, + 0xa2, 0xa6, 0x3e, 0x39, 0x81, 0xbc, 0x1b, 0x40, 0xea, 0xcb, 0x52, 0x8a, + 0x08, 0x76, 0x18, 0xd2, 0xeb, 0xfb, 0xbb, 0x6b, 0x44, 0xff, 0x7a, 0x02, + 0xda, 0xb5, 0x93, 0x07, 0x01, 0x14, 0x34, 0x08, 0xb0, 0xa5, 0x80, 0xa5, + 0x56, 0xc5, 0x15, 0xbe, 0xd3, 0x10, 0x80, 0x84, 0x20, 0x58, 0x09, 0x4e, + 0x8c, 0xd1, 0xa0, 0x78, 0xd3, 0xa4, 0xca, 0x9b, 0xc7, 0xdf, 0x95, 0x35, + 0x21, 0x8e, 0x38, 0x80, 0xb7, 0x2d, 0xa1, 0x05, 0x05, 0xec, 0x74, 0x2a, + 0x9a, 0x2f, 0x6d, 0xf1, 0xe7, 0x02, 0xe2, 0xe9, 0xb5, 0x5b, 0x3d, 0x92, + 0x2f, 0xa3, 0xfd, 0xfe, 0x32, 0x00, 0x16, 0x22, 0x2f, 0x58, 0xe8, 0xfb, + 0x3b, 0x22, 0xea, 0x10, 0x9f, 0x8c, 0xc0, 0x27, 0x1a, 0xdd, 0xf5, 0x48, + 0xab, 0xed, 0x9a, 0xc8, 0x17, 0xa5, 0x54, 0x73, 0xfe, 0x35, 0x37, 0xd5, + 0xfa, 0x4d, 0x8f, 0xc7, 0xca, 0xea, 0xec, 0xc7, 0x9e, 0xaa, 0xd3, 0x15, + 0x16, 0x01, 0xf5, 0x8d, 0x79, 0xfe, 0x5e, 0x9f, 0xc8, 0x29, 0xaf, 0x93, + 0x5d, 0x61, 0x3f, 0xd3, 0x8f, 0xe3, 0xe3, 0x7e, 0x13, 0x92, 0x60, 0x4a, + 0x5b, 0x66, 0x06, 0xf0, 0x7d, 0x02, 0xaa, 0x47, 0xbc, 0x62, 0xda, 0xf3, + 0x43, 0x90, 0x92, 0xb2, 0x4d, 0xb7, 0x8a, 0x09, 0x5d, 0x28, 0xda, 0xcb, + 0x40, 0x0e, 0x01, 0x06, 0x34, 0x1a, 0x48, 0x48, 0x0a, 0x2c, 0xcc, 0xc1, + 0xeb, 0x07, 0xa1, 0xbd, 0x55, 0xad, 0x42, 0x02, 0xc1, 0x6d, 0x80, 0x22, + 0x0b, 0xcd, 0x6e, 0xcd, 0x47, 0xaa, 0x54, 0x3f, 0xe1, 0x55, 0x35, 0xde, + 0x74, 0xfa, 0x66, 0x63, 0x02, 0xa2, 0x7b, 0x2a, 0x8d, 0x72, 0x69, 0x15, + 0x42, 0x41, 0x02, 0x86, 0xca, 0x89, 0x9d, 0xcb, 0x07, 0x28, 0xcf, 0xf0, + 0x70, 0xc4, 0x34, 0x75, 0x7f, 0xe8, 0x2e, 0x78, 0x26, 0x5b, 0xb5, 0x72, + 0x08, 0xfe, 0x79, 0xff, 0x97, 0xa6, 0x71, 0xfc, 0x0b, 0xa4, 0x83, 0x95, + 0x6f, 0x85, 0xdc, 0x09, 0xc1, 0x49, 0xf5, 0x3b, 0xbe, 0x16, 0xdb, 0xd4, + 0x6e, 0xfc, 0x68, 0xa5, 0x95, 0xad, 0x75, 0x4a, 0x93, 0x03, 0x36, 0x19, + 0x53, 0xa6, 0xb9, 0x99, 0x95, 0x64, 0x8e, 0xd7, 0xf1, 0xe8, 0x94, 0x4e, + 0xa1, 0x6e, 0xaa, 0x68, 0xe9, 0xb5, 0xe0, 0xc2, 0xa6, 0xbe, 0x8f, 0x59, + 0xd2, 0x8e, 0x76, 0x9b, 0x83, 0xa2, 0xf6, 0x93, 0xda, 0xe1, 0xc8, 0x84, + 0xcd, 0xa1, 0xa3, 0x38, 0x01, 0xbc, 0x1f, 0x40, 0xf2, 0x84, 0x80, 0xad, + 0x28, 0x7a, 0x15, 0x39, 0xac, 0xf1, 0xf5, 0xa8, 0xb2, 0xcb, 0xee, 0xed, + 0x44, 0xe9, 0x42, 0x03, 0x07, 0x01, 0x02, 0x34, 0x10, 0xd0, 0x30, 0x0c, + 0x15, 0x0d, 0x02, 0x11, 0x31, 0x10, 0x84, 0x33, 0x23, 0xd6, 0x07, 0x82, + 0xab, 0x02, 0xc0, 0x01, 0x60, 0x03, 0xb0, 0xa8, 0xf9, 0x17, 0x61, 0x89, + 0xd0, 0x19, 0xc9, 0x2d, 0x31, 0x5d, 0x76, 0x02, 0x72, 0x93, 0x2c, 0xd0, + 0x5c, 0xb4, 0x09, 0xbf, 0x3e, 0x84, 0x25, 0x3c, 0xc1, 0x9c, 0xe8, 0xb6, + 0xd9, 0x65, 0x8c, 0xe7, 0x81, 0x7a, 0x79, 0x46, 0x08, 0x44, 0xbb, 0x3e, + 0xc7, 0xdb, 0x7d, 0x42, 0x1b, 0x74, 0xa2, 0x9a, 0x43, 0xdf, 0xb6, 0x7f, + 0x64, 0xc0, 0x28, 0x00, 0x17, 0xdb, 0x3f, 0xd3, 0x42, 0x10, 0x82, 0x39, + 0xa0, 0xcb, 0x3a, 0xbe, 0x44, 0xd1, 0x1f, 0x76, 0xb2, 0x55, 0xf2, 0x8b, + 0x23, 0x83, 0xbe, 0x36, 0x43, 0xcd, 0x59, 0x14, 0xa2, 0x16, 0x2e, 0x1a, + 0x0c, 0xa0, 0x68, 0x2b, 0xcf, 0xbe, 0x01, 0x37, 0x10, 0x12, 0x15, 0x96, + 0x13, 0x6e, 0xdd, 0xfe, 0xc3, 0x4c, 0x8a, 0x11, 0xc7, 0x3e, 0x19, 0xf4, + 0x9d, 0xb9, 0xcb, 0x56, 0xd6, 0x70, 0x98, 0xc1, 0x2e, 0xc3, 0xb5, 0xd0, + 0x03, 0x78, 0x4e, 0x81, 0x65, 0x21, 0xee, 0x3b, 0x8d, 0xb5, 0x81, 0xe8, + 0x6b, 0xd4, 0x7f, 0xef, 0xf7, 0xd6, 0x96, 0x1b, 0x6d, 0x7b, 0x54, 0xcc, + 0x00, 0x07, 0x01, 0x06, 0x34, 0x08, 0xb0, 0xa5, 0x88, 0xa1, 0xb7, 0x6a, + 0x63, 0x91, 0xb0, 0x69, 0x81, 0x4d, 0x00, 0x46, 0x06, 0x4b, 0x17, 0x9e, + 0x77, 0xed, 0x67, 0x31, 0x0d, 0xa8, 0x84, 0xa4, 0xa3, 0x39, 0x7f, 0x7c, + 0x83, 0x94, 0x13, 0x20, 0xf4, 0x43, 0xbb, 0x7b, 0xe3, 0xd6, 0x3a, 0x22, + 0x2f, 0x26, 0x7d, 0x7e, 0xad, 0x92, 0x5d, 0x5e, 0x2b, 0x2d, 0x73, 0xfa, + 0x70, 0x9b, 0x8e, 0xdf, 0xfb, 0xff, 0xae, 0xc0, 0x09, 0x17, 0x74, 0x5c, + 0xe7, 0x8f, 0xf8, 0xbd, 0xec, 0x13, 0x7c, 0xbb, 0x20, 0x17, 0x73, 0x53, + 0xd1, 0x0d, 0xd4, 0x63, 0xb3, 0x87, 0xc3, 0x50, 0x42, 0x35, 0x28, 0xbe, + 0xde, 0x9e, 0x09, 0x44, 0x64, 0x08, 0xee, 0x4a, 0xd8, 0x6f, 0x4b, 0xb8, + 0xc5, 0x45, 0xfd, 0xd3, 0x90, 0x0e, 0xd7, 0xd0, 0xe3, 0x8a, 0x5e, 0x28, + 0x96, 0x4e, 0xc2, 0x91, 0xed, 0xfc, 0x3f, 0x12, 0x72, 0x00, 0x4b, 0xb8, + 0x06, 0xf0, 0xdd, 0xea, 0x40, 0x02, 0x2a, 0x47, 0xb8, 0x95, 0xab, 0x81, + 0xc7, 0xc0, 0x2c, 0xc8, 0x92, 0xc9, 0x72, 0xcb, 0x61, 0x62, 0xca, 0xfe, + 0x37, 0x0f, 0x57, 0xa2, 0x60, 0x0e, 0x01, 0x0a, 0x34, 0x19, 0x73, 0x53, + 0x40, 0xde, 0xd9, 0x9b, 0xac, 0x00, 0x80, 0x52, 0xcb, 0x03, 0x5c, 0x81, + 0x37, 0x04, 0x13, 0xd6, 0x9b, 0xf4, 0xfa, 0x2b, 0xdb, 0x33, 0x15, 0x91, + 0x61, 0x25, 0xfc, 0x67, 0x9b, 0x75, 0x1b, 0x77, 0x69, 0x69, 0x5b, 0xb2, + 0xf2, 0x5e, 0xf9, 0xa3, 0x77, 0x08, 0x33, 0x74, 0x38, 0x02, 0xee, 0xc4, + 0x4e, 0x85, 0x0c, 0xe3, 0x76, 0x1e, 0xc3, 0x6a, 0x8b, 0x62, 0xa2, 0x0c, + 0xc1, 0x1a, 0x5e, 0x76, 0xae, 0x0b, 0x64, 0x6b, 0xb2, 0x14, 0x18, 0x5d, + 0x6a, 0x22, 0xca, 0x60, 0xc8, 0x54, 0x12, 0x6b, 0xc5, 0x22, 0x4a, 0x8a, + 0xa2, 0x5d, 0x23, 0xee, 0xf4, 0x3a, 0x7c, 0x75, 0xb4, 0x65, 0xef, 0xb4, + 0x6f, 0xa8, 0x8f, 0x23, 0x75, 0xa4, 0x8a, 0x82, 0x78, 0x29, 0xda, 0xf0, + 0x60, 0xc3, 0xf7, 0xf7, 0x28, 0xad, 0x8b, 0xa9, 0xc7, 0xd8, 0x25, 0x82, + 0xf3, 0xd4, 0xd7, 0xcd, 0xd3, 0xb1, 0xd3, 0x32, 0xb5, 0x2b, 0x3d, 0xa0, + 0x1b, 0xc2, 0x34, 0x0a, 0xa9, 0x1e, 0xe3, 0x17, 0x2b, 0x60, 0x76, 0x1b, + 0xb4, 0xf7, 0xb2, 0xf6, 0x13, 0x6c, 0xfb, 0x2d, 0xf3, 0x2d, 0xa8, 0xc0, + 0x38, 0x01, 0x04, 0x34, 0x19, 0xb0, 0x25, 0x33, 0x0d, 0x52, 0x27, 0x3b, + 0x3c, 0x65, 0x54, 0x76, 0x50, 0x68, 0x02, 0xa1, 0x08, 0x40, 0x68, 0x35, + 0x80, 0x4b, 0xbd, 0x9e, 0x8f, 0x2a, 0xd2, 0xe4, 0xf2, 0x4d, 0x95, 0xd2, + 0xba, 0x7f, 0x54, 0x01, 0x81, 0xaa, 0x6d, 0x05, 0x12, 0x7f, 0x7f, 0x44, + 0x9b, 0x2a, 0x95, 0x3a, 0xf0, 0x95, 0x24, 0x60, 0xd2, 0x66, 0xf2, 0xb8, + 0x11, 0x0c, 0xd9, 0xfa, 0xea, 0x62, 0x54, 0x28, 0x55, 0x89, 0x41, 0x68, + 0xe3, 0xe5, 0xfa, 0x79, 0x48, 0x5a, 0xf9, 0xf2, 0xed, 0x99, 0x05, 0xaf, + 0x57, 0x35, 0x1b, 0x8e, 0xfc, 0x42, 0x09, 0x04, 0xbb, 0x71, 0x1c, 0x88, + 0x00, 0x9d, 0x5e, 0xea, 0xd0, 0xae, 0x79, 0x71, 0x53, 0xf9, 0xe8, 0xbc, + 0x8f, 0xb7, 0x6f, 0x3c, 0x56, 0x2e, 0xb2, 0x5f, 0x2d, 0x51, 0x79, 0xb7, + 0x08, 0x21, 0x8b, 0x80, 0xa6, 0x49, 0xcb, 0xf0, 0x9e, 0x47, 0x07, 0x3a, + 0x81, 0xc7, 0xdb, 0x20, 0x37, 0x83, 0x68, 0x1d, 0x51, 0x41, 0xb0, 0x10, + 0x3b, 0x0f, 0x7e, 0xb2, 0x9d, 0x8b, 0x9a, 0xab, 0xdc, 0xbb, 0x2b, 0xc9, + 0x81, 0x80, 0x70, 0x01, 0x0e, 0x34, 0x08, 0x94, 0x88, 0x1a, 0x15, 0x5c, + 0x66, 0x30, 0x3b, 0x1e, 0x83, 0x41, 0xb2, 0x05, 0x5a, 0x37, 0xa9, 0x33, + 0x06, 0x11, 0xa6, 0x21, 0x00, 0xd3, 0x9a, 0x29, 0x49, 0xc5, 0x39, 0x0e, + 0x80, 0x9a, 0xb4, 0x47, 0x6e, 0xc4, 0x02, 0x03, 0x29, 0x50, 0x88, 0x23, + 0x97, 0x2b, 0xd1, 0x78, 0xfd, 0x39, 0x00, 0xbf, 0x96, 0x8f, 0x1c, 0xa4, + 0x10, 0x1d, 0xfe, 0x11, 0x25, 0xbf, 0x57, 0xaf, 0x44, 0x51, 0x2c, 0xa5, + 0x12, 0x9a, 0xc6, 0x9f, 0x2f, 0xaf, 0xf7, 0xeb, 0x77, 0x48, 0xa8, 0xe7, + 0xc3, 0xa6, 0xf7, 0x9a, 0xa6, 0x66, 0x5d, 0xdd, 0xf6, 0x54, 0xaf, 0xa6, + 0xe8, 0x54, 0x2a, 0x13, 0x17, 0xd5, 0x0c, 0x44, 0x85, 0x8b, 0x46, 0xd0, + 0x4d, 0xd4, 0xa4, 0x26, 0xaf, 0x35, 0x1b, 0x9d, 0x07, 0x54, 0x24, 0xa1, + 0x47, 0x45, 0xed, 0xa1, 0xa6, 0x88, 0x2d, 0x6d, 0xe5, 0x38, 0x79, 0xbf, + 0x0f, 0xaa, 0xdc, 0x84, 0xc4, 0x01, 0x55, 0x80, 0x6f, 0x0d, 0xd0, 0x62, + 0xa9, 0x1b, 0xb7, 0x95, 0x4c, 0x4e, 0x98, 0x75, 0x63, 0x81, 0xe8, 0x46, + 0x77, 0x22, 0x6c, 0xab, 0x8d, 0xc5, 0x3e, 0x1a, 0x4b, 0xca, 0xcb, 0x68, + 0x00, 0xe0, 0x01, 0x0e, 0x34, 0x14, 0x90, 0x60, 0x0c, 0x0e, 0x58, 0xc1, + 0x42, 0x11, 0x0c, 0xa2, 0x87, 0xce, 0x28, 0x19, 0x56, 0x10, 0x42, 0x0b, + 0x00, 0xa4, 0xf3, 0xde, 0x88, 0x8d, 0xbf, 0x24, 0xbf, 0xc4, 0x25, 0x96, + 0xb2, 0x54, 0x1f, 0x53, 0x34, 0xcb, 0x52, 0x86, 0xcd, 0x98, 0xde, 0xe9, + 0x3e, 0xf7, 0xd6, 0x6c, 0x0c, 0xb5, 0x38, 0x98, 0x56, 0x16, 0xbc, 0xe5, + 0x17, 0x64, 0x1d, 0x1e, 0xa0, 0x65, 0x87, 0x03, 0xe2, 0xd5, 0x48, 0x67, + 0x22, 0x69, 0x11, 0x28, 0xcd, 0x5c, 0x59, 0xb1, 0xd0, 0xa8, 0x30, 0x24, + 0x66, 0xd4, 0x8f, 0x84, 0x1e, 0xde, 0x26, 0x65, 0xb7, 0xe5, 0x85, 0x4d, + 0x5a, 0x45, 0xd9, 0x4d, 0xfb, 0xed, 0x87, 0x53, 0xc6, 0x79, 0xdc, 0x38, + 0x2e, 0x17, 0x74, 0xc6, 0x9a, 0xb9, 0xf2, 0x4f, 0x18, 0xa3, 0x6a, 0xc3, + 0x01, 0xf6, 0x32, 0xd2, 0x09, 0xef, 0x3c, 0x2c, 0x8e, 0xdb, 0xfd, 0x1e, + 0x15, 0x16, 0x3d, 0x5b, 0x4a, 0x96, 0xe3, 0x9e, 0x7e, 0x1b, 0x71, 0xf6, + 0x3b, 0xd4, 0xac, 0x52, 0x53, 0x8b, 0x5f, 0x4b, 0x37, 0x4c, 0x03, 0x78, + 0x3e, 0x81, 0x15, 0x25, 0x2b, 0xa1, 0x73, 0x10, 0xe3, 0x90, 0xb2, 0x92, + 0x2e, 0xe4, 0x5b, 0x0a, 0x55, 0x09, 0xee, 0x98, 0xc0, 0x07, 0x01, 0x0a, + 0x34, 0x1c, 0xc8, 0x58, 0x23, 0x15, 0xce, 0x83, 0x35, 0x0c, 0x76, 0xac, + 0xdb, 0x60, 0x00, 0x20, 0x5b, 0x7a, 0x00, 0x4e, 0x58, 0xb5, 0xeb, 0xa7, + 0xeb, 0xe9, 0x73, 0x0c, 0xd6, 0x41, 0x61, 0x6e, 0x9f, 0xc6, 0xf0, 0x4e, + 0xe0, 0xb8, 0x3e, 0x3a, 0xa2, 0xd8, 0xd6, 0x84, 0x26, 0x68, 0x77, 0xf7, + 0xc8, 0x7d, 0xa9, 0x71, 0x9b, 0x9d, 0x24, 0xe2, 0xd4, 0x39, 0x1c, 0x94, + 0x29, 0xad, 0x3d, 0xe6, 0x31, 0x09, 0x25, 0x21, 0x08, 0x18, 0x72, 0x7c, + 0x1d, 0x0c, 0xc1, 0x17, 0xd5, 0xaf, 0xc8, 0xe1, 0x98, 0x7b, 0x67, 0x75, + 0xe9, 0x7d, 0x56, 0xda, 0xab, 0x57, 0xe2, 0xfe, 0xb2, 0x68, 0x37, 0x59, + 0xbf, 0xab, 0xae, 0x55, 0x96, 0xe1, 0xef, 0xc6, 0x38, 0x46, 0xa8, 0x06, + 0x6b, 0x6c, 0x41, 0x6d, 0x1b, 0x92, 0x00, 0xbe, 0xda, 0x7c, 0xbe, 0x64, + 0x52, 0x00, 0xad, 0xe9, 0xb2, 0x03, 0x78, 0x56, 0x83, 0x55, 0x49, 0x2f, + 0x17, 0x4c, 0xf0, 0x6d, 0xb0, 0x70, 0x39, 0x0f, 0xaf, 0x2b, 0xef, 0xb2, + 0x12, 0xc5, 0xa2, 0xb2, 0x36, 0x50, 0x00, 0x07, 0x01, 0x12, 0x34, 0x14, + 0xac, 0x9a, 0x0a, 0xcc, 0x4c, 0x56, 0x31, 0x85, 0x72, 0x02, 0x50, 0x8c, + 0xd0, 0x5b, 0x71, 0xae, 0xec, 0x2d, 0xaa, 0x25, 0xdf, 0xb6, 0xcb, 0xfa, + 0xee, 0x89, 0x68, 0xd1, 0x5e, 0x98, 0x5b, 0xb4, 0xca, 0x4c, 0x29, 0x97, + 0x98, 0xc0, 0x5e, 0x8f, 0xa8, 0x93, 0x5c, 0xe5, 0xc7, 0x49, 0xdf, 0xfd, + 0xf6, 0x30, 0xbb, 0x98, 0x9d, 0x0c, 0xd7, 0xd1, 0xb3, 0x6e, 0x3f, 0x7f, + 0xf6, 0x9e, 0x42, 0xd0, 0x10, 0x44, 0xa5, 0x13, 0xeb, 0xed, 0xa8, 0x2c, + 0x8e, 0x8e, 0x1e, 0x88, 0x21, 0xa9, 0x9d, 0xcb, 0x84, 0xa9, 0x0e, 0xff, + 0x9e, 0xb2, 0xc3, 0x53, 0x03, 0x17, 0xd4, 0xef, 0xb9, 0x02, 0x84, 0xf1, + 0xe7, 0xa3, 0x1b, 0xac, 0x5c, 0x08, 0xa2, 0xf9, 0xee, 0x74, 0x58, 0xe1, + 0x84, 0x01, 0xae, 0x38, 0xcc, 0xf3, 0x03, 0xc1, 0x35, 0xf8, 0xfe, 0xec, + 0x9e, 0xc0, 0x88, 0x17, 0x4e, 0x19, 0xa0, 0x06, 0xf0, 0xbd, 0x06, 0x6a, + 0x93, 0xbb, 0x2a, 0xa8, 0x4f, 0xb9, 0xa6, 0xee, 0x00, 0x72, 0x1f, 0x6e, + 0xdf, 0x7d, 0xb3, 0x63, 0x61, 0x33, 0x53, 0xa8, 0x53, 0x00, 0x0e, 0x01, + 0x0c, 0x34, 0x04, 0x6c, 0xb9, 0x91, 0x0c, 0xca, 0xc1, 0xe1, 0x95, 0xe2, + 0xa7, 0x5b, 0x06, 0xb6, 0x16, 0x05, 0x80, 0x14, 0x94, 0xe9, 0xa3, 0xbb, + 0x42, 0x6b, 0x3a, 0xba, 0xaa, 0xc3, 0xad, 0x95, 0x8a, 0x6b, 0x34, 0x82, + 0xbd, 0x60, 0x1e, 0x7a, 0x51, 0xf7, 0x7f, 0x8e, 0x68, 0x65, 0xad, 0x95, + 0x55, 0xc6, 0x75, 0xd0, 0xca, 0x65, 0x75, 0x2b, 0xa4, 0x61, 0xce, 0xe0, + 0x33, 0xa5, 0xea, 0x78, 0x5d, 0x1e, 0x2c, 0xa9, 0x13, 0x19, 0x82, 0x11, + 0x71, 0x8e, 0x5a, 0x9e, 0xb1, 0x85, 0x01, 0x3c, 0x9e, 0x87, 0x6e, 0x94, + 0x42, 0x17, 0x26, 0xa6, 0xa5, 0x4d, 0x5e, 0xdb, 0xd7, 0xd1, 0xdb, 0xb2, + 0x96, 0x82, 0xa9, 0x1a, 0x3c, 0x7d, 0x2d, 0x19, 0x82, 0xc5, 0xd1, 0xfc, + 0xaf, 0x82, 0x07, 0xf9, 0x30, 0x0e, 0x9c, 0x7c, 0x68, 0xd3, 0x8b, 0x1b, + 0xd4, 0x46, 0x4a, 0x63, 0x5f, 0x6b, 0x8f, 0xe2, 0x3a, 0xae, 0x6a, 0x02, + 0x79, 0x28, 0x25, 0xa5, 0xc3, 0xad, 0xb8, 0xa2, 0xdb, 0x74, 0x22, 0x0a, + 0xe5, 0xe8, 0x5b, 0x00, 0x6f, 0x09, 0xd0, 0x22, 0xa4, 0xb6, 0xbe, 0x83, + 0x78, 0xc3, 0x8e, 0xc2, 0x8b, 0x12, 0x1a, 0xce, 0xb1, 0x44, 0x6f, 0xad, + 0xec, 0xf6, 0x0c, 0x00, 0x00, 0xe0, 0x01, 0x0e, 0x34, 0x04, 0x70, 0x49, + 0xb2, 0x11, 0x42, 0x81, 0x12, 0xb0, 0x29, 0x8f, 0x70, 0x10, 0xee, 0xd6, + 0xcb, 0x59, 0x0c, 0x58, 0x1b, 0x35, 0x05, 0x26, 0x05, 0xd7, 0x0e, 0x86, + 0xec, 0xf1, 0x98, 0x3a, 0xdb, 0x87, 0xda, 0x33, 0x5e, 0x9c, 0x74, 0x35, + 0x8e, 0x51, 0xa3, 0xf8, 0xff, 0x91, 0x88, 0xc3, 0x26, 0x75, 0x37, 0xd0, + 0x72, 0xe9, 0x92, 0x51, 0x59, 0x46, 0x54, 0x9c, 0xf1, 0x80, 0xad, 0x6e, + 0xbf, 0x69, 0x69, 0x2c, 0x5a, 0xe6, 0x21, 0x3b, 0xa7, 0xc7, 0xf4, 0x98, + 0x0a, 0x1a, 0xf9, 0xd6, 0x85, 0x50, 0x21, 0xaf, 0xa5, 0x76, 0x56, 0xbb, + 0xc3, 0x63, 0x47, 0x1a, 0x55, 0x46, 0x72, 0x3d, 0x83, 0xd7, 0x12, 0xac, + 0x8c, 0xce, 0x1b, 0x95, 0x93, 0x34, 0x98, 0x26, 0xf3, 0xbf, 0x19, 0xc4, + 0x25, 0x53, 0x26, 0x12, 0x21, 0x1e, 0x41, 0x60, 0x4b, 0x14, 0xa5, 0x2c, + 0xf0, 0x80, 0x73, 0xed, 0xf7, 0x4e, 0xb4, 0xaf, 0xbf, 0xc1, 0x43, 0x17, + 0xef, 0x50, 0xea, 0xd7, 0xa7, 0xf9, 0x5a, 0x20, 0x6f, 0x0c, 0xd0, 0x62, + 0xa9, 0x4e, 0x6e, 0x4e, 0x84, 0xea, 0xf3, 0x57, 0x77, 0x8e, 0x07, 0x21, + 0xa7, 0x75, 0xba, 0xf3, 0xb6, 0x4c, 0x66, 0xc6, 0xe5, 0x5b, 0x10, 0x00, + 0xe0, 0x01, 0x0e, 0x34, 0x10, 0x94, 0xb3, 0x34, 0x09, 0x48, 0x86, 0x13, + 0x83, 0xc1, 0x55, 0xbc, 0x30, 0x58, 0x13, 0x12, 0xc1, 0x05, 0x0c, 0x52, + 0x57, 0xed, 0x5f, 0x9b, 0x90, 0x64, 0x53, 0x0b, 0x18, 0x46, 0xa0, 0xac, + 0x83, 0xc6, 0x4d, 0xce, 0x89, 0x0c, 0xe4, 0x10, 0x3c, 0x22, 0x08, 0x72, + 0x93, 0x16, 0x39, 0xfa, 0xb0, 0x4b, 0xe2, 0xa7, 0x40, 0x9c, 0x5b, 0x54, + 0x23, 0x10, 0xb5, 0x9c, 0x26, 0xd9, 0xeb, 0x8a, 0x50, 0x17, 0x56, 0x81, + 0x35, 0x55, 0x38, 0xf5, 0x7e, 0xff, 0x14, 0x31, 0x07, 0xbf, 0x0a, 0x79, + 0xf2, 0xaa, 0xfe, 0xd2, 0x33, 0x88, 0xe5, 0xd2, 0xd4, 0xb1, 0xc9, 0xcc, + 0x0d, 0xfc, 0xf7, 0xf1, 0x28, 0xde, 0x5b, 0xbe, 0x25, 0x75, 0x12, 0xdc, + 0x79, 0x12, 0xd8, 0xb0, 0x64, 0x56, 0x70, 0xc7, 0x9b, 0x9d, 0xd8, 0x13, + 0x1d, 0xfb, 0xe7, 0xe5, 0xfe, 0x75, 0x5f, 0x29, 0xd2, 0x95, 0xc3, 0xc1, + 0x4e, 0xdf, 0xea, 0xdb, 0xbf, 0x6b, 0xd4, 0x11, 0x05, 0xad, 0x3a, 0x81, + 0xbc, 0x23, 0x41, 0xfa, 0xa6, 0xc5, 0x8c, 0x3a, 0x2e, 0xaa, 0x01, 0xc8, + 0x7c, 0x1d, 0xd3, 0xb7, 0x98, 0xc1, 0xd4, 0xd5, 0xad, 0x90, 0x58, 0x03, + 0x07, 0x01, 0x10, 0x34, 0x08, 0xac, 0x8a, 0x13, 0xa1, 0x4c, 0x84, 0x13, + 0x8d, 0xed, 0x43, 0x7c, 0x81, 0x01, 0x0c, 0xd1, 0x02, 0x00, 0xbf, 0x21, + 0x4e, 0xdf, 0x1d, 0x3a, 0x7b, 0x29, 0xdb, 0xea, 0xa8, 0xf7, 0xb7, 0x12, + 0xfd, 0x9d, 0x61, 0x02, 0x07, 0x2d, 0xd8, 0x83, 0x6e, 0xf0, 0xe1, 0x56, + 0x6b, 0x64, 0xc3, 0x53, 0xa9, 0xcd, 0x77, 0x08, 0x98, 0x9a, 0x94, 0xed, + 0xd9, 0xb2, 0xe4, 0xd4, 0xdf, 0xde, 0x6b, 0xe7, 0x4d, 0x45, 0xce, 0x2b, + 0x5b, 0x09, 0xa5, 0x47, 0xc7, 0xd3, 0x9d, 0xe0, 0x0c, 0x6b, 0xb3, 0xc3, + 0x15, 0x34, 0x8a, 0x9b, 0xe8, 0xf7, 0xf6, 0x61, 0x6f, 0x42, 0xe2, 0x4d, + 0xda, 0xf8, 0xb2, 0x63, 0xf9, 0x9d, 0x31, 0x23, 0x52, 0xbc, 0xb7, 0xef, + 0x16, 0x11, 0x93, 0x22, 0x16, 0xb7, 0x2f, 0xf8, 0xc3, 0x49, 0x57, 0x26, + 0x34, 0xe4, 0x09, 0x6d, 0xe1, 0x38, 0xfd, 0x35, 0x16, 0xe3, 0x41, 0xef, + 0x68, 0xc5, 0x86, 0xf4, 0xc9, 0x8e, 0x98, 0x98, 0x04, 0x02, 0x5e, 0xff, + 0xa4, 0x40, 0xde, 0x19, 0xa0, 0xcd, 0x52, 0xb7, 0x5b, 0x92, 0xe1, 0x39, + 0x62, 0xab, 0x00, 0xf4, 0x33, 0xbd, 0xed, 0xdf, 0x97, 0x76, 0xa8, 0x1f, + 0x2d, 0xa7, 0x3f, 0x5a, 0x23, 0x41, 0xc0, 0x01, 0x12, 0x34, 0x08, 0x8c, + 0x98, 0x2a, 0x11, 0x86, 0x85, 0x61, 0x0a, 0x1b, 0x29, 0xbc, 0x8e, 0xc0, + 0x0c, 0xb4, 0xac, 0xb4, 0x4a, 0x42, 0x61, 0x6b, 0x15, 0x80, 0x82, 0x5a, + 0x4f, 0x92, 0x8b, 0xe6, 0xf7, 0xea, 0x05, 0xb0, 0x4c, 0x6c, 0x40, 0x7f, + 0xed, 0x80, 0x8d, 0x20, 0x99, 0x6a, 0x82, 0x0d, 0x85, 0x54, 0x04, 0xb2, + 0xe4, 0x94, 0xe7, 0x1b, 0x04, 0x72, 0xd7, 0x95, 0x79, 0xa8, 0xe5, 0xc6, + 0x07, 0x9e, 0x56, 0x3e, 0x32, 0xf3, 0x2b, 0x33, 0x96, 0x55, 0xa9, 0x4f, + 0x31, 0xbb, 0xcb, 0x36, 0xd2, 0x91, 0xfe, 0xf4, 0x4f, 0x27, 0xce, 0xb7, + 0x29, 0xdf, 0x67, 0x77, 0xb7, 0x8a, 0xad, 0xf1, 0xb3, 0x53, 0x05, 0x80, + 0xa0, 0x1c, 0xaf, 0x77, 0x7e, 0x41, 0x2e, 0x52, 0x4b, 0x79, 0x8a, 0x88, + 0x82, 0x9f, 0x93, 0x2e, 0x08, 0xaf, 0xba, 0x3f, 0x68, 0x74, 0xd4, 0x37, + 0x4b, 0x8f, 0xf2, 0xdc, 0xa0, 0x64, 0xe1, 0x5e, 0xb2, 0x8d, 0xfe, 0x5d, + 0x45, 0xa5, 0x0a, 0x32, 0xd2, 0xb1, 0xa5, 0x71, 0x16, 0x00, 0xaa, 0xcc, + 0xb3, 0x03, 0x78, 0x4e, 0x83, 0xf5, 0x4a, 0x67, 0x8d, 0x8b, 0x1a, 0x54, + 0x03, 0xb0, 0xf6, 0xfd, 0x7c, 0xd2, 0x3b, 0x35, 0x19, 0xfa, 0xbb, 0xec, + 0xe5, 0x24, 0x07, 0x01, 0x0e, 0x34, 0x1c, 0xac, 0xd2, 0x1b, 0x0a, 0x46, + 0x67, 0x70, 0x80, 0x5c, 0x26, 0x25, 0x08, 0x04, 0x1e, 0xfb, 0x37, 0x58, + 0xa0, 0xb1, 0x84, 0xe6, 0x2c, 0x5b, 0x7c, 0x60, 0x27, 0x05, 0xf2, 0xb3, + 0xd1, 0xfe, 0xd9, 0xb5, 0x97, 0x84, 0xa3, 0x4b, 0xad, 0x70, 0xf7, 0xd0, + 0x4a, 0x48, 0x24, 0xf9, 0x80, 0x5d, 0xcf, 0xf0, 0x6e, 0x6e, 0x11, 0x02, + 0x25, 0xe0, 0x21, 0x5c, 0x4c, 0x4a, 0xfb, 0xa3, 0x38, 0x11, 0x34, 0x3f, + 0xbf, 0xad, 0x90, 0xcf, 0x18, 0x96, 0xfe, 0x4f, 0x75, 0x7a, 0xb7, 0xff, + 0xbf, 0xd4, 0x34, 0xac, 0x96, 0xd0, 0x85, 0xa2, 0x08, 0x34, 0x4f, 0xa0, + 0xf8, 0xd1, 0x74, 0xae, 0x93, 0xf7, 0x2d, 0x40, 0x54, 0xa5, 0xd7, 0xd6, + 0x7e, 0x27, 0xc0, 0xfb, 0x3d, 0x49, 0xb0, 0x00, 0x90, 0x27, 0x77, 0x51, + 0xe0, 0x7c, 0x5a, 0x00, 0x02, 0x45, 0x9b, 0x24, 0xba, 0x3a, 0x3d, 0xbc, + 0x30, 0x00, 0x0d, 0xe1, 0xfa, 0x0c, 0xd5, 0x24, 0x92, 0xa4, 0x65, 0x5b, + 0x55, 0x68, 0x07, 0xc0, 0xa1, 0x63, 0x73, 0x2d, 0x9b, 0xde, 0xfb, 0x6d, + 0x97, 0x98, 0xed, 0xda, 0x91, 0xf7, 0x3d, 0xe6, 0x00, 0x1c, 0x01, 0x0e, + 0x34, 0x1a, 0x46, 0x68, 0x0b, 0x84, 0x46, 0x81, 0x11, 0x20, 0x80, 0x2c, + 0x13, 0x20, 0x7a, 0xae, 0x6a, 0x63, 0x6a, 0x06, 0xb3, 0x62, 0x8e, 0x00, + 0x40, 0x01, 0x82, 0x89, 0x35, 0x97, 0x2d, 0xd5, 0xa2, 0x82, 0x2d, 0x69, + 0x15, 0x94, 0x7e, 0x8c, 0x9c, 0x06, 0x1b, 0xa8, 0x5b, 0x2f, 0x3c, 0x80, + 0x8c, 0x90, 0x51, 0x44, 0x87, 0xda, 0x28, 0x60, 0x46, 0x9b, 0x49, 0x3f, + 0xde, 0xde, 0xda, 0xfd, 0xa0, 0x69, 0xf8, 0xae, 0x6e, 0x3e, 0xd7, 0xaa, + 0x9c, 0x68, 0x5f, 0xee, 0x15, 0xdc, 0x12, 0xf8, 0xc7, 0xa7, 0xee, 0xa4, + 0xf8, 0xdd, 0x18, 0x1c, 0x3c, 0xfa, 0x40, 0xe9, 0xe9, 0x73, 0x91, 0x05, + 0x92, 0xf1, 0x2f, 0xeb, 0x5a, 0x2e, 0xe3, 0xbb, 0x84, 0x00, 0x2a, 0x94, + 0x21, 0x22, 0xa3, 0xde, 0x9d, 0xd2, 0x6b, 0x6f, 0x68, 0x20, 0x2e, 0xdc, + 0x7a, 0x47, 0xc1, 0xea, 0xf9, 0x3e, 0x99, 0xbd, 0xfc, 0xae, 0x95, 0xb5, + 0xc5, 0xd6, 0x80, 0x5c, 0xd4, 0x03, 0x78, 0x76, 0x81, 0x1a, 0x2c, 0x70, + 0xed, 0x25, 0xe1, 0x0e, 0x3e, 0x05, 0x72, 0x45, 0xf4, 0x5a, 0x9c, 0x7f, + 0xdf, 0xe3, 0xc8, 0x4b, 0xea, 0xeb, 0x56, 0x31, 0xaf, 0xb7, 0x88, 0x07, + 0x01, 0x10, 0x34, 0x19, 0xe8, 0x75, 0x41, 0x95, 0x46, 0x22, 0x29, 0xe3, + 0xbd, 0x28, 0xd9, 0x59, 0x4e, 0x19, 0xc8, 0x42, 0xaf, 0x58, 0xa4, 0x50, + 0x03, 0x19, 0x80, 0xb6, 0x42, 0x43, 0x2e, 0x15, 0x12, 0xad, 0xef, 0xf8, + 0x00, 0x3d, 0x95, 0x31, 0x39, 0x95, 0x7e, 0xd0, 0xad, 0x70, 0x60, 0xa9, + 0x2e, 0xe1, 0x1f, 0x1d, 0xa0, 0x04, 0x50, 0xcd, 0xd5, 0xac, 0x5b, 0xba, + 0xfd, 0x7f, 0x75, 0x44, 0x6e, 0xff, 0x5d, 0x65, 0x9c, 0x72, 0x8b, 0x95, + 0x0a, 0xd4, 0x72, 0x80, 0x11, 0x3b, 0xb0, 0x0b, 0xbd, 0xf7, 0xfe, 0xce, + 0xba, 0x98, 0x34, 0xa5, 0x48, 0xf2, 0xe0, 0x23, 0x3f, 0xcb, 0xe8, 0xb5, + 0x69, 0x5a, 0x56, 0xb8, 0x02, 0x8a, 0x66, 0xf6, 0xf5, 0x69, 0x4e, 0xfa, + 0xe9, 0x94, 0xd2, 0xad, 0xa4, 0x5a, 0x1b, 0x93, 0x62, 0xd9, 0x96, 0x73, + 0x18, 0xdf, 0x0a, 0x9d, 0xed, 0x18, 0xb9, 0x00, 0x02, 0x54, 0x99, 0x70, + 0x1b, 0xc4, 0x34, 0x18, 0xb4, 0x5c, 0xa5, 0xba, 0xd1, 0x6e, 0x63, 0x77, + 0x31, 0xc0, 0xf8, 0x0c, 0x37, 0x74, 0x92, 0x11, 0x45, 0x49, 0x54, 0xb5, + 0xac, 0x76, 0xf9, 0x43, 0x46, 0x32, 0x68, 0x00, 0x38, 0x01, 0x08, 0x34, + 0x19, 0xea, 0x86, 0x0a, 0x98, 0x48, 0xa1, 0x60, 0xa0, 0x84, 0x41, 0xfc, + 0x46, 0x0e, 0x58, 0x1d, 0x3b, 0x56, 0x2c, 0x0b, 0x15, 0x6a, 0x02, 0x07, + 0x4c, 0xd0, 0x1f, 0xfc, 0x80, 0x82, 0x79, 0x51, 0xa0, 0x68, 0x59, 0xae, + 0x2f, 0xca, 0x03, 0x23, 0xb0, 0x93, 0x2f, 0x5f, 0xbd, 0xc4, 0x22, 0x7e, + 0xca, 0xaa, 0x96, 0xb4, 0xc6, 0x8c, 0x8a, 0x71, 0x90, 0x7c, 0xfa, 0x2f, + 0x3d, 0x9e, 0x3d, 0x2d, 0xd0, 0xf0, 0xc6, 0xae, 0xfb, 0x26, 0x8b, 0x4e, + 0xbb, 0x0b, 0xab, 0xa5, 0xb5, 0x40, 0xa5, 0x97, 0x7d, 0x5e, 0xbe, 0x3f, + 0x5f, 0x21, 0x03, 0xce, 0x4e, 0x5c, 0x04, 0x3e, 0x3a, 0xbf, 0x81, 0xf2, + 0xf6, 0x64, 0x78, 0xec, 0xb8, 0x58, 0xa2, 0x1b, 0xe9, 0x9e, 0xaf, 0xa3, + 0x5b, 0xe3, 0x34, 0x38, 0x2d, 0x99, 0xd8, 0x9a, 0x30, 0x00, 0x04, 0x77, + 0x8d, 0x40, 0x06, 0x2b, 0xdd, 0x5f, 0x05, 0x84, 0x88, 0xee, 0x04, 0xaa, + 0xc1, 0x0f, 0x62, 0x17, 0x52, 0x1c, 0x4c, 0x99, 0x60, 0x00, 0xde, 0x15, + 0xa0, 0xfd, 0x50, 0xdb, 0x40, 0x0b, 0xbb, 0xae, 0xe4, 0x69, 0xd8, 0x63, + 0x42, 0xea, 0x5d, 0xd2, 0x25, 0xac, 0x93, 0x35, 0x18, 0xf0, 0xc8, 0x01, + 0xc0, 0x01, 0x0e, 0x34, 0x08, 0xac, 0x35, 0x75, 0x09, 0x5a, 0x63, 0x66, + 0x55, 0x76, 0xa1, 0xcb, 0xb2, 0x1a, 0x30, 0x04, 0x25, 0x20, 0x03, 0x0a, + 0x64, 0xec, 0x7c, 0x71, 0xb7, 0x22, 0x2b, 0x59, 0xbe, 0x73, 0xe8, 0xd2, + 0x66, 0xb5, 0x4b, 0x7f, 0xb6, 0x40, 0xe3, 0xe3, 0x35, 0xf2, 0x80, 0x0c, + 0xc9, 0x58, 0xbd, 0xa2, 0xc0, 0x44, 0x71, 0x18, 0x24, 0xe6, 0xf2, 0x82, + 0x7a, 0x08, 0xf6, 0x95, 0x57, 0x33, 0x22, 0x75, 0xab, 0x80, 0xa5, 0xda, + 0x39, 0x05, 0x52, 0x35, 0xeb, 0xdf, 0x46, 0x2e, 0x86, 0xf2, 0x4d, 0x70, + 0x54, 0x06, 0xe5, 0x77, 0x7a, 0xd7, 0x5f, 0x64, 0x02, 0x14, 0x57, 0x20, + 0x04, 0x0d, 0x80, 0x08, 0xbd, 0x6b, 0xfb, 0xfb, 0x23, 0x3d, 0x86, 0x11, + 0x75, 0x5c, 0x7f, 0x8a, 0xb5, 0x33, 0x2b, 0x23, 0x5d, 0x9f, 0xc7, 0x87, + 0xc7, 0xf7, 0x5a, 0x20, 0xb2, 0x77, 0xfe, 0xa9, 0xcb, 0x0c, 0xfd, 0xd8, + 0x04, 0xdc, 0x90, 0x4a, 0xa0, 0x0d, 0xe1, 0xba, 0x0c, 0xd5, 0x2f, 0x74, + 0xb2, 0xeb, 0xc6, 0x5c, 0xf1, 0x72, 0xc0, 0x3d, 0x0d, 0x65, 0x68, 0xaa, + 0x8b, 0x45, 0x4a, 0x19, 0x7f, 0x4c, 0xde, 0x25, 0x18, 0xc0, 0x1c, 0x01, + 0x12, 0x34, 0x08, 0x70, 0x96, 0x32, 0x12, 0x04, 0x82, 0x50, 0xa1, 0x0c, + 0x48, 0x51, 0x10, 0x56, 0x73, 0x8a, 0xda, 0x80, 0xbd, 0x8c, 0xeb, 0x08, + 0x55, 0x8b, 0x50, 0x99, 0x17, 0x16, 0xb5, 0xb3, 0x8f, 0x92, 0xfb, 0x62, + 0x53, 0x68, 0x3c, 0xa3, 0x9c, 0x76, 0x9a, 0x37, 0xd4, 0xe7, 0x19, 0x7b, + 0x88, 0xe7, 0xf4, 0x7c, 0x52, 0x56, 0x72, 0xa6, 0x2b, 0x2d, 0xdf, 0x04, + 0x07, 0x7b, 0xca, 0xaa, 0x43, 0x98, 0x40, 0xda, 0xd1, 0xc3, 0xb8, 0xf6, + 0xbc, 0x28, 0xcd, 0x25, 0x28, 0xa1, 0x14, 0xb2, 0x7e, 0xc8, 0xa5, 0xf4, + 0xbf, 0xdf, 0xbb, 0x20, 0xc9, 0x0f, 0xf1, 0x64, 0x7c, 0x6f, 0x82, 0x6e, + 0xe0, 0x94, 0xf3, 0x4a, 0xde, 0x15, 0xb0, 0xbf, 0x10, 0xbf, 0x47, 0x25, + 0xb1, 0x1d, 0x2a, 0xf6, 0xde, 0xc9, 0x91, 0x2c, 0xa7, 0x30, 0x2b, 0x85, + 0xfe, 0x4b, 0x96, 0xfa, 0x9b, 0x46, 0x66, 0xcc, 0x66, 0x9b, 0xc8, 0xff, + 0x95, 0xb2, 0xd9, 0xb6, 0xb2, 0x0b, 0xac, 0x82, 0x82, 0x06, 0x92, 0x68, + 0x15, 0x10, 0x57, 0x2c, 0x42, 0xce, 0x50, 0x66, 0xec, 0x8c, 0x39, 0x6c, + 0x7a, 0x53, 0xee, 0x11, 0xa6, 0xaa, 0xa0, 0x03, 0x78, 0x46, 0x83, 0xb5, + 0x66, 0x46, 0xc0, 0xad, 0x77, 0x8e, 0xa8, 0x60, 0xdc, 0x34, 0x6a, 0x7f, + 0x6b, 0x0f, 0xce, 0xf3, 0x59, 0x8d, 0x82, 0x47, 0x01, 0x12, 0x34, 0x08, + 0x8c, 0x79, 0x22, 0x2d, 0xc2, 0x83, 0x22, 0x20, 0x48, 0x80, 0x16, 0x29, + 0xdd, 0xe5, 0x4d, 0xb6, 0x0b, 0x66, 0xe1, 0x64, 0x2c, 0x17, 0x60, 0xe9, + 0xb2, 0xac, 0xb8, 0x15, 0x74, 0x4c, 0x95, 0xc4, 0xaf, 0x9d, 0x33, 0x74, + 0x7f, 0xec, 0x71, 0x32, 0x9a, 0x5b, 0x5a, 0xae, 0xfe, 0x29, 0xe0, 0xf3, + 0x39, 0x80, 0x8d, 0x03, 0xdf, 0x4b, 0x8c, 0xb6, 0xba, 0xc0, 0xf5, 0x52, + 0xb3, 0x0e, 0xda, 0xf8, 0x3f, 0xad, 0xd5, 0xda, 0x2c, 0xf0, 0x7c, 0xf4, + 0xe8, 0x9f, 0xb4, 0xed, 0xb6, 0x58, 0x17, 0xfa, 0x79, 0xd5, 0x9d, 0x54, + 0x94, 0xb4, 0xee, 0x78, 0x8e, 0xd8, 0x69, 0x0b, 0x40, 0x91, 0x1d, 0x10, + 0x99, 0x4d, 0xb0, 0x8d, 0xd8, 0x32, 0x34, 0xe2, 0xb6, 0x15, 0x64, 0x0f, + 0x5b, 0x19, 0x38, 0xd7, 0xee, 0xbd, 0x77, 0x31, 0x07, 0xae, 0xb5, 0xe3, + 0xda, 0x24, 0x21, 0x7f, 0x90, 0x62, 0xc2, 0x0f, 0x27, 0x22, 0x49, 0xc2, + 0xbf, 0xe5, 0x1e, 0x74, 0xf8, 0x06, 0xfe, 0xf0, 0x2c, 0x0d, 0xe1, 0xfa, + 0x0d, 0xd5, 0x29, 0x19, 0x3b, 0xbc, 0xd9, 0xb3, 0x37, 0xb0, 0x0f, 0x80, + 0xd9, 0x91, 0x5c, 0x9b, 0x16, 0x35, 0x9b, 0xcb, 0x62, 0xc7, 0xda, 0x6a, + 0x9b, 0x25, 0x97, 0xa0, 0x1c, 0x01, 0x0c, 0x34, 0x00, 0x30, 0xe4, 0x33, + 0x89, 0x04, 0xe2, 0x42, 0x10, 0xcc, 0x24, 0x40, 0x0b, 0x75, 0x95, 0x5d, + 0xa8, 0x98, 0x06, 0xc4, 0x2c, 0x21, 0x60, 0x19, 0x04, 0x54, 0x02, 0x03, + 0x06, 0xd5, 0x6f, 0xc6, 0x40, 0x10, 0xa0, 0x32, 0x14, 0xe5, 0x43, 0x9a, + 0xa0, 0x00, 0x2e, 0x56, 0x12, 0xdf, 0xbf, 0xa1, 0xab, 0x56, 0xda, 0x71, + 0x49, 0xbf, 0x0a, 0x79, 0x53, 0x64, 0x33, 0x2b, 0x63, 0x53, 0x62, 0xcd, + 0x6d, 0x0f, 0x6e, 0xf0, 0x3e, 0x9e, 0x27, 0x42, 0x67, 0x00, 0x97, 0x4f, + 0xa6, 0x4b, 0x19, 0x15, 0xf8, 0x4e, 0x87, 0xbc, 0xad, 0x61, 0x9a, 0x45, + 0xd0, 0xa8, 0x5e, 0xe8, 0xb7, 0x81, 0x56, 0xc7, 0xae, 0x88, 0x08, 0xc8, + 0xe6, 0xd5, 0xe2, 0xbc, 0x14, 0x67, 0x13, 0x4e, 0xc3, 0xb2, 0xe9, 0xa3, + 0xbc, 0xf8, 0x78, 0x62, 0xe2, 0x10, 0x45, 0xe9, 0xd7, 0xf4, 0xec, 0xec, + 0xc1, 0x74, 0x29, 0x86, 0x50, 0x43, 0x2e, 0x14, 0x32, 0xe6, 0x40, 0xa0, + 0xe2, 0xfc, 0x8e, 0x65, 0x03, 0x83, 0xef, 0x09, 0xd0, 0x76, 0xad, 0x58, + 0xf6, 0x41, 0x3e, 0xe0, 0xb9, 0xd8, 0x06, 0xe1, 0x4b, 0x8b, 0x49, 0x59, + 0x4b, 0x11, 0x64, 0xa2, 0x93, 0x1a, 0x80, 0xe0, 0x01, 0x0a, 0x34, 0x08, + 0x90, 0xb5, 0x3b, 0x0d, 0x50, 0x27, 0x7c, 0xc2, 0x98, 0xae, 0xca, 0x05, + 0xf2, 0x2c, 0x41, 0x64, 0x4a, 0xd8, 0x23, 0x97, 0x03, 0xe1, 0x9e, 0xda, + 0x10, 0x55, 0x56, 0x8a, 0xa6, 0x88, 0x5f, 0x74, 0x03, 0x7d, 0xe8, 0x42, + 0x4b, 0x91, 0xf1, 0x00, 0x92, 0x74, 0x79, 0xca, 0xcc, 0xcc, 0xe2, 0x32, + 0xf2, 0xff, 0x33, 0x31, 0x65, 0xe1, 0x45, 0xbe, 0x49, 0x21, 0x2d, 0x3d, + 0x71, 0x36, 0x02, 0xc6, 0x46, 0x6d, 0x5a, 0x99, 0xea, 0xd1, 0x15, 0x15, + 0xe9, 0xaa, 0xaa, 0x12, 0x7b, 0xfb, 0x7a, 0xf0, 0xa9, 0xb5, 0x1a, 0xa8, + 0xe0, 0x17, 0x70, 0xa1, 0x28, 0x22, 0xe7, 0x7b, 0xb2, 0x80, 0x17, 0x7d, + 0x37, 0x6c, 0x67, 0x49, 0x8e, 0xf2, 0xc7, 0x3f, 0x16, 0xeb, 0xd3, 0x54, + 0xad, 0xf9, 0x28, 0xc5, 0x23, 0x3d, 0x7a, 0x7a, 0xf4, 0xa1, 0x92, 0xb3, + 0x90, 0x96, 0x28, 0x9a, 0x65, 0x8b, 0x6f, 0x9e, 0xb7, 0xa4, 0xc0, 0x2f, + 0x30, 0x06, 0xf0, 0xed, 0x06, 0xea, 0x93, 0xa6, 0x37, 0xeb, 0xbf, 0x03, + 0x6d, 0xb6, 0xf6, 0x46, 0x3d, 0x0b, 0x53, 0x2e, 0x6e, 0x4e, 0xe2, 0xc5, + 0x4d, 0x8f, 0xec, 0xdd, 0x18, 0x02, 0xf4, 0x0e, 0x01, 0x0e, 0x34, 0x19, + 0xd0, 0x16, 0x18, 0x8d, 0x8c, 0x85, 0x34, 0x29, 0x59, 0xdd, 0x33, 0x18, + 0xde, 0x5d, 0xd3, 0x54, 0x02, 0x22, 0xac, 0xcb, 0x60, 0x20, 0x04, 0x47, + 0x1e, 0x0b, 0x82, 0xe5, 0x59, 0x08, 0x49, 0x23, 0x4e, 0xc9, 0x76, 0xf7, + 0x07, 0x08, 0x27, 0xc1, 0xd1, 0x2d, 0x8c, 0x77, 0x2a, 0x98, 0xd9, 0x62, + 0x8b, 0xea, 0x50, 0x56, 0x32, 0xaf, 0xde, 0x4f, 0x68, 0x01, 0xac, 0xb5, + 0xd9, 0xd0, 0x6a, 0xb6, 0xca, 0x77, 0x1b, 0x00, 0xde, 0x0a, 0xac, 0x06, + 0x6a, 0xaa, 0x3d, 0x6f, 0x04, 0x51, 0x10, 0x1e, 0x1d, 0xb1, 0xad, 0xe4, + 0xa1, 0x06, 0x96, 0x6e, 0xcb, 0x82, 0x8c, 0xb1, 0x2e, 0x50, 0x2b, 0xfb, + 0x81, 0x89, 0xe2, 0x26, 0xa7, 0x5b, 0x89, 0x1c, 0xc5, 0x77, 0xdb, 0x35, + 0x87, 0x61, 0x83, 0x26, 0x1e, 0x55, 0x18, 0x97, 0x09, 0x4f, 0x66, 0x05, + 0x80, 0x74, 0xea, 0x96, 0x3b, 0x94, 0xa6, 0xc2, 0x60, 0x0a, 0x2f, 0x10, + 0x37, 0x85, 0xe8, 0x3b, 0x57, 0x62, 0x6d, 0xa4, 0x56, 0xa1, 0xca, 0xc0, + 0x3d, 0x0f, 0xde, 0x87, 0x8b, 0xf9, 0x86, 0x6d, 0xd5, 0x72, 0xfd, 0xe9, + 0x92, 0xb5, 0x00, 0x70, 0x01, 0x14, 0x34, 0x19, 0xe8, 0x76, 0x12, 0x28, + 0xcc, 0x39, 0xa5, 0x51, 0xcf, 0x2c, 0xb1, 0x66, 0xd2, 0xb8, 0x40, 0x42, + 0x2c, 0x08, 0xc0, 0x81, 0x01, 0xa2, 0x36, 0x1e, 0x20, 0x35, 0x80, 0x7f, + 0xff, 0x68, 0xfc, 0x0f, 0xec, 0x8a, 0x06, 0x64, 0x14, 0x22, 0xd9, 0xe1, + 0xf4, 0x8b, 0x78, 0x28, 0x40, 0x7b, 0xdd, 0xe4, 0x93, 0x8d, 0x26, 0x64, + 0x6f, 0x27, 0x51, 0x48, 0xb6, 0xbd, 0xe4, 0xca, 0xd3, 0x23, 0x86, 0x35, + 0x8c, 0x25, 0xca, 0xb5, 0xfd, 0x27, 0xea, 0x29, 0x92, 0x6d, 0x34, 0x21, + 0x97, 0x98, 0x9c, 0x1f, 0x8e, 0x0a, 0xb9, 0xae, 0xbf, 0xbf, 0xdc, 0xcf, + 0x88, 0xa0, 0xaf, 0xec, 0x64, 0x48, 0x38, 0xea, 0xdd, 0xf3, 0x4b, 0x92, + 0x6b, 0x28, 0xc8, 0xe7, 0x6d, 0xaa, 0x43, 0x15, 0x5a, 0xad, 0xff, 0x56, + 0x09, 0xec, 0xa5, 0xdf, 0xd7, 0xd6, 0xbb, 0x98, 0x8d, 0xef, 0x1d, 0xb6, + 0x1e, 0x8b, 0x09, 0xd7, 0x83, 0x63, 0xa6, 0x62, 0x9e, 0x4c, 0x1c, 0x9b, + 0xb9, 0xaa, 0x1c, 0x6a, 0xcf, 0x99, 0x10, 0x37, 0x85, 0xe8, 0x11, 0x52, + 0x92, 0x8d, 0x5b, 0x72, 0x1c, 0x7c, 0x02, 0x15, 0xb9, 0x3b, 0xf4, 0xff, + 0x42, 0x2e, 0x54, 0x91, 0xd1, 0xbe, 0x2c, 0x6b, 0x2c, 0x00, 0x70 +}; + +static const guint BBB_32k_1_mp4_len = 8423; diff --git a/tests/check/elements/qtmux.c b/tests/check/elements/qtmux.c index 642b409f1f..aecde5928b 100644 --- a/tests/check/elements/qtmux.c +++ b/tests/check/elements/qtmux.c @@ -182,13 +182,13 @@ qtmux_sinkpad_query (GstPad * pad, GstObject * parent, GstQuery * query) } static GstElement * -setup_qtmux (GstStaticPadTemplate * srctemplate, const gchar * sinkname, - gboolean seekable) +setup_qtmux (const gchar * element, GstStaticPadTemplate * srctemplate, + const gchar * sinkname, gboolean seekable) { GstElement *qtmux; GST_DEBUG ("setup_qtmux"); - qtmux = gst_check_setup_element ("qtmux"); + qtmux = gst_check_setup_element (element); mysrcpad = setup_src_pad (qtmux, srctemplate, sinkname); mysinkpad = gst_check_setup_sink_pad (qtmux, &sinktemplate); @@ -228,7 +228,7 @@ check_qtmux_pad (GstStaticPadTemplate * srctemplate, const gchar * sinkname, guint8 data2[4] = "moov"; GstSegment segment; - qtmux = setup_qtmux (srctemplate, sinkname, TRUE); + qtmux = setup_qtmux ("qtmux", srctemplate, sinkname, TRUE); g_object_set (qtmux, "dts-method", dts_method, NULL); fail_unless (gst_element_set_state (qtmux, GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, @@ -311,14 +311,14 @@ check_qtmux_pad_fragmented (GstStaticPadTemplate * srctemplate, GstCaps *caps; int num_buffers; int i; - guint8 data0[12] = "\000\000\000\024ftypqt "; + guint8 data0[12] = "\000\000\000\040ftypmp42"; guint8 data1[4] = "mdat"; guint8 data2[4] = "moov"; guint8 data3[4] = "moof"; guint8 data4[4] = "mfra"; GstSegment segment; - qtmux = setup_qtmux (srctemplate, sinkname, !streamable); + qtmux = setup_qtmux ("mp4mux", srctemplate, sinkname, !streamable); g_object_set (qtmux, "dts-method", dts_method, NULL); g_object_set (qtmux, "fragment-duration", 2000, NULL); g_object_set (qtmux, "streamable", streamable, NULL); @@ -548,7 +548,8 @@ GST_END_TEST; GST_START_TEST (test_reuse) { - GstElement *qtmux = setup_qtmux (&srcvideotemplate, "video_%u", TRUE); + GstElement *qtmux = + setup_qtmux ("qtmux", &srcvideotemplate, "video_%u", TRUE); GstBuffer *inbuffer; GstCaps *caps; GstSegment segment; diff --git a/tests/examples/v4l2/Makefile.am b/tests/examples/v4l2/Makefile.am index 1fb4165d0f..945f5d8f13 100644 --- a/tests/examples/v4l2/Makefile.am +++ b/tests/examples/v4l2/Makefile.am @@ -1,4 +1,4 @@ -noinst_PROGRAMS = camctrl v4l2src-renegotiate +noinst_PROGRAMS = camctrl v4l2src-renegotiate v4l2scalersrc-negotiate-dmabuf camctrl_SOURCES = camctrl.c camctrl_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CONTROLLER_CFLAGS) $(GST_CFLAGS) @@ -7,3 +7,7 @@ camctrl_LDADD = $(GST_BASE_LIBS) $(GST_CONTROLLER_LIBS) $(GST_LIBS) v4l2src_renegotiate_SOURCES = v4l2src-renegotiate.c v4l2src_renegotiate_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) v4l2src_renegotiate_LDADD = $(GST_BASE_LIBS) $(GST_LIBS) + +v4l2scalersrc_negotiate_dmabuf_SOURCES = v4l2scalersrc-negotiate-dmabuf.c +v4l2scalersrc_negotiate_dmabuf_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) +v4l2scalersrc_negotiate_dmabuf_LDADD = $(GST_BASE_LIBS) $(GST_LIBS) diff --git a/tests/examples/v4l2/meson.build b/tests/examples/v4l2/meson.build index d59d000e3e..9e6b702dc5 100644 --- a/tests/examples/v4l2/meson.build +++ b/tests/examples/v4l2/meson.build @@ -9,3 +9,9 @@ executable('v4l2src-renegotiate', 'v4l2src-renegotiate.c', c_args : gst_plugins_good_args, include_directories : [configinc], install: false) + +executable('v4l2scalersrc-negotiate-dmabuf', 'v4l2scalersrc-negotiate-dmabuf.c', + dependencies: [gstbase_dep, gstallocators_dep, gst_dep], + c_args : gst_plugins_good_args, + include_directories : [configinc], + install: false) diff --git a/tests/examples/v4l2/v4l2scalersrc-negotiate-dmabuf.c b/tests/examples/v4l2/v4l2scalersrc-negotiate-dmabuf.c new file mode 100644 index 0000000000..8e6320143b --- /dev/null +++ b/tests/examples/v4l2/v4l2scalersrc-negotiate-dmabuf.c @@ -0,0 +1,152 @@ +/* GStreamer + * + * Copyright (C) 2019 LG Electronics. All rights reserved. + * Author: Wonchul Lee + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +static const gchar *device = "/dev/video70"; + +static GOptionEntry entries[] = { + {"device", 'd', 0, G_OPTION_ARG_STRING, &device, "V4L2 Device number", + NULL}, + {NULL} +}; + +static gboolean +bus_callback (GstBus * bus, GstMessage * message, gpointer data) +{ + switch (message->type) { + case GST_MESSAGE_EOS: + break; + case GST_MESSAGE_ERROR:{ + GError *gerror; + gchar *debug; + + gst_message_parse_error (message, &gerror, &debug); + gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug); + g_error_free (gerror); + g_free (debug); + break; + } + default: + break; + } + + return TRUE; +} + +gint +main (gint argc, gchar ** argv) +{ + GstBus *bus; + GError *error = NULL; + GOptionContext *context; + gchar *desc; + gboolean ret, check_dmabuf = FALSE; + GstElement *pipeline, *sink, *src; + GstPad *sinkpad, *srcpad; + GstCaps *caps_sink, *caps_src, *result; + GstCapsFeatures *features; + guint i, n; + + context = g_option_context_new ("- test v4l2scalersrc negotite dmabuf with " + "waylandsink"); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gst_init_get_option_group ()); + ret = g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + + if (!ret) { + g_print ("option parsing failed: %s\n", error->message); + g_error_free (error); + return 1; + } + + desc = + g_strdup_printf + ("v4l2scalersrc name=src device=\"%s\" io-mode=\"dmabuf\" " + "! waylandsink name=sink", device); + pipeline = gst_parse_launch (desc, &error); + g_free (desc); + + if (!pipeline) { + g_print ("failed to create pipeline: %s", error->message); + g_error_free (error); + return 1; + } + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_watch (bus, bus_callback, NULL); + gst_object_unref (bus); + + gst_element_set_state (pipeline, GST_STATE_READY); + + if (gst_element_get_state (pipeline, NULL, NULL, 3 * GST_SECOND) + == GST_STATE_CHANGE_FAILURE) { + g_print ("failed to change pipeline state to READY\n"); + return -1; + } + + src = gst_bin_get_by_name (GST_BIN (pipeline), "src"); + sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink"); + sinkpad = gst_element_get_static_pad (sink, "sink"); + srcpad = gst_element_get_static_pad (src, "src"); + + caps_sink = gst_pad_query_caps (sinkpad, NULL); + caps_src = gst_pad_query_caps (srcpad, NULL); + result = gst_caps_intersect (caps_sink, caps_src); + + if (result) { + n = gst_caps_get_size (result); + for (i = 0; i < n; ++i) { + features = gst_caps_get_features (result, i); + if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) { + check_dmabuf = TRUE; + break; + } + } + gst_caps_unref (result); + } + + gst_caps_unref (caps_sink); + gst_caps_unref (caps_src); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + gst_object_unref (sink); + gst_object_unref (src); + + /* stop and cleanup */ + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (GST_OBJECT (pipeline)); + + if (!check_dmabuf) { + g_print ("test failed, failed to use dmabuf\n"); + return -1; + } + + g_print ("test success\n"); + + return 0; +}