diff --git a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java index 16b39fba95b..793c7b2f0f5 100644 --- a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java +++ b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java @@ -27,6 +27,7 @@ import com.sun.media.jfxmedia.MediaError; import com.sun.media.jfxmedia.MediaException; import com.sun.media.jfxmediaimpl.MediaUtils; +import com.sun.javafx.PlatformUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -73,6 +74,7 @@ final class HLSConnectionHolder extends ConnectionHolder { // Seek will set this value and HLS_PROP_SEGMENT_START_TIME // should return it if set. private int segmentStartTimeAfterSeek = -1; + private static final boolean SEND_HEADER_WITH_EACH_SEGMENT = PlatformUtil.isWindows(); static final long HLS_VALUE_FLOAT_MULTIPLIER = 1000; static final int HLS_PROP_GET_DURATION = 1; static final int HLS_PROP_GET_HLS_MODE = 2; @@ -368,7 +370,9 @@ private int loadNextSegment() { } catch (IOException | URISyntaxException e) { return -1; } - sendHeader = false; + if (!SEND_HEADER_WITH_EACH_SEGMENT) { + sendHeader = false; + } } mediaFile = currentPlaylist.getNextMediaFile(); diff --git a/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c b/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c index 787642fcc06..b645182849d 100644 --- a/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c +++ b/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,8 +41,7 @@ gboolean lite_plugins_init (GstPlugin * plugin) !plugin_init_wavparse(plugin) || !plugin_init_aiff(plugin) || !plugin_init_app(plugin) || - !plugin_init_audioparsers(plugin) || - !plugin_init_qtdemux(plugin)) + !plugin_init_audioparsers(plugin)) return FALSE; #ifdef WIN32 @@ -59,7 +58,8 @@ gboolean lite_plugins_init (GstPlugin * plugin) #ifdef LINUX if (!plugin_init_audiofx(plugin) || !plugin_init_alsa(plugin) || - !plugin_init_volume(plugin)) + !plugin_init_volume(plugin) || + !plugin_init_qtdemux(plugin)) return FALSE; #endif diff --git a/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h b/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h index e0d87270ddb..644419a63b9 100644 --- a/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h +++ b/modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/projects/plugins/gstplugins-lite.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,6 @@ gboolean plugin_init_wavparse (GstPlugin * plugin); gboolean plugin_init_aiff (GstPlugin * plugin); gboolean plugin_init_app (GstPlugin * plugin); gboolean plugin_init_audioparsers (GstPlugin * plugin); -gboolean plugin_init_qtdemux (GstPlugin * plugin); #ifdef WIN32 gboolean plugin_init_directsound (GstPlugin * plugin); @@ -55,6 +54,7 @@ gboolean plugin_init_osxaudio (GstPlugin * plugin); gboolean plugin_init_audiofx (GstPlugin * plugin); gboolean plugin_init_alsa (GstPlugin * plugin); gboolean plugin_init_volume (GstPlugin * plugin); +gboolean plugin_init_qtdemux (GstPlugin * plugin); #endif G_END_DECLS diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/av/videodecoder.c b/modules/javafx.media/src/main/native/gstreamer/plugins/av/videodecoder.c index 2c005b3b539..61597f842c1 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/av/videodecoder.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/av/videodecoder.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -208,7 +208,7 @@ static gboolean videodecoder_is_decoder_by_codec_id_supported(gint codec_id) { switch(codec_id) { - case JFX_CODEC_ID_H265: + case JFX_CODEC_ID_HEVC: #if HEVC_SUPPORT return TRUE; #else // HEVC_SUPPORT diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/dshowwrapper/dshowwrapper.cpp b/modules/javafx.media/src/main/native/gstreamer/plugins/dshowwrapper/dshowwrapper.cpp index 8320cb9954b..b6f4ea877ec 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/dshowwrapper/dshowwrapper.cpp +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/dshowwrapper/dshowwrapper.cpp @@ -835,7 +835,6 @@ int dshowwrapper_deliver(GstBuffer *pBuffer, sUserData *pUserData) g_print("JFXMEDIA OUTPUT AAC -1\n"); } #endif - // Set output buffer to NULL before delivering it, otherwise flush stop can release it right after // we finish delivery. pBufferOut = decoder->out_buffer[pUserData->output_index]; @@ -2967,7 +2966,6 @@ static GstFlowReturn dshowwrapper_chain (GstPad * pad, GstObject *parent, GstBuf g_print("JFXMEDIA INPUT AAC -1\n"); } #endif - if (GST_BUFFER_TIMESTAMP_IS_VALID(buf)) { decoder->enable_pts = TRUE; diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins.c b/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins.c index f111a7c6954..d42cac48f7e 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ #if defined(WIN32) gboolean dshowwrapper_init(GstPlugin* dshowwrapper); gboolean mfwrapper_init(GstPlugin* mfwrapper); +gboolean mfdemux_init(GstPlugin* mfdemux); #endif #ifdef STATIC_BUILD @@ -47,6 +48,7 @@ static gboolean fxplugins_init (GstPlugin * plugin) #if defined(WIN32) dshowwrapper_init(plugin) && mfwrapper_init(plugin) && + mfdemux_init(plugin) && #endif // WIN32 progress_buffer_plugin_init(plugin); } diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins_common.h b/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins_common.h index 2690a4d938b..d13707f4940 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins_common.h +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/fxplugins_common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,9 @@ G_BEGIN_DECLS // Custom events enum { - FX_EVENT_RANGE_READY = GST_EVENT_MAKE_TYPE (64, GST_EVENT_TYPE_DOWNSTREAM | GST_EVENT_TYPE_SERIALIZED) + FX_EVENT_RANGE_READY = GST_EVENT_MAKE_TYPE (64, GST_EVENT_TYPE_DOWNSTREAM | GST_EVENT_TYPE_SERIALIZED), + FX_EVENT_SEGMENT_READY = GST_EVENT_MAKE_TYPE (65, GST_EVENT_TYPE_DOWNSTREAM | GST_EVENT_TYPE_SERIALIZED), + FX_EVENT_NEXT_SEGMENT = GST_EVENT_MAKE_TYPE (66, GST_EVENT_TYPE_UPSTREAM | GST_EVENT_TYPE_SERIALIZED) }; // Query to find out if a sinkpad supports progressive getrange @@ -46,9 +48,9 @@ enum JFX_CODEC_ID { JFX_CODEC_ID_UNKNOWN = 0, JFX_CODEC_ID_AAC, - JFX_CODEC_ID_H264, // HLS - JFX_CODEC_ID_AVC1, // MP4 - JFX_CODEC_ID_H265, // MP4 + JFX_CODEC_ID_H264, // HLS (H.264 with start codes) + JFX_CODEC_ID_AVC1, // MP4 (H.264 without start codes) + JFX_CODEC_ID_HEVC, // H.265 with start codes }; // Custom error codes used by our plugins diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/javasource/javasource.c b/modules/javafx.media/src/main/native/gstreamer/plugins/javasource/javasource.c index 429168f1dad..72256f9af07 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/javasource/javasource.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/javasource/javasource.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,7 +98,6 @@ struct _JavaSource // Seek helper fields gboolean is_seekable; // property controlled gboolean is_random_access; // property controlled - gboolean update; gboolean discont; guint mode; // property controlled and/or internally @@ -485,7 +484,13 @@ static gboolean java_source_perform_seek(JavaSource *element, GstPad *pad, GstEv element->position_time = 0; } element->discont = TRUE; - element->update = FALSE; + result = TRUE; + } + else if ((element->mode & MODE_HLS) == MODE_HLS && new_position == -1) + { + element->pending_event = GST_EVENT_EOS; + element->position = 0; + element->discont = TRUE; result = TRUE; } @@ -543,7 +548,7 @@ static void java_source_loop(void *user_data) next_event: switch (element->pending_event) { - case GST_EVENT_STREAM_START: + case GST_EVENT_STREAM_START: { gchar *stream_id; GstEvent *event; @@ -554,6 +559,42 @@ static void java_source_loop(void *user_data) result = gst_pad_push_event (element->srcpad, event) ? GST_FLOW_OK : GST_FLOW_FLUSHING; g_free (stream_id); + element->pending_event = GST_EVENT_CAPS; + break; + } + + case GST_EVENT_CAPS: + { + // Set caps to mimetype if provided, we need to do this before pushing buffer, + // so downstream filters can configure itself correctly. Caps are set via event in GStreamer 1.0. + if (element->mimetype) + { + GstCaps *caps = NULL; + GstEvent *caps_event = NULL; + // Special case for HLS AAC elementary stream. + // It has "audio/aac" mimetype which is same as + // "audio/mpeg" mpegversion=4. We changing it here + // so downstream will undestand it. + if (strstr(element->mimetype, "audio/aac") != NULL) + { + caps = gst_caps_new_simple("audio/mpeg", + "mpegversion", G_TYPE_INT, 4, + "stream-format", G_TYPE_STRING, "adts", + NULL); + } + else + { + caps = gst_caps_new_simple(element->mimetype, NULL, NULL); + } + + caps_event = gst_event_new_caps(caps); + if (caps_event) + gst_pad_push_event(element->srcpad, caps_event); + gst_caps_unref(caps); + g_free(element->mimetype); + element->mimetype = NULL; + } + element->pending_event = GST_EVENT_SEGMENT; break; } @@ -593,8 +634,6 @@ static void java_source_loop(void *user_data) } gst_segment_init (&segment, GST_FORMAT_BYTES); - if (element->update) - segment.flags |= GST_SEGMENT_FLAG_UPDATE; segment.rate = element->rate; segment.start = ((gint64)start_time*GST_SECOND) / HLS_VALUE_FLOAT_MULTIPLIER; segment.stop = result; @@ -605,8 +644,6 @@ static void java_source_loop(void *user_data) else { gst_segment_init (&segment, GST_FORMAT_BYTES); - if (element->update) - segment.flags |= GST_SEGMENT_FLAG_UPDATE; segment.rate = element->rate; segment.start = element->position; segment.stop = element->size; @@ -620,6 +657,7 @@ static void java_source_loop(void *user_data) case GST_EVENT_EOS: gst_pad_push_event (element->srcpad, gst_event_new_eos()); + element->pending_event = GST_EVENT_UNKNOWN; result = GST_FLOW_EOS; break; @@ -652,29 +690,6 @@ static void java_source_loop(void *user_data) element->discont = FALSE; } - // Set caps to mimetype if provided, we need to do this before pushing buffer, - // so downstream filters can configure itself correctly. Caps are set via event in GStreamer 1.0. - if (element->mimetype) - { - GstCaps *caps = NULL; - GstEvent *caps_event = NULL; - // Special case for HLS AAC elementary stream. - // It has "audio/aac" mimetype which is same as - // "audio/mpeg" mpegversion=4. We changing it here - // so downstream will undestand it. - if (strstr(element->mimetype, "audio/aac") != NULL) - caps = gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 4, NULL); - else - caps = gst_caps_new_simple(element->mimetype, NULL, NULL); - - caps_event = gst_event_new_caps(caps); - if (caps_event) - gst_pad_push_event(element->srcpad, caps_event); - gst_caps_unref(caps); - g_free(element->mimetype); - element->mimetype = NULL; - } - result = gst_pad_push(element->srcpad, buffer); if (element->pending_event != GST_EVENT_SEGMENT) @@ -886,10 +901,6 @@ static GstStateChangeReturn java_source_change_state (GstElement *e, element->position = 0; element->position_time = 0; element->discont = FALSE; - if ((element->mode & MODE_HLS) == MODE_HLS) - element->update = FALSE; - else - element->update = TRUE; GST_PAD_STREAM_UNLOCK(element->srcpad); g_mutex_lock(&element->lock); diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.cpp b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.cpp new file mode 100644 index 00000000000..85d300a5494 --- /dev/null +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.cpp @@ -0,0 +1,1728 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include +#include + +#include + +#include "mfdemux.h" +#include "mftrace.h" + +using namespace std; + +#define MAX_CODEC_DATA_SIZE 256 + +enum +{ + PROP_0, + PROP_HLS_MODE +}; + +GST_DEBUG_CATEGORY_STATIC(gst_mfdemux_debug); +#define GST_CAT_DEFAULT gst_mfdemux_debug + +// The input capabilities +static GstStaticPadTemplate sink_factory = +GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "video/mp4;" + "video/quicktime;" + "audio/x-m4a;" + "video/x-m4v" + )); + +// The output capabilities +static GstStaticPadTemplate src_video_factory = +GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_audio_factory = +GST_STATIC_PAD_TEMPLATE ("audio", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +// Forward declarations +static void gst_mfdemux_dispose(GObject *object); +static void mfdemux_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *spec); + +static GstFlowReturn mfdemux_chain(GstPad *pad, GstObject *parent, GstBuffer *buf); +static void mfdemux_loop(GstPad *pad); +static void mfdemux_reload_demux(GstMFDemux *demux, gboolean bSeek); + +static gboolean mfdemux_sink_event(GstPad *pad, GstObject *parent, GstEvent *event); + +static gboolean mfdemux_src_query (GstPad *pad, GstObject *parent, GstQuery *query); +static gboolean mfdemux_src_event (GstPad *pad, GstObject *parent, GstEvent *event); + +static gboolean mfdemux_activate(GstPad *pad, GstObject *parent); +static gboolean mfdemux_activate_mode(GstPad *pad, GstObject *parent, + GstPadMode mode, gboolean active); + +//static HRESULT mfdemux_load_demux(GstMFDemux *demux, GstCaps *caps); + +template void SafeRelease(T **ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + +/*********************************************************************************** +* Substitution for +* G_DEFINE_TYPE (GstMFDemux, gst_mfdemux, GstElement, GST_TYPE_ELEMENT); +***********************************************************************************/ +#define gst_mfdemux_parent_class parent_class +static void gst_mfdemux_init(GstMFDemux *self); +static void gst_mfdemux_class_init(GstMFDemuxClass *klass); +static gpointer gst_mfdemux_parent_class = NULL; +static void gst_mfdemux_class_intern_init(gpointer klass) +{ + gst_mfdemux_parent_class = g_type_class_peek_parent(klass); + gst_mfdemux_class_init((GstMFDemuxClass*)klass); +} + +GType gst_mfdemux_get_type(void) +{ + static volatile gsize gonce_data = 0; + // INLINE - g_once_init_enter() + if (g_once_init_enter(&gonce_data)) + { + GType _type; + _type = g_type_register_static_simple(GST_TYPE_ELEMENT, + g_intern_static_string("GstMFDemux"), + sizeof(GstMFDemuxClass), + (GClassInitFunc)gst_mfdemux_class_intern_init, + sizeof(GstMFDemux), + (GInstanceInitFunc)gst_mfdemux_init, + (GTypeFlags)0); + g_once_init_leave(&gonce_data, (gsize)_type); + } + return (GType)gonce_data; +} + +// Initialize mfdemux's class. +static void gst_mfdemux_class_init(GstMFDemuxClass *klass) +{ + GstElementClass *element_class = (GstElementClass*)klass; + GObjectClass *gobject_class = (GObjectClass*)klass; + + gobject_class->set_property = mfdemux_set_property; + + gst_element_class_set_metadata(element_class, + "MFDemux", + "Codec/Decoder/Audio/Video", + "Media Foundation Demux", + "Oracle Corporation"); + + g_object_class_install_property (gobject_class, PROP_HLS_MODE, + g_param_spec_boolean ("hls-mode", "HLS Mode", "HTTP Live Streaming Mode", FALSE, + static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS))); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&src_video_factory)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&src_audio_factory)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sink_factory)); + + gobject_class->dispose = gst_mfdemux_dispose; +} + +// Initialize the new element +// Instantiate pads and add them to element +// Set pad calback functions +// Initialize instance structure +static void gst_mfdemux_init(GstMFDemux *demux) +{ + g_mutex_init(&demux->lock); + + demux->src_result = GST_FLOW_OK; + + // Input + demux->sink_pad = gst_pad_new_from_static_template(&sink_factory, "sink"); + gst_pad_set_chain_function(demux->sink_pad, mfdemux_chain); + gst_pad_set_event_function(demux->sink_pad, mfdemux_sink_event); + gst_pad_set_activate_function(demux->sink_pad, mfdemux_activate); + gst_pad_set_activatemode_function(demux->sink_pad, mfdemux_activate_mode); + gst_element_add_pad(GST_ELEMENT(demux), demux->sink_pad); + + // Output + // Output pads will be created dynamically + + demux->is_flushing = FALSE; + demux->is_eos = FALSE; + demux->is_demux_initialized = FALSE; + demux->force_discontinuity = FALSE; + demux->send_new_segment = FALSE; + demux->start_task_on_first_segment = FALSE; + demux->is_hls = FALSE; +#if TRACE_ENABLE + demux->log_first_audio_pts = TRUE; + demux->log_first_video_pts = TRUE; + demux->last_audio_pts = 0; + demux->last_audio_dur = 0; + demux->last_video_pts = 0; + demux->last_video_dur = 0; +#endif + + demux->rate = 1.0; + demux->seek_position = 0; + + demux->pGSTMFByteStream = NULL; + demux->pIMFByteStream = NULL; + demux->pSourceReader = NULL; + + demux->llDuration = -1; + + // Init audio format with some defaults + demux->audioFormat.codecID = JFX_CODEC_ID_UNKNOWN; + demux->audioFormat.uiChannels = 2; + demux->audioFormat.uiRate = 48000; + demux->audioFormat.codec_data = NULL; + + // Init video format with some defaults + demux->videoFormat.codecID = JFX_CODEC_ID_UNKNOWN; + demux->videoFormat.uiWidth = 3840; + demux->videoFormat.uiHeight = 2160; + demux->videoFormat.uiFrameRateNum = 0; + demux->videoFormat.uiFrameRateDen = 0; + demux->videoFormat.uiInterlaceMode = MFVideoInterlace_Unknown; + demux->videoFormat.uiPixelAspectRatioNum = 0; + demux->videoFormat.uiPixelAspectRatioDen = 0; + demux->videoFormat.uiMPEG2Profile = 0; + demux->videoFormat.uiMPEG2Level = 0; + demux->videoFormat.sequence_header = NULL; + + demux->audio_src_pad = NULL; + demux->video_src_pad = NULL; + + demux->audio_stream_index = -1; + demux->video_stream_index = -1; + + demux->cached_segment_event = NULL; + + // Initialize Media Foundation + bool bCallCoUninitialize = true; + + if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))) + bCallCoUninitialize = false; + + demux->hr_mfstartup = MFStartup(MF_VERSION, MFSTARTUP_LITE); + + if (bCallCoUninitialize) + CoUninitialize(); +} + +static void mfdemux_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *spec) +{ + GstMFDemux *demux = GST_MFDEMUX(object); + switch (prop_id) + { + case PROP_HLS_MODE: + if (g_value_get_boolean(value)) + demux->is_hls = TRUE; + break; + default: + break; + } +} + +static void gst_mfdemux_dispose(GObject* object) +{ + GstMFDemux *demux = GST_MFDEMUX(object); + + SafeRelease(&demux->pSourceReader); + SafeRelease(&demux->pIMFByteStream); + + if (demux->audioFormat.codec_data != NULL) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref(demux->audioFormat.codec_data); + demux->audioFormat.codec_data = NULL; + } + + if (demux->videoFormat.sequence_header != NULL) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref(demux->videoFormat.sequence_header); + demux->videoFormat.sequence_header = NULL; + } + + if (demux->cached_segment_event != NULL) + { + // INLINE - gst_event_unref() + gst_event_unref(demux->cached_segment_event); + demux->cached_segment_event = NULL; + } + + g_mutex_clear(&demux->lock); + + if (demux->hr_mfstartup == S_OK) + MFShutdown(); + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +// Processes input buffers +static GstFlowReturn mfdemux_chain(GstPad *pad, GstObject *parent, GstBuffer *buf) +{ + // INLINE - gst_buffer_unref() + gst_buffer_unref(buf); + return GST_FLOW_NOT_SUPPORTED; +} + +static gboolean mfdemux_push_sink_event(GstMFDemux *demux, GstEvent *event) +{ + gboolean ret = TRUE; + + if (demux->audio_src_pad != NULL && gst_pad_is_linked(demux->audio_src_pad)) + ret &= gst_pad_push_event(demux->audio_src_pad, gst_event_ref(event)); // INLINE - gst_event_ref() + + if (demux->video_src_pad != NULL && gst_pad_is_linked(demux->video_src_pad)) + ret &= gst_pad_push_event(demux->video_src_pad, gst_event_ref(event)); // INLINE - gst_event_ref() + + // INLINE - gst_event_unref() + gst_event_unref(event); + + return ret; +} + +static gboolean mfdemux_sink_event(GstPad* pad, GstObject *parent, GstEvent *event) +{ + gboolean ret = FALSE; + GstMFDemux *demux = GST_MFDEMUX(parent); + + switch (GST_EVENT_TYPE(event)) + { + case GST_EVENT_SEGMENT: + { +#if TRACE_ENABLE + { + const GstSegment *segment = NULL; + gst_event_parse_segment(event, &segment); + TRACE(DEMUX_SINK_EVENTS, "GST_EVENT_SEGMENT start=%lld time=%lld position=%lld\n", + segment != NULL ? segment->start : -1, + segment != NULL ? segment->time : -1, + segment != NULL ? segment->position : -1); + } +#endif + + demux->force_discontinuity = TRUE; + demux->is_eos = FALSE; + + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->ClearEOS(); + + // Cache segment event if we not ready yet + if ((demux->audio_src_pad != NULL && gst_pad_is_linked(demux->audio_src_pad)) || + (demux->video_src_pad != NULL && gst_pad_is_linked(demux->video_src_pad))) + { + ret = mfdemux_push_sink_event(demux, event); + } + else + { + if (demux->cached_segment_event != NULL) + gst_event_unref(demux->cached_segment_event); // INLINE - gst_event_unref() + + demux->cached_segment_event = event; + ret = TRUE; + } + } + break; + case GST_EVENT_FLUSH_START: + { + TRACE(DEMUX_SINK_EVENTS, "GST_EVENT_FLUSH_START\n"); + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + case GST_EVENT_FLUSH_STOP: + { + TRACE(DEMUX_SINK_EVENTS, "GST_EVENT_FLUSH_STOP\n"); + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + case GST_EVENT_EOS: + { + TRACE(DEMUX_SINK_EVENTS, "GST_EVENT_EOS\n"); + + demux->is_eos = TRUE; + + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->SignalEOS(); + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + case GST_EVENT_CAPS: + { +#if TRACE_ENABLE + { + GstCaps *caps = NULL; + gst_event_parse_caps(event, &caps); + gchar *caps_str = gst_caps_to_string(caps); + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_CAPS: %s\n", caps_str); + g_free(caps_str); + } +#endif + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + // This event appears only in pull mode during outrange reading or seeking. + case FX_EVENT_RANGE_READY: + { + TRACE(DEMUX_SINK_EVENTS, "FX_EVENT_RANGE_READY\n"); + + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->ReadRangeAvailable(); + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + case FX_EVENT_SEGMENT_READY: + { + gint64 size = -1; + const GstStructure *s = gst_event_get_structure(event); + if (s != NULL) + { + if (!gst_structure_get_int64(s, "size", &size)) + size = -1; + } + + TRACE(DEMUX_SINK_EVENTS, "FX_EVENT_SEGMENT_READY size=%lld\n", size); + + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->SetStreamLength((QWORD)size); + + // Force discontinuity, since it is new stream + demux->force_discontinuity = TRUE; + + // Start task if needed + if (demux->start_task_on_first_segment) + { + TRACE(DEMUX_TASK, "Start task on first segment ready\n"); + gst_pad_start_task(pad, (GstTaskFunction) mfdemux_loop, pad, NULL); + demux->start_task_on_first_segment = FALSE; + } + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + } + break; + default: + TRACE(DEMUX_SINK_EVENTS, "Received pass-thru event\n"); + ret = mfdemux_push_sink_event(demux, event); + break; + } + + return ret; +} + +static gboolean mfdemux_src_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + gboolean ret = TRUE; + GstMFDemux *demux = GST_MFDEMUX(parent); + + switch (GST_QUERY_TYPE(query)) + { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration(query, &format, NULL); + if (format != GST_FORMAT_TIME || demux->llDuration == -1) + ret = gst_pad_query_default(pad, parent, query); + else + gst_query_set_duration(query, GST_FORMAT_TIME, demux->llDuration * 100); + } + break; + default: + ret = gst_pad_query_default(pad, parent, query); + } + + return ret; +} + +static gboolean mfdemux_src_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + gboolean ret = FALSE; + GstMFDemux *demux = GST_MFDEMUX(parent); + + switch (GST_EVENT_TYPE (event)) + { + case GST_EVENT_SEEK: + { + HRESULT hr = S_OK; + + gdouble rate; // segment rate + GstFormat format; // format of the seek values + GstSeekFlags flags; // the seek flags + GstSeekType start_type; // the seek type of the start position + GstSeekType stop_type; // the seek type of the stop position + gint64 start; // the seek start position in the given format + gint64 stop; // the seek stop position in the given format + guint32 seqnum; + + // Do not init seek if we in error state. It can happen if + // critical error occured and we disposing pipeline. + g_mutex_lock(&demux->lock); + TRACE(DEMUX_SRC_EVENTS, "GST_EVENT_SEEK src_result=%d is_hls=%d stream=%p reader=%p\n", + demux->src_result, demux->is_hls, demux->pGSTMFByteStream, demux->pSourceReader); + if (demux->src_result == GST_FLOW_ERROR || + demux->pGSTMFByteStream == NULL || + (!demux->is_hls && demux->pSourceReader == NULL)) + { + g_mutex_unlock(&demux->lock); + // INLINE - gst_event_unref() + gst_event_unref (event); + return TRUE; + } + g_mutex_unlock(&demux->lock); + + demux->is_eos = FALSE; + // Clear EOS on byte stream, since SourceReader will start + // reading it during seek. + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->ClearEOS(); + + // Get seek description from the event. + gst_event_parse_seek (event, &rate, &format, &flags, + &start_type, &start, &stop_type, &stop); + seqnum = gst_event_get_seqnum(event); + + TRACE(DEMUX_SRC_EVENTS, "GST_EVENT_SEEK rate=%lf format=%d flags=0x%X start_type=%d start=%lld stop_type=%d stop=%lld is_hls=%d\n", + rate, format, flags, start_type, start, stop_type, stop, demux->is_hls); + + if (format == GST_FORMAT_TIME) + { + if (flags & GST_SEEK_FLAG_FLUSH) + { + GstEvent *e = gst_event_new_flush_start(); + gst_event_set_seqnum(e, seqnum); + // Push event dowstream. We do not flush upstream, since + // we working in pull mode. + mfdemux_push_sink_event(demux, e); + } + + // Stop streaming thread + g_mutex_lock(&demux->lock); + demux->src_result = GST_FLOW_FLUSHING; + g_mutex_unlock(&demux->lock); + + // Lock pad. Streaming thread might be waiting for data, but + // it should release stream lock when doing it. + GST_PAD_STREAM_LOCK(demux->sink_pad); + // Unblock source reader if it was waiting for read. + if (demux->pSourceReader != NULL) // Can be NULL for HLS + demux->pSourceReader->Flush(MF_SOURCE_READER_ALL_STREAMS); + // Unlock stream lock so streaming thread can continue. + GST_PAD_STREAM_UNLOCK(demux->sink_pad); + + // Wait for streaming thread to exit + TRACE(DEMUX_TASK, "Pause task due to seek\n"); + gst_pad_pause_task(demux->sink_pad); + + if (demux->is_hls) + { + // Upstream will handle and unref event + TRACE(DEMUX_SRC_EVENTS, "GST_EVENT_SEEK forward upstream for HLS\n"); + ret = gst_pad_push_event(demux->sink_pad, event); + } + else + { + TRACE(DEMUX_SRC_EVENTS, "GST_EVENT_SEEK handle event for MP4\n"); + demux->rate = rate; + demux->seek_position = start; + demux->send_new_segment = !demux->is_hls; + + PROPVARIANT pv = { 0 }; + pv.vt = VT_I8; + pv.hVal.QuadPart = (LONGLONG)(start / 100); + hr = demux->pSourceReader->SetCurrentPosition(GUID_NULL, pv); + PropVariantClear(&pv); + + // INLINE - gst_event_unref() + gst_event_unref(event); + + ret = SUCCEEDED(hr) ? true : false; + } + + // Stop flushing even if we failed + if (flags & GST_SEEK_FLAG_FLUSH) + { + GstEvent *e = gst_event_new_flush_stop(TRUE); + gst_event_set_seqnum(e, seqnum); + mfdemux_push_sink_event(demux, e); + } + + g_mutex_lock(&demux->lock); + demux->src_result = SUCCEEDED(hr) ? GST_FLOW_OK : GST_FLOW_ERROR; + g_mutex_unlock(&demux->lock); + + // Do not re-start if we fail + if (SUCCEEDED(hr) && ret) + { + if (demux->is_hls) + { + // For HLS just reload + TRACE(DEMUX_RELOAD, "Reload on seek for HLS\n"); + mfdemux_reload_demux(demux, TRUE); + } + else + { + // Start streaming thread + TRACE(DEMUX_TASK, "Restart task after non-HLS seek\n"); + gst_pad_start_task(demux->sink_pad, (GstTaskFunction)mfdemux_loop, + demux->sink_pad, NULL); + } + } + else + { + gst_element_message_full(GST_ELEMENT(demux), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX, + g_strdup("mfdemux failed to seek"), NULL, + ("mfdemux.c"), ("mfdemux_src_event"), 0); + } + } + } + break; + default: + ret = gst_pad_push_event(demux->sink_pad, event); + break; + } + + return ret; +} + +static void mfdemux_reload_demux(GstMFDemux *demux, gboolean bSeek) +{ + TRACE(DEMUX_RELOAD, "bSeek=%d initialized=%d eos=%d start_task_on_first_segment=%d reader=%p byteSteam %p\n", + bSeek, demux->is_demux_initialized, demux->is_eos, demux->start_task_on_first_segment, + demux->pSourceReader, demux->pGSTMFByteStream); + + if (demux->pGSTMFByteStream == NULL) + return; // Unlikely + + // Release source reader. + SafeRelease(&demux->pSourceReader); + + // It might change. + demux->audio_stream_index = -1; + demux->video_stream_index = -1; + + // Reset GSTMFByteStream object. + demux->pGSTMFByteStream->Reset(); + + // Set length to -1. + demux->pGSTMFByteStream->SetStreamLength(-1); + + // Ask HLS for next segment if not seek. Seek will reset HLS buffer, so + // it will be starting from right segment we need. + if (bSeek) + { + demux->start_task_on_first_segment = TRUE; + } + else + { + TRACE(DEMUX_RELOAD, "Sending FX_EVENT_NEXT_SEGMENT\n"); + gst_pad_push_event(demux->sink_pad, gst_event_new_custom( + static_cast(FX_EVENT_NEXT_SEGMENT), NULL)); + + // If next segment is available we will receive FX_EVENT_SEGMENT_READY + // during FX_EVENT_NEXT_SEGMENT call. In this case we good to go, but + // otherwise we need to wait. FX_EVENT_NEXT_SEGMENT is serialized event + // and pad lock will be held. Same lock is held during reload. + + // Check length of byte stream. + QWORD qwLength = -1; + if (SUCCEEDED(demux->pGSTMFByteStream->GetLength(&qwLength))) + { + if (qwLength == -1) // If still unknown start on event + demux->start_task_on_first_segment = TRUE; + } + TRACE(DEMUX_RELOAD, "length=%llu\n", qwLength); + } + +#if TRACE_ENABLE + TRACE(DEMUX_FIRST_AND_LAST_PTS, "Last sample PTS pad=%s pts=%lld dur=%lld\n", + "Audio", + demux->last_audio_pts, + demux->last_audio_dur); + + TRACE(DEMUX_FIRST_AND_LAST_PTS, "Last sample PTS pad=%s pts=%lld dur=%lld\n", + "Video", + demux->last_video_pts, + demux->last_video_dur); + + demux->log_first_audio_pts = TRUE; + demux->log_first_video_pts = TRUE; + demux->last_audio_pts = -1; + demux->last_audio_dur = -1; + demux->last_video_pts = -1; + demux->last_video_dur = -1; +#endif + + demux->is_demux_initialized = FALSE; + TRACE(DEMUX_RELOAD, "Reload completed start_task_on_first_segment=%d\n", demux->start_task_on_first_segment); +} + +static gboolean mfdemux_init_demux(GstMFDemux *demux, GstCaps *caps) +{ + if (demux->is_demux_initialized) + return TRUE; + + HRESULT hr = S_OK; + + gint64 data_length = 0; + if (!gst_pad_peer_query_duration(demux->sink_pad, GST_FORMAT_BYTES, &data_length)) + { + return false; + } + + // For HTTP/HTTPS/FILE we need to provide segment. For HLS no need, since + // hlsprogressbuffer will handle it. + demux->send_new_segment = !demux->is_hls; + + if (demux->pGSTMFByteStream == NULL) + { + demux->pGSTMFByteStream = new (nothrow) CMFGSTByteStream((QWORD)data_length, demux->sink_pad, demux->is_hls); + if (demux->pGSTMFByteStream == NULL) + return FALSE; + + hr = demux->pGSTMFByteStream->QueryInterface(IID_IMFByteStream, (void**)&demux->pIMFByteStream); + if (FAILED(hr) || demux->pIMFByteStream == NULL) + return FALSE; + } + + hr = MFCreateSourceReaderFromByteStream(demux->pIMFByteStream, NULL, &demux->pSourceReader); + TRACE(DEMUX_TASK, "MFCreateSourceReaderFromByteStream hr=0x%08X reader=%p\n", + hr, demux->pSourceReader); + if (FAILED(hr) || demux->pSourceReader == NULL) + return FALSE; + + // Get duration + PROPVARIANT pv = {0}; + hr = demux->pSourceReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &pv); + if (SUCCEEDED(hr)) + { + demux->llDuration = (LONGLONG)pv.uhVal.QuadPart; + PropVariantClear(&pv); + } + + // Disable all streams. Disabled streams does not consume memory if not + // read. MP4 might contain subtitles or additional audio stream and we do + // not support it. We will enable needed streams when configuring demux. + hr = demux->pSourceReader->SetStreamSelection((DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE); + if (FAILED(hr)) + return FALSE; + + demux->is_demux_initialized = TRUE; + + return TRUE; +} + +// JFX_CODEC_ID_AAC (MFAudioFormat_AAC): +// From https://learn.microsoft.com/en-us/windows/win32/medfound/aac-media-types +// pBlobBytes contains the portion of the HEAACWAVEINFO structure that appears after +// the WAVEFORMATEX structure (that is, after the wfx member). +// This is followed by the AudioSpecificConfig() data, as defined by ISO/IEC 14496-3. +static gboolean mfdemux_extract_codec_data(GstMFDemux *demux, JFX_CODEC_ID codecID, + UINT8 *pBlobBytes, UINT32 cbBlobSize, + UINT8 *pCodecData, UINT32 *cbCodecDataSize) +{ + if (demux == NULL) + return false; + if (pBlobBytes == NULL || cbBlobSize <= 0) + return false; + if (pCodecData == NULL || cbCodecDataSize == NULL) + return false; + if ((*cbCodecDataSize) != MAX_CODEC_DATA_SIZE) + return false; + + if (codecID == JFX_CODEC_ID_AAC && + cbBlobSize > (sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX))) + { + DWORD offset = sizeof(HEAACWAVEINFO) - sizeof(WAVEFORMATEX); + + if ((*cbCodecDataSize) >= (cbBlobSize - offset)) + (*cbCodecDataSize) = cbBlobSize - offset; + else + return false; // Not enough space in pCodecData buffer + + memcpy(pCodecData, pBlobBytes + offset, (*cbCodecDataSize)); + } + else + { + return false; + } + + return true; +} + +// If codec_data not available or fail to read, then codec_data will be set to NULL. +// We will attempt to playback media stream without codec data anyway. +static void mfdemux_get_codec_data(GstMFDemux *demux, const GUID &guidKey, + IMFMediaType *pMediaType, GstBuffer **codec_data, + JFX_CODEC_ID codecID) +{ + UINT32 cbBlobSize = 0; + UINT8 blobBytes[MAX_CODEC_DATA_SIZE] = {0}; + UINT32 cbCodecDataSize = MAX_CODEC_DATA_SIZE; + UINT8 codecDataBytes[MAX_CODEC_DATA_SIZE] = {0}; + + if (pMediaType == NULL) + return; + + HRESULT hr = pMediaType->GetBlobSize(guidKey, &cbBlobSize); + if (SUCCEEDED(hr) && cbBlobSize > 0 && cbBlobSize <= MAX_CODEC_DATA_SIZE) + { + hr = pMediaType->GetBlob(guidKey, &blobBytes[0], cbBlobSize, NULL); + if (SUCCEEDED(hr)) + { + if (!mfdemux_extract_codec_data(demux, codecID, + &blobBytes[0], cbBlobSize, &codecDataBytes[0], &cbCodecDataSize)) + return; + } + + if (SUCCEEDED(hr)) + (*codec_data) = gst_buffer_new_allocate(NULL, (gsize)cbCodecDataSize, NULL); + + if ((*codec_data) != NULL) + { + GstMapInfo info; + if (gst_buffer_map((*codec_data), &info, GST_MAP_READWRITE)) + { + if (memcpy_s(info.data, info.maxsize, &codecDataBytes[0], cbCodecDataSize) != 0) + hr = E_FAIL; + + gst_buffer_unmap((*codec_data), &info); + if (SUCCEEDED(hr)) + return; + } + + // INLINE - gst_buffer_unref() + gst_buffer_unref((*codec_data)); + (*codec_data) = NULL; + } + } +} + +static void mfdemux_get_media_type_blob(const GUID &guidKey, + IMFMediaType *pMediaType, + GstBuffer **blob) +{ + UINT32 cbBlobSize = 0; + + if (pMediaType == NULL || blob == NULL) + return; + + HRESULT hr = pMediaType->GetBlobSize(guidKey, &cbBlobSize); + if (FAILED(hr) || cbBlobSize == 0) + return; + + (*blob) = gst_buffer_new_allocate(NULL, (gsize)cbBlobSize, NULL); + if ((*blob) == NULL) + return; + + GstMapInfo info; + if (gst_buffer_map((*blob), &info, GST_MAP_READWRITE)) + { + hr = pMediaType->GetBlob(guidKey, info.data, cbBlobSize, NULL); + gst_buffer_unmap((*blob), &info); + } + else + { + hr = E_FAIL; + } + + if (FAILED(hr)) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref((*blob)); + (*blob) = NULL; + } +} + +// Only push caps event if caps are not set or if they change. +// Caps events are serialized with frames and we will drain compressed +// frames queue if we pushing unnecessary caps events. +// It will cause video glitch. +static void mfdemux_push_caps_if_needed(GstPad *pad, GstCaps *caps, gboolean *ret) +{ + if (pad == NULL || ret == NULL || caps == NULL) + return; + + GstCaps *current_caps = gst_pad_get_current_caps(pad); + gboolean is_caps_equal = current_caps != NULL && gst_caps_is_equal(current_caps, caps); + if (current_caps) + gst_caps_unref(current_caps); + + if (is_caps_equal) + { + *ret = TRUE; // Caps already valid + return; + } + + GstEvent *caps_event = gst_event_new_caps(caps); + if (caps_event) + *ret = gst_pad_push_event(pad, caps_event); +} + +static gboolean mfdemux_configure_audio_stream(GstMFDemux *demux, gboolean *hasAudio) +{ + HRESULT hr = S_OK; + IMFMediaType *pMediaType = NULL; + GUID subType = GUID_NULL; + + hr = demux->pSourceReader-> + SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE); + if (hr == MF_E_INVALIDSTREAMNUMBER) + { + (*hasAudio) = false; + return TRUE; + } + + if (SUCCEEDED(hr)) + { + hr = demux->pSourceReader-> + GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + (DWORD)MF_SOURCE_READER_CURRENT_TYPE_INDEX, + &pMediaType); + } + + if (SUCCEEDED(hr)) + hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &subType); + + if (SUCCEEDED(hr) && IsEqualGUID(subType, MFAudioFormat_AAC)) { + demux->audioFormat.codecID = JFX_CODEC_ID_AAC; + } else if (SUCCEEDED(hr)) { + // Disable if format is not supported + hr = demux->pSourceReader-> + SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, FALSE); + } + + if (SUCCEEDED(hr) && demux->audioFormat.codecID == JFX_CODEC_ID_AAC) + { + // When we reload codec_data might be already set, so unref it for + // new codec_data. + if (demux->audioFormat.codec_data != NULL) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref(demux->audioFormat.codec_data); + demux->audioFormat.codec_data = NULL; + } + + pMediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, + &demux->audioFormat.uiChannels); + pMediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, + &demux->audioFormat.uiRate); + mfdemux_get_codec_data(demux, MF_MT_USER_DATA, pMediaType, + &demux->audioFormat.codec_data, + demux->audioFormat.codecID); + } + + SafeRelease(&pMediaType); + + if (FAILED(hr)) + { + demux->audioFormat.codecID = JFX_CODEC_ID_UNKNOWN; + (*hasAudio) = false; + return TRUE; + } + + (*hasAudio) = true; + + return TRUE; +} + +static gboolean mfdemux_configure_audio_src_caps(GstMFDemux *demux) +{ + gboolean ret = FALSE; + GstCaps *caps = NULL; + + if (demux->audioFormat.codecID != JFX_CODEC_ID_AAC) + return FALSE; // We should not be called with unsupported codec + + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 4, + "rate", G_TYPE_INT, (gint)demux->audioFormat.uiRate, + "channels", G_TYPE_INT, (gint)demux->audioFormat.uiChannels, + NULL); + if (caps == NULL) + return FALSE; + + if (demux->audioFormat.codec_data) + gst_caps_set_simple(caps, "codec_data", GST_TYPE_BUFFER, + demux->audioFormat.codec_data, NULL); + + mfdemux_push_caps_if_needed(demux->audio_src_pad, caps, &ret); + gst_caps_unref(caps); + + return ret; +} + +static gboolean mfdemux_configure_audio_src_pad(GstMFDemux *demux) +{ + if (!demux) + return FALSE; + + // If pad exist just reconfigure caps. + if (demux->audio_src_pad) + return mfdemux_configure_audio_src_caps(demux); + + if (demux->audioFormat.codecID != JFX_CODEC_ID_AAC) + return TRUE; // Just ignore unknown audio stream + + demux->audio_src_pad = + gst_pad_new_from_template(gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS(demux), "audio"), "audio"); + if (demux->audio_src_pad == NULL) + return FALSE; + + gst_pad_set_query_function(demux->audio_src_pad, mfdemux_src_query); + gst_pad_set_event_function(demux->audio_src_pad, mfdemux_src_event); + + if (!gst_pad_set_active(demux->audio_src_pad, TRUE) || + !mfdemux_configure_audio_src_caps(demux)) + { + gst_object_unref(demux->audio_src_pad); + demux->audio_src_pad = NULL; + return FALSE; + } + + if (!gst_element_add_pad(GST_ELEMENT(demux), demux->audio_src_pad)) { + // Pad will be unref even if gst_element_add_pad() fails + demux->audio_src_pad = NULL; + return FALSE; + } + + return TRUE; +} + +static gboolean mfdemux_configure_video_stream(GstMFDemux *demux, gboolean *hasVideo) +{ + HRESULT hr = S_OK; + IMFMediaType *pMediaType = NULL; + GUID subType = GUID_NULL; + + hr = demux->pSourceReader-> + SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE); + if (hr == MF_E_INVALIDSTREAMNUMBER) + { + (*hasVideo) = false; + return TRUE; + } + + if (SUCCEEDED(hr)) + { + hr = demux->pSourceReader-> + GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + (DWORD)MF_SOURCE_READER_CURRENT_TYPE_INDEX, + &pMediaType); + } + + if (SUCCEEDED(hr)) + hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &subType); + + if (SUCCEEDED(hr) && IsEqualGUID(subType, MFVideoFormat_H264)) + { + demux->videoFormat.codecID = JFX_CODEC_ID_H264; + } + else if (SUCCEEDED(hr) && IsEqualGUID(subType, MFVideoFormat_HEVC)) + { + demux->videoFormat.codecID = JFX_CODEC_ID_HEVC; + } + else if (SUCCEEDED(hr)) + { + // Disable if format is not known + hr = demux->pSourceReader-> + SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, FALSE); + } + + if (SUCCEEDED(hr) && (demux->videoFormat.codecID == JFX_CODEC_ID_H264 || + demux->videoFormat.codecID == JFX_CODEC_ID_HEVC)) + { + if (FAILED(MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, + &demux->videoFormat.uiWidth, &demux->videoFormat.uiHeight))) + { + demux->videoFormat.uiWidth = 0; + demux->videoFormat.uiHeight = 0; + } + + if (FAILED(MFGetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, + &demux->videoFormat.uiFrameRateNum, &demux->videoFormat.uiFrameRateDen))) + { + demux->videoFormat.uiFrameRateNum = 0; + demux->videoFormat.uiFrameRateDen = 0; + } + + if (FAILED(pMediaType->GetUINT32(MF_MT_INTERLACE_MODE, + &demux->videoFormat.uiInterlaceMode))) + { + demux->videoFormat.uiInterlaceMode = MFVideoInterlace_Unknown; + } + + if (FAILED(MFGetAttributeRatio(pMediaType, MF_MT_PIXEL_ASPECT_RATIO, + &demux->videoFormat.uiPixelAspectRatioNum, &demux->videoFormat.uiPixelAspectRatioDen))) + { + demux->videoFormat.uiPixelAspectRatioNum = 0; + demux->videoFormat.uiPixelAspectRatioDen = 0; + } + + if (FAILED(pMediaType->GetUINT32(MF_MT_MPEG2_PROFILE, + &demux->videoFormat.uiMPEG2Profile))) + { + demux->videoFormat.uiMPEG2Profile = 0; + } + + if (FAILED(pMediaType->GetUINT32(MF_MT_MPEG2_LEVEL, + &demux->videoFormat.uiMPEG2Level))) + { + demux->videoFormat.uiMPEG2Level = 0; + } + + if (demux->videoFormat.sequence_header != NULL) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref(demux->videoFormat.sequence_header); + demux->videoFormat.sequence_header = NULL; + } + + mfdemux_get_media_type_blob(MF_MT_MPEG_SEQUENCE_HEADER, pMediaType, + &demux->videoFormat.sequence_header); + } + + SafeRelease(&pMediaType); + + if (FAILED(hr)) + { + demux->videoFormat.codecID = JFX_CODEC_ID_UNKNOWN; + (*hasVideo) = false; + return TRUE; + } + + (*hasVideo) = true; + + return TRUE; +} + +static gboolean mfdemux_configure_video_src_caps(GstMFDemux *demux) +{ + gboolean ret = FALSE; + GstCaps *caps = NULL; + + if (demux->videoFormat.codecID != JFX_CODEC_ID_H264 && + demux->videoFormat.codecID != JFX_CODEC_ID_HEVC) + return FALSE; // We should not be called with unsupported codec + + if (demux->videoFormat.codecID == JFX_CODEC_ID_H264) + { + caps = gst_caps_new_simple ("video/x-h264", + "width", G_TYPE_INT, (gint)demux->videoFormat.uiWidth, + "height", G_TYPE_INT, (gint)demux->videoFormat.uiHeight, + NULL); + } + else if (demux->videoFormat.codecID == JFX_CODEC_ID_HEVC) + { + caps = gst_caps_new_simple ("video/x-h265", + "width", G_TYPE_INT, (gint)demux->videoFormat.uiWidth, + "height", G_TYPE_INT, (gint)demux->videoFormat.uiHeight, + NULL); + } + + if (caps == NULL) + return FALSE; + + if (demux->videoFormat.uiFrameRateNum != 0 && + demux->videoFormat.uiFrameRateDen != 0) + { + gst_caps_set_simple(caps, "framerate", GST_TYPE_FRACTION, + (gint)demux->videoFormat.uiFrameRateNum, + (gint)demux->videoFormat.uiFrameRateDen, + NULL); + } + + if (demux->videoFormat.uiInterlaceMode != MFVideoInterlace_Unknown) + { + gst_caps_set_simple(caps, "mf-interlace-mode", G_TYPE_UINT, + demux->videoFormat.uiInterlaceMode, NULL); + } + + if (demux->videoFormat.uiPixelAspectRatioNum != 0 && + demux->videoFormat.uiPixelAspectRatioDen != 0) + { + gst_caps_set_simple(caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + (gint)demux->videoFormat.uiPixelAspectRatioNum, + (gint)demux->videoFormat.uiPixelAspectRatioDen, + NULL); + } + + if (demux->videoFormat.uiMPEG2Profile != 0) + { + gst_caps_set_simple(caps, "mf-mpeg2-profile", G_TYPE_UINT, + demux->videoFormat.uiMPEG2Profile, NULL); + } + + if (demux->videoFormat.uiMPEG2Level != 0) + { + gst_caps_set_simple(caps, "mf-mpeg2-level", G_TYPE_UINT, + demux->videoFormat.uiMPEG2Level, NULL); + } + + if (demux->videoFormat.sequence_header != NULL) + { + gst_caps_set_simple(caps, "mf-mpeg-sequence-header", GST_TYPE_BUFFER, + demux->videoFormat.sequence_header, NULL); + } + + if (!demux->pGSTMFByteStream->IsSeekSupported()) + { + gst_caps_set_simple(caps, "fragmented", G_TYPE_BOOLEAN, + TRUE, NULL); + } + + mfdemux_push_caps_if_needed(demux->video_src_pad, caps, &ret); + gst_caps_unref(caps); + + return ret; +} + +static gboolean mfdemux_configure_video_src_pad(GstMFDemux *demux) +{ + if (!demux) + return FALSE; + + // If pad exist just reconfigure caps. + if (demux->video_src_pad) + return mfdemux_configure_video_src_caps(demux); + + if (demux->videoFormat.codecID != JFX_CODEC_ID_H264 && + demux->videoFormat.codecID != JFX_CODEC_ID_HEVC) + { + return TRUE; // Just ignore unknown video stream + } + + demux->video_src_pad = + gst_pad_new_from_template(gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS(demux), "video"), "video"); + if (demux->video_src_pad == NULL) + return FALSE; + + gst_pad_set_query_function(demux->video_src_pad, mfdemux_src_query); + gst_pad_set_event_function(demux->video_src_pad, mfdemux_src_event); + + if (!gst_pad_set_active(demux->video_src_pad, TRUE) || + !mfdemux_configure_video_src_caps(demux)) + { + gst_object_unref(demux->video_src_pad); + demux->video_src_pad = NULL; + return FALSE; + } + + if (!gst_element_add_pad(GST_ELEMENT(demux), demux->video_src_pad)) { + // Pad will be unref even if gst_element_add_pad() fails + demux->video_src_pad = NULL; + return FALSE; + } + + return TRUE; +} + +// Enables streams and creates src pads +static gboolean mfdemux_configure_demux(GstMFDemux *demux) +{ + gboolean hasAudio = false; + gboolean hasVideo = false; + + if (!demux->is_demux_initialized) + return FALSE; + + if (!mfdemux_configure_audio_stream(demux, &hasAudio)) + return FALSE; + + if (hasAudio && !mfdemux_configure_audio_src_pad(demux)) + return FALSE; + + if (!mfdemux_configure_video_stream(demux, &hasVideo)) + return FALSE; + + if (hasVideo && !mfdemux_configure_video_src_pad(demux)) + return FALSE; + + // No more pads are expected + gst_element_no_more_pads(GST_ELEMENT(demux)); + + return TRUE; +} + +static void mfdemux_send_new_segment(GstMFDemux *demux, GstClockTime position) +{ + GstSegment segment; + GstEvent *new_segment = NULL; + + gst_segment_init(&segment, GST_FORMAT_TIME); + + segment.rate = demux->rate; + segment.start = demux->seek_position; + segment.stop = demux->llDuration < 0 ? GST_CLOCK_TIME_NONE : demux->llDuration * 100; + segment.time = demux->seek_position; + segment.position = position; + segment.duration = segment.stop; + + new_segment = gst_event_new_segment(&segment); + mfdemux_push_sink_event(demux, new_segment); +} + +static GstFlowReturn mfdemux_deliver_sample(GstMFDemux *demux, GstPad* pad, + IMFSample *pMFSample) +{ + GstFlowReturn ret = GST_FLOW_ERROR; + IMFMediaBuffer *pMFBuffer = NULL; + gboolean unlock_buffer = FALSE; + BYTE *pbMFBuffer = NULL; + DWORD cbMFCurrentLength = 0; + GstBuffer *pBuffer = NULL; + GstMapInfo info; + gboolean unmap_buffer = FALSE; + + // Allocate GStreamer buffer and copy data to it + HRESULT hr = pMFSample->ConvertToContiguousBuffer(&pMFBuffer); + if (SUCCEEDED(hr)) + { + hr = pMFBuffer->Lock(&pbMFBuffer, NULL, &cbMFCurrentLength); + if (SUCCEEDED(hr)) + unlock_buffer = TRUE; + } + + if (SUCCEEDED(hr)) + pBuffer = gst_buffer_new_allocate(NULL, (gsize)cbMFCurrentLength, NULL); + + if (FAILED(hr) || pBuffer == NULL) + { + if (unlock_buffer) + pMFBuffer->Unlock(); + + SafeRelease(&pMFBuffer); + + return GST_FLOW_ERROR; + } + + if (gst_buffer_map(pBuffer, &info, GST_MAP_READWRITE)) + unmap_buffer = TRUE; + else + hr = E_FAIL; + + if (SUCCEEDED(hr)) + { + if (memcpy_s(info.data, info.maxsize, pbMFBuffer, cbMFCurrentLength) != 0) + hr = E_FAIL; + } + + if (unmap_buffer) + gst_buffer_unmap(pBuffer, &info); + + if (unlock_buffer) + pMFBuffer->Unlock(); + + SafeRelease(&pMFBuffer); + + // Set PTS, duration, etc. + LONGLONG hnsSampleTime = -1; + if (SUCCEEDED(hr) && SUCCEEDED(pMFSample->GetSampleTime(&hnsSampleTime))) + GST_BUFFER_TIMESTAMP(pBuffer) = (hnsSampleTime * 100); + + LONGLONG hnsSampleDuration = -1; + if (SUCCEEDED(hr) && SUCCEEDED(pMFSample->GetSampleDuration(&hnsSampleDuration))) + GST_BUFFER_DURATION(pBuffer) = (hnsSampleDuration * 100); + + UINT32 bDiscontinuity = FALSE; + if (SUCCEEDED(hr) && SUCCEEDED(pMFSample->GetUINT32(MFSampleExtension_Discontinuity, &bDiscontinuity))) + { + if (bDiscontinuity) + GST_BUFFER_FLAG_SET(pBuffer, GST_BUFFER_FLAG_DISCONT); + } + + UINT32 bCleanPoint = FALSE; + if (SUCCEEDED(hr) && SUCCEEDED(pMFSample->GetUINT32(MFSampleExtension_CleanPoint, &bCleanPoint))) + { + if (!bCleanPoint) + GST_BUFFER_FLAG_SET(pBuffer, GST_BUFFER_FLAG_DELTA_UNIT); + } + + if (SUCCEEDED(hr) && demux->force_discontinuity) + { + GST_BUFFER_FLAG_SET(pBuffer, GST_BUFFER_FLAG_DISCONT); + demux->force_discontinuity = FALSE; + } + + // Before pushing buffer send new segment if needed + if (demux->send_new_segment) + { + mfdemux_send_new_segment(demux, GST_BUFFER_TIMESTAMP(pBuffer)); + demux->send_new_segment = FALSE; + } + else if (demux->cached_segment_event != NULL) + { + GstSegment input_segment; + GstSegment output_segment; + + gst_event_copy_segment(demux->cached_segment_event, &input_segment); + + if (input_segment.format == GST_FORMAT_BYTES) + { + // hlsprogressbuffer uses a byte segment for HLS Live. Convert it to + // time since we will handle timestamps for HLS Live. + gst_segment_init(&output_segment, GST_FORMAT_TIME); + output_segment.rate = input_segment.rate; + output_segment.applied_rate = input_segment.applied_rate; + output_segment.flags = input_segment.flags; + output_segment.start = GST_BUFFER_TIMESTAMP(pBuffer); + output_segment.stop = GST_CLOCK_TIME_NONE; + output_segment.position = GST_BUFFER_TIMESTAMP(pBuffer); + output_segment.time = 0; + } + else + { + output_segment = input_segment; + + // Adjust segment time to first sample time. For HLS Live time + // can start from any PTS. By default segment starts at 0, so pipeline + // will start from 0 and wait until PTS is reached. Thus we need to adjust + // it to start imidiately. + output_segment.start = GST_BUFFER_TIMESTAMP(pBuffer); + output_segment.position = GST_BUFFER_TIMESTAMP(pBuffer); + } + + GstEvent *event = gst_event_new_segment(&output_segment); + gst_event_unref(demux->cached_segment_event); + demux->cached_segment_event = NULL; + +#if TRACE_ENABLE + TRACE(DEMUX_SRC_EVENTS, "GST_EVENT_SEGMENT start=%lld time=%lld position=%lld\n", + segment.start, segment.time, segment.position); +#endif + + mfdemux_push_sink_event(demux, event); + } + +#if TRACE_ENABLE + if (pad == demux->audio_src_pad) + { + demux->last_audio_pts = GST_BUFFER_TIMESTAMP_IS_VALID(pBuffer) ? GST_BUFFER_TIMESTAMP(pBuffer) : -1; + demux->last_audio_dur = GST_BUFFER_DURATION_IS_VALID(pBuffer) ? GST_BUFFER_DURATION(pBuffer) : -1; + } + if (pad == demux->video_src_pad) + { + demux->last_video_pts = GST_BUFFER_TIMESTAMP_IS_VALID(pBuffer) ? GST_BUFFER_TIMESTAMP(pBuffer) : -1;; + demux->last_video_dur = GST_BUFFER_DURATION_IS_VALID(pBuffer) ? GST_BUFFER_DURATION(pBuffer) : -1; + } + + if ((demux->log_first_audio_pts && pad == demux->audio_src_pad) || + (demux->log_first_video_pts && pad == demux->video_src_pad)) + { + TRACE(DEMUX_FIRST_AND_LAST_PTS, "First sample PTS pad=%s pts=%lld dur=%lld\n", + GST_PAD_NAME(pad), + GST_BUFFER_TIMESTAMP_IS_VALID(pBuffer) ? GST_BUFFER_TIMESTAMP(pBuffer) : -1, + GST_BUFFER_DURATION_IS_VALID(pBuffer) ? GST_BUFFER_DURATION(pBuffer) : -1); + + if (pad == demux->audio_src_pad) + demux->log_first_audio_pts = FALSE; + if (pad == demux->video_src_pad) + demux->log_first_video_pts = FALSE; + } +#endif + + TRACE(DEMUX_OUTPUT_PTS, "PTS pad=%s pts=%lld dur=%lld discont=%d\n", + GST_PAD_NAME(pad), + GST_BUFFER_TIMESTAMP_IS_VALID(pBuffer) ? GST_BUFFER_TIMESTAMP(pBuffer) : -1, + GST_BUFFER_DURATION_IS_VALID(pBuffer) ? GST_BUFFER_DURATION(pBuffer) : -1, + GST_BUFFER_FLAG_IS_SET(pBuffer, GST_BUFFER_FLAG_DISCONT)); + + if (SUCCEEDED(hr)) + ret = gst_pad_push(pad, pBuffer); + + if (FAILED(hr)) + { + // Since we did not push buffer, unref it if needed. + if (pBuffer != NULL) + { + // INLINE - gst_buffer_unref() + gst_buffer_unref(pBuffer); + } + } + + return ret; +} + +static GstPad* mfdemux_get_src_pad(GstMFDemux *demux, DWORD index) +{ + if (demux->audio_stream_index == index) + return demux->audio_src_pad; + else if (demux->video_stream_index == index) + return demux->video_src_pad; + + IMFMediaType *pMediaType = NULL; + HRESULT hr = demux->pSourceReader->GetCurrentMediaType(index, &pMediaType); + if (SUCCEEDED(hr) && pMediaType != NULL) + { + GUID guidMajorType; + hr = pMediaType->GetMajorType(&guidMajorType); + SafeRelease(&pMediaType); + if (SUCCEEDED(hr) && IsEqualGUID(guidMajorType, MFMediaType_Audio)) + { + demux->audio_stream_index = index; + return demux->audio_src_pad; + } + else if (SUCCEEDED(hr) && IsEqualGUID(guidMajorType, MFMediaType_Video)) + { + demux->video_stream_index = index; + return demux->video_src_pad; + } + } + + return NULL; +} + +static void mfdemux_loop(GstPad * pad) +{ + GstMFDemux *demux = GST_MFDEMUX(GST_PAD_PARENT(pad)); + GstFlowReturn result = GST_FLOW_OK; + + if (!demux->is_demux_initialized) + { + GST_PAD_STREAM_UNLOCK(pad); + TRACE(DEMUX_TASK, "Init and configure demux begin\n"); + if (!mfdemux_init_demux(demux, NULL) || + !mfdemux_configure_demux(demux)) + { + gst_element_message_full(GST_ELEMENT(demux), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX, + g_strdup("mfdemux init or configure failed"), NULL, + ("mfdemux.c"), ("mfdemux_loop"), 0); + gst_pad_pause_task(pad); + GST_PAD_STREAM_LOCK(pad); + return; + } + TRACE(DEMUX_TASK, "Init and configure demux done\n"); + GST_PAD_STREAM_LOCK(pad); + } + + g_mutex_lock(&demux->lock); + result = demux->src_result; + g_mutex_unlock(&demux->lock); + + if (result != GST_FLOW_OK) + { + gst_pad_pause_task(pad); + return; + } + + if (demux->pGSTMFByteStream == NULL || demux->pSourceReader == NULL) + { + gst_pad_pause_task(pad); + return; + } + + //DWORD dwControlFlags = demux->is_eos ? MF_SOURCE_READER_CONTROLF_DRAIN : 0; + DWORD dwControlFlags = 0; + DWORD dwActualStreamIndex = 0; + DWORD dwStreamFlags = 0; + LONGLONG llTimestamp = -1; + IMFSample *pSample = NULL; + GST_PAD_STREAM_UNLOCK(pad); + HRESULT hr = demux->pSourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM, + dwControlFlags, + &dwActualStreamIndex, + &dwStreamFlags, + &llTimestamp, + &pSample); + TRACE(DEMUX_READ_SAMPLE, "ReadSample hr=0x%X stream=%lu flags=0x%X ts=%lld sample=%p\n", + hr, dwActualStreamIndex, dwStreamFlags, llTimestamp, pSample); + GST_PAD_STREAM_LOCK(pad); + if (hr == S_OK) + { + if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) + { + // Before delivering EOS, check if we actually doing reload. + if (!demux->is_eos && demux->pGSTMFByteStream->IsReload()) + { + mfdemux_reload_demux(demux, FALSE); + } + else + { + // Deliver EOS to all src pads, since source reader reports it for + // last read only and not for each stream. + mfdemux_push_sink_event(demux, gst_event_new_eos()); + result = GST_FLOW_EOS; + } + } + else if ((dwStreamFlags & MF_SOURCE_READERF_ERROR) == MF_SOURCE_READERF_ERROR) + { + gst_element_message_full(GST_ELEMENT(demux), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX, + g_strdup("ReadSample() failed (MF_SOURCE_READERF_ERROR)"), NULL, + ("mfdemux.c"), ("mfdemux_loop"), 0); + result = GST_FLOW_ERROR; + } + + if (pSample != NULL) + { + GstPad *src_pad = mfdemux_get_src_pad(demux, dwActualStreamIndex); + if (src_pad != NULL) + result = mfdemux_deliver_sample(demux, src_pad, pSample); + + SafeRelease(&pSample); + } + else if (demux->is_eos) + { + // Deliver EOS to all src pads, since source reader reports it for + // last read only and not for each stream. + mfdemux_push_sink_event(demux, gst_event_new_eos()); + result = GST_FLOW_EOS; + } + } + else + { + g_mutex_lock(&demux->lock); + result = demux->src_result; + g_mutex_unlock(&demux->lock); + + if (result != GST_FLOW_ERROR) + { + gst_element_message_full(GST_ELEMENT(demux), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DEMUX, + g_strdup_printf("ReadSample() failed (0x%X)", hr), NULL, + ("mfdemux.c"), ("mfdemux_loop"), 0); + result = GST_FLOW_ERROR; + } + } + + g_mutex_lock(&demux->lock); + if (GST_FLOW_OK == demux->src_result || GST_FLOW_OK != result) + demux->src_result = result; + else + result = demux->src_result; + g_mutex_unlock(&demux->lock); + + if (result != GST_FLOW_OK || demux->start_task_on_first_segment) + { + TRACE(DEMUX_TASK, "Pause task result=%d start_task_on_first_segment=%d\n", + result, demux->start_task_on_first_segment); + gst_pad_pause_task(pad); + } +} + +static gboolean mfdemux_activate(GstPad *pad, GstObject *parent) +{ + return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE); +} + +static gboolean mfdemux_activate_mode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active) +{ + GstMFDemux *demux = GST_MFDEMUX(parent); + gboolean res = FALSE; + + switch (mode) + { + case GST_PAD_MODE_PUSH: + res = TRUE; + break; + case GST_PAD_MODE_PULL: + if (active) + { + g_mutex_lock(&demux->lock); + demux->start_task_on_first_segment = demux->is_hls; + demux->src_result = GST_FLOW_OK; + g_mutex_unlock(&demux->lock); + + if (demux->is_hls) + { + TRACE(DEMUX_TASK, "Activate pull for HLS. start_on_event=%d\n", + demux->start_task_on_first_segment); + res = TRUE; + } + else + { + TRACE(DEMUX_TASK, "Activate pull for MP4. Starting task...\n"); + res = gst_pad_start_task(pad, (GstTaskFunction) mfdemux_loop, + pad, NULL); + } + } + else + { + g_mutex_lock(&demux->lock); + demux->start_task_on_first_segment = FALSE; + demux->src_result = GST_FLOW_ERROR; + g_mutex_unlock(&demux->lock); + + // Unblock Source Reader creation or ReadSample if it is waiting + // for data from our byte stream. + if (demux->pGSTMFByteStream) + demux->pGSTMFByteStream->AbortRead(MF_E_OPERATION_CANCELLED); + + res = gst_pad_stop_task(pad); + } + break; + default: + // Unknown scheduling mode + res = FALSE; + break; + } + + return res; +} + +gboolean mfdemux_init(GstPlugin* mfdemux) +{ + return gst_element_register(mfdemux, "mfdemux", 512, GST_TYPE_MFDEMUX); +} diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.h b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.h new file mode 100644 index 00000000000..620989aa1a4 --- /dev/null +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfdemux.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef __MF_DEMUX_H__ +#define __MF_DEMUX_H__ + +#include + +#include +#include +#include + +#include "mfgstbytestream.h" +#include "mftrace.h" + +#include "fxplugins_common.h" + +typedef struct { + JFX_CODEC_ID codecID; + UINT32 uiChannels; + UINT32 uiRate; + GstBuffer *codec_data; +} AudioFormat; + +typedef struct { + JFX_CODEC_ID codecID; + UINT32 uiWidth; + UINT32 uiHeight; + UINT32 uiFrameRateNum; + UINT32 uiFrameRateDen; + UINT32 uiInterlaceMode; + UINT32 uiPixelAspectRatioNum; + UINT32 uiPixelAspectRatioDen; + UINT32 uiMPEG2Profile; + UINT32 uiMPEG2Level; + GstBuffer *sequence_header; +} VideoFormat; + +G_BEGIN_DECLS + +#define GST_TYPE_MFDEMUX \ + (gst_mfdemux_get_type()) +#define GST_MFDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MFDEMUX,GstMFDemux)) +#define GST_MFDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MFDEMUX,GstMFDemuxClass)) +#define GST_IS_MFDEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MFDEMUX)) +#define GST_IS_MFDEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MFDEMUX)) + +typedef struct _GstMFDemux GstMFDemux; +typedef struct _GstMFDemuxClass GstMFDemuxClass; + +struct _GstMFDemux +{ + GstElement element; + + GMutex lock; + + GstFlowReturn src_result; + + GstPad *sink_pad; // input pad + + HRESULT hr_mfstartup; + + gboolean is_flushing; + gboolean is_eos; + gboolean force_discontinuity; + gboolean is_demux_initialized; + gboolean send_new_segment; + gboolean start_task_on_first_segment; + gboolean is_hls; +#if TRACE_ENABLE + gboolean log_first_audio_pts; + gboolean log_first_video_pts; + GstClockTime last_audio_pts; + GstClockTime last_audio_dur; + GstClockTime last_video_pts; + GstClockTime last_video_dur; +#endif + + + gdouble rate; + gint64 seek_position; + + CMFGSTByteStream *pGSTMFByteStream; + IMFByteStream *pIMFByteStream; + IMFSourceReader *pSourceReader; + + LONGLONG llDuration; + + AudioFormat audioFormat; + VideoFormat videoFormat; + + GstPad *audio_src_pad; + GstPad *video_src_pad; + + DWORD audio_stream_index; + DWORD video_stream_index; + + GstEvent *cached_segment_event; +}; + +struct _GstMFDemuxClass +{ + GstElementClass parent_class; +}; + +GType gst_mfdemux_get_type(void); + +gboolean mfdemux_init(GstPlugin* mfdemux); + +G_END_DECLS + +#endif // __MF_DEMUX_H__ diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.cpp b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.cpp index 95c03612f7f..92622a98888 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.cpp +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,50 @@ #include "mfgstbuffer.h" CMFGSTBuffer::CMFGSTBuffer(DWORD cbMaxLength) +{ + Init(); + + m_cbMaxLength = cbMaxLength; +} + +// pGstBuffer will be ref, so caller needs to unref buffer if no +// longer needed. +CMFGSTBuffer::CMFGSTBuffer(HRESULT &hr, GstBuffer *pGstBuffer) +{ + Init(); + + if (pGstBuffer == NULL) + { + hr = E_POINTER; + return; + } + + // Map buffer + if (!gst_buffer_map(pGstBuffer, &m_GstMapInfo, GST_MAP_READWRITE)) + { + hr = E_FAIL; + return; + } + + // Set unmap flag, so AllocateOrGetBuffer() does not map it again. + m_bUnmapGstBuffer = TRUE; + + // If we mapped buffer, then update our max and data length from buffer. + m_cbMaxLength = m_GstMapInfo.maxsize; + m_cbCurrentLength = m_GstMapInfo.size; + + // Keep reference to buffer, so it does not gets released. + // INLINE - gst_buffer_ref() + m_pGstBuffer = gst_buffer_ref(pGstBuffer); +} + +void CMFGSTBuffer::Init() { m_ulRefCount = 0; m_ulLockCount = 0; InitializeCriticalSection(&m_csBufferLock); - m_cbMaxLength = cbMaxLength; m_cbCurrentLength = 0; m_pbBuffer = NULL; @@ -243,7 +280,7 @@ HRESULT CMFGSTBuffer::AllocateOrGetBuffer(BYTE **ppbBuffer) return E_INVALIDARG; // If we have GStreamer get buffer callback set, then call it to get - // buffer. Otherwise allocate memory internally. + // buffer if we do not have one. if (GetGstBufferCallback != NULL) { // Get buffer if needed @@ -253,7 +290,12 @@ HRESULT CMFGSTBuffer::AllocateOrGetBuffer(BYTE **ppbBuffer) if (m_pGstBuffer == NULL) return E_OUTOFMEMORY; } + } + // If we have m_pGstBuffer, just use it. It might be from callback or + // provided to us via constructor. + if (m_pGstBuffer) + { // Lock can be called multiple times, so if we have GStreamer buffer // allocated and mapped just return it. if (m_bUnmapGstBuffer) diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.h b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.h index 4505dbac041..2865014bc52 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.h +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbuffer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,6 +43,7 @@ class CMFGSTBuffer : public IMFMediaBuffer { public: CMFGSTBuffer(DWORD cbMaxLength); + CMFGSTBuffer(HRESULT &hr, GstBuffer *pGstBuffer); ~CMFGSTBuffer(); // IMFMediaBuffer @@ -64,6 +65,7 @@ class CMFGSTBuffer : public IMFMediaBuffer long lSize, sCallbackData *pCallbackData)); private: + void Init(); HRESULT AllocateOrGetBuffer(BYTE **ppbBuffer); ULONG m_ulRefCount; diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.cpp b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.cpp new file mode 100644 index 00000000000..3865a3f0ed2 --- /dev/null +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.cpp @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "mfgstbytestream.h" +#include "mftrace.h" + +template void SafeRelease(T **ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + +CMFGSTByteStream::CMFGSTByteStream(QWORD qwLength, GstPad *pSinkPad, BOOL bIsSegmentedStream) +{ + m_ulRefCount = 0; + + m_bIsSegmentedStream = bIsSegmentedStream; + m_qwLength = qwLength; + m_pSinkPad = pSinkPad; + + Reset(); + + InitializeCriticalSection(&m_csLock); +} + +CMFGSTByteStream::~CMFGSTByteStream() +{ + DeleteCriticalSection(&m_csLock); +} + +void CMFGSTByteStream::Reset() +{ + m_pBytes = NULL; + m_cbBytes = 0; + m_cbBytesRead = 0; + m_pAsyncResult = NULL; + m_readResult = S_OK; + m_qwPosition = 0; + m_bWaitForEvent = FALSE; + m_bIsAborted = FALSE; + m_bIsEOS = FALSE; + m_bIsEOSEventReceived = FALSE; +} + +HRESULT CMFGSTByteStream::ReadRangeAvailable() +{ + Lock(); + BOOL bWaitForEvent = m_bWaitForEvent; + m_bWaitForEvent = FALSE; + Unlock(); + + if (bWaitForEvent) + return ReadData(); + else + return S_FALSE; +} + +void CMFGSTByteStream::SetStreamLength(QWORD qwLength) +{ + Lock(); + m_qwLength = qwLength; + Unlock(); +} + +// Even if we reporting MFBYTESTREAM_IS_SEEKABLE to MF to make it happy (will not +// initialized otherwise), we can only issue seek on MF source reader if length +// is known (HTTP/FILE). For HLS we wil forward seek event upstream to handle +// seek. +bool CMFGSTByteStream::IsSeekSupported() +{ + return !m_bIsSegmentedStream; +} + +HRESULT CMFGSTByteStream::CompleteReadData(HRESULT hr) +{ + m_readResult = hr; + + if (m_pAsyncResult) + return MFInvokeCallback(m_pAsyncResult); + + return S_OK; +} + +HRESULT CMFGSTByteStream::AbortRead(HRESULT hr) +{ + Lock(); + m_bIsAborted = TRUE; + m_bWaitForEvent = FALSE; + Unlock(); + + return CompleteReadData(hr); +} + +void CMFGSTByteStream::SignalEOS() +{ + Lock(); + m_bIsEOSEventReceived = TRUE; + Unlock(); +} + +void CMFGSTByteStream::ClearEOS() +{ + Lock(); + m_bIsEOS = FALSE; + m_bIsEOSEventReceived = FALSE; + Unlock(); +} + +BOOL CMFGSTByteStream::IsReload() +{ + bool bIsReload = false; + if (m_bIsSegmentedStream && !m_bIsEOSEventReceived) + bIsReload = true; + + return bIsReload; +} + +// IMFByteStream +HRESULT CMFGSTByteStream::BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, IUnknown *punkState) +{ + HRESULT hr = S_OK; + + if (pb == NULL || pCallback == NULL) + return E_POINTER; + + if (m_pSinkPad == NULL) + return E_POINTER; + + Lock(); + BOOL bIsAborted = m_bIsAborted; + Unlock(); + + if (bIsAborted) + return MF_E_OPERATION_CANCELLED; + + // Reject BeginRead() if we already have pending read + if (m_pAsyncResult != NULL) + return MF_E_INVALIDREQUEST; + + // Do not start new read if old one failed + if (m_readResult != S_OK) + return m_readResult; + + // Save read request + m_pBytes = pb; + m_cbBytes = cb; + m_cbBytesRead = 0; + + // Create async result object to signal read completion + hr = MFCreateAsyncResult(NULL, pCallback, punkState, &m_pAsyncResult); + if (FAILED(hr)) + return hr; + + return ReadData(); +} + +HRESULT CMFGSTByteStream::BeginWrite(const BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, IUnknown *punkState) +{ + return E_NOTIMPL; +} + +HRESULT CMFGSTByteStream::Close() +{ + // Nothing to close + return S_OK; +} + +HRESULT CMFGSTByteStream::EndRead(IMFAsyncResult *pResult, ULONG *pcbRead) +{ + Lock(); + m_bWaitForEvent = FALSE; + Unlock(); + + if (pResult == NULL || pcbRead == NULL) + return E_POINTER; + + if (pResult != NULL) + pResult->SetStatus(m_readResult); + + *pcbRead = m_cbBytesRead; + + SafeRelease(&m_pAsyncResult); + + return S_OK; +} + +HRESULT CMFGSTByteStream::EndWrite(IMFAsyncResult *pResult, ULONG *pcbWritten) +{ + return E_NOTIMPL; +} + +HRESULT CMFGSTByteStream::Flush() +{ + return AbortRead(MF_E_OPERATION_CANCELLED); +} + +HRESULT CMFGSTByteStream::GetCapabilities(DWORD *pdwCapabilities) +{ + if (pdwCapabilities == NULL) + return E_POINTER; + + (*pdwCapabilities) = MFBYTESTREAM_IS_READABLE | + MFBYTESTREAM_IS_SEEKABLE | + MFBYTESTREAM_IS_REMOTE; + return S_OK; +} + +HRESULT CMFGSTByteStream::GetCurrentPosition(QWORD *pqwPosition) +{ + if (pqwPosition == NULL) + return E_POINTER; + + (*pqwPosition) = m_qwPosition; + + return S_OK; +} + +HRESULT CMFGSTByteStream::GetLength(QWORD *pqwLength) +{ + if (pqwLength == NULL) + return E_FAIL; + + (*pqwLength) = m_qwLength; + + return S_OK; +} + +HRESULT CMFGSTByteStream::IsEndOfStream(BOOL *pfEndOfStream) +{ + if (pfEndOfStream == NULL) + return E_POINTER; + + if (m_bIsEOS) + (*pfEndOfStream) = TRUE; + else if (m_qwPosition >= m_qwLength) + (*pfEndOfStream) = TRUE; + else + (*pfEndOfStream) = FALSE; + + return S_OK; +} + +HRESULT CMFGSTByteStream::Read(BYTE *pb, ULONG cb, ULONG *pcbRead) +{ + return E_NOTIMPL; +} + +HRESULT CMFGSTByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, LONGLONG llSeekOffset, DWORD dwSeekFlags, QWORD *pqwCurrentPosition) +{ + HRESULT hr = S_OK; + + if (pqwCurrentPosition == NULL) + return E_POINTER; + + QWORD qwSeekPosition = 0; + switch (SeekOrigin) + { + case msoBegin: + qwSeekPosition = llSeekOffset; + break; + case msoCurrent: + qwSeekPosition = m_qwPosition + llSeekOffset; + break; + default: + return E_FAIL; + } + + hr = SetCurrentPosition(qwSeekPosition); + if (FAILED(hr)) + return hr; + + *pqwCurrentPosition = m_qwPosition; + + return S_OK; +} + +HRESULT CMFGSTByteStream::SetCurrentPosition(QWORD qwPosition) +{ + if (qwPosition > m_qwLength) + { + return E_INVALIDARG; + } + + if (m_qwPosition == qwPosition) + { + return S_OK; + } + + m_qwPosition = qwPosition; + + return S_OK; +} + +HRESULT CMFGSTByteStream::SetLength(QWORD qwLength) +{ + return E_NOTIMPL; +} + +HRESULT CMFGSTByteStream::Write(const BYTE *pb, ULONG cb, ULONG *pcbWritten) +{ + return E_NOTIMPL; +} + +// IUnknown +HRESULT CMFGSTByteStream::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + { + return E_POINTER; + } + else if (riid == IID_IUnknown) + { + *ppvObject = static_cast(static_cast(this)); + } + else if (riid == IID_IMFByteStream) + { + *ppvObject = static_cast(this); + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG CMFGSTByteStream::AddRef() +{ + return InterlockedIncrement(&m_ulRefCount); +} + +ULONG CMFGSTByteStream::Release() +{ + ULONG uCount = InterlockedDecrement(&m_ulRefCount); + if (uCount == 0) + delete this; + return uCount; +} + +HRESULT CMFGSTByteStream::ReadData() +{ + HRESULT hr = S_OK; + GstFlowReturn ret = GST_FLOW_ERROR; + GstBuffer *buf = NULL; + guint64 offset = 0; + ULONG cbBytes = 0; + + // Read data from upstream + do + { + // If length known adjust m_cbBytes to make sure we do not read + // pass EOS. "progressbuffer" or "hlsprogressbuffer" does not handle + // last buffer nicely and will return EOS if we do not read exact + // amount of data. + Lock(); + if (m_bIsAborted) + { + Unlock(); + return CompleteReadData(MF_E_OPERATION_CANCELLED); + } + + if (m_qwPosition < m_qwLength && (m_qwPosition + m_cbBytes) > m_qwLength) + m_cbBytes = m_qwLength - m_qwPosition; + + if (m_cbBytesRead < m_cbBytes) + { + cbBytes = m_cbBytes - m_cbBytesRead; + } + else + { + Unlock(); + return CompleteReadData(E_FAIL); + } + + offset = (guint64)m_qwPosition; + + Unlock(); + + ret = gst_pad_pull_range(m_pSinkPad, offset, (guint)cbBytes, &buf); + if (ret == GST_FLOW_FLUSHING) + { + // Wait for FX_EVENT_RANGE_READY. It will be send when data available. + return PrepareWaitForData(); + } + else if (ret == GST_FLOW_EOS) + { + m_bIsEOS = TRUE; + return CompleteReadData(S_OK); + } + else if (ret == GST_FLOW_OK) + { + hr = PushDataBuffer(buf); + if (FAILED(hr)) + return CompleteReadData(E_FAIL); + else if (m_cbBytesRead == m_cbBytes || m_bIsEOS) + return CompleteReadData(S_OK); + } + else + { + return CompleteReadData(E_FAIL); + } + } while (SUCCEEDED(hr) && m_cbBytesRead < m_cbBytes); + + return hr; +} + +HRESULT CMFGSTByteStream::PushDataBuffer(GstBuffer* pBuffer) +{ + HRESULT hr = S_OK; + + if (pBuffer == NULL) + return E_POINTER; + + // Set EOS flag, so we can complete and signal EOS + Lock(); + if (m_bIsEOSEventReceived) + m_bIsEOS = TRUE; + Unlock(); + + GstMapInfo info; + gboolean unmap = FALSE; + if (gst_buffer_map(pBuffer, &info, GST_MAP_READ)) + unmap = TRUE; + else + hr = E_FAIL; + + if (SUCCEEDED(hr) && m_cbBytesRead >= m_cbBytes) + hr = E_FAIL; + + if (SUCCEEDED(hr) && (m_cbBytes - m_cbBytesRead) < info.size) + hr = E_FAIL; + + if (SUCCEEDED(hr) && + memcpy_s(m_pBytes + m_cbBytesRead, m_cbBytes - m_cbBytesRead, + info.data, info.size) != 0) + { + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) + { + m_cbBytesRead += info.size; + m_qwPosition += info.size; + } + + if (unmap) + gst_buffer_unmap(pBuffer, &info); + + // INLINE - gst_buffer_unref() + gst_buffer_unref(pBuffer); + + return hr; +} + +HRESULT CMFGSTByteStream::PrepareWaitForData() +{ + Lock(); + m_bWaitForEvent = TRUE; + Unlock(); + + return S_OK; +} + +void CMFGSTByteStream::Lock() +{ + EnterCriticalSection(&m_csLock); +} + +void CMFGSTByteStream::Unlock() +{ + LeaveCriticalSection(&m_csLock); +} diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.h b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.h new file mode 100644 index 00000000000..10f9a322b94 --- /dev/null +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfgstbytestream.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef __MF_GST_BYTESTREAM_H__ +#define __MF_GST_BYTESTREAM_H__ + +#include + +#include +#include +#include +#include + +// {00000000-0000-0000-0000-000000000000} +static const GUID GUID_NULL = +{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; + +class CMFGSTByteStream : public IMFByteStream +{ +public: + CMFGSTByteStream(QWORD qwLength, GstPad *pSinkPad, BOOL bIsSegmentedStream); + ~CMFGSTByteStream(); + + void Reset(); + HRESULT ReadRangeAvailable(); + void SetStreamLength(QWORD qwLength); + bool IsSeekSupported(); + HRESULT CompleteReadData(HRESULT hr); + void SignalEOS(); + void ClearEOS(); + BOOL IsReload(); + HRESULT AbortRead(HRESULT hr); + + // IMFByteStream + HRESULT BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState); + HRESULT BeginWrite(const BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState); + HRESULT Close(); + HRESULT EndRead(IMFAsyncResult *pResult, ULONG *pcbRead); + HRESULT EndWrite(IMFAsyncResult *pResult, ULONG *pcbWritten); + HRESULT Flush(); + HRESULT GetCapabilities(DWORD *pdwCapabilities); + HRESULT GetCurrentPosition(QWORD *pqwPosition); + HRESULT GetLength(QWORD *pqwLength); + HRESULT IsEndOfStream(BOOL *pfEndOfStream); + HRESULT Read(BYTE *pb, ULONG cb, ULONG *pcbRead); + HRESULT Seek(MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, LONGLONG llSeekOffset, + DWORD dwSeekFlags, QWORD *pqwCurrentPosition); + HRESULT SetCurrentPosition(QWORD qwPosition); + HRESULT SetLength(QWORD qwLength); + HRESULT Write(const BYTE *pb, ULONG cb, ULONG *pcbWritten); + + // IUnknown + HRESULT QueryInterface(REFIID riid, void **ppvObject); + ULONG AddRef(); + ULONG Release(); + +private: + HRESULT ReadData(); + HRESULT PushDataBuffer(GstBuffer *pBuffer); + HRESULT PrepareWaitForData(); + + void Lock(); + void Unlock(); + + ULONG m_ulRefCount; + + QWORD m_qwPosition; + QWORD m_qwLength; + + // Pointer to store read bytes + BYTE *m_pBytes; + // Total number of bytes requested + ULONG m_cbBytes; + // Bytes read and stored in m_pBytes + ULONG m_cbBytesRead; + // Completion result + IMFAsyncResult *m_pAsyncResult; + // Read result + HRESULT m_readResult; + + BOOL m_bWaitForEvent; + BOOL m_bIsAborted; + BOOL m_bIsEOS; + BOOL m_bIsEOSEventReceived; + // Set to true if source is fragmented MP4 + BOOL m_bIsSegmentedStream; + + CRITICAL_SECTION m_csLock; + + GstPad *m_pSinkPad; +}; + +#endif // __MF_GST_BYTESTREAM_H__ diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mftrace.h b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mftrace.h new file mode 100644 index 00000000000..f3ec2972351 --- /dev/null +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mftrace.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef __MF_TRACE_H__ +#define __MF_TRACE_H__ + +#include +#include +#include + +#define TRACE_ENABLE 0 + +// Enable trace categories +// Decoder (mfwrapper) +#define DECODER_SINK_EVENTS 0 +#define DECODER_FIRST_PTS 0 +#define DECODER_OUTPUT_PTS 0 +#define DECODER_INPUT_PTS 0 + +// Demux +#define DEMUX_SINK_EVENTS 0 +#define DEMUX_SRC_EVENTS 0 +#define DEMUX_RELOAD 0 +#define DEMUX_READ_SAMPLE 0 +#define DEMUX_FIRST_AND_LAST_PTS 0 +#define DEMUX_OUTPUT_PTS 0 +#define DEMUX_TASK 0 + +// Byte Stream +#define BYTE_STREAM 0 + +#if TRACE_ENABLE +static inline const char *trace_filename(const char *path) +{ + const char *p; + + p = strrchr(path, '\\'); + if (p) + return p + 1; + + p = strrchr(path, '/'); + if (p) + return p + 1; + + return path; +} + +static inline void trace_impl( + const char *file, + int line, + const char *func, + const char *fmt, + ...) +{ + va_list ap; + + fprintf(stderr, "JFXMEDIA [%s:%d %s] ", + trace_filename(file), + line, + func); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#define TRACE(cat, fmt, ...) \ + do { \ + if (cat) { \ + trace_impl(__FILE__, __LINE__, __func__, \ + fmt, ##__VA_ARGS__); \ + } \ + } while (0) +#else // MF_TRACE_ENABLE +#define TRACE(cat, fmt, ...) ((void)0) +#endif //MF_TRACE_ENABLE + +#endif // __MF_TRACE_H__ diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.cpp b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.cpp index c72d1ff2b41..e210d462889 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.cpp +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.cpp @@ -33,13 +33,13 @@ #include #include "mfwrapper.h" +#include "mftrace.h" #include #include #include "fxplugins_common.h" -#define PTS_DEBUG 0 #define MEDIA_FORMAT_DEBUG 0 // 3 buffers is enough for rendering. During testing 2 buffers is actually @@ -58,6 +58,15 @@ enum PROP_IS_SUPPORTED, }; +// Process input return values +enum +{ + PI_DELIVERED, + PI_NOTACCEPTING, + PI_FAILED, +}; + +// Process output return values enum { PO_DELIVERED, @@ -75,6 +84,8 @@ GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS( + // H.264 + "video/x-h264; " // H.265 "video/x-h265" )); @@ -204,6 +215,9 @@ static void gst_mfwrapper_init(GstMFWrapper *decoder) decoder->is_decoder_error = FALSE; decoder->is_force_discontinuity = FALSE; decoder->is_force_output_discontinuity = FALSE; +#if TRACE_ENABLE + decoder->trace_first_input_sample = TRUE; +#endif // Initialize Media Foundation bool bCallCoUninitialize = true; @@ -217,6 +231,7 @@ static void gst_mfwrapper_init(GstMFWrapper *decoder) CoUninitialize(); decoder->pDecoder = NULL; + decoder->pDecoderInputType = NULL; decoder->pDecoderOutput = NULL; decoder->pDecoderBuffer = NULL; @@ -229,12 +244,11 @@ static void gst_mfwrapper_init(GstMFWrapper *decoder) decoder->pool = NULL; - decoder->header = NULL; - decoder->header_size = 0; - decoder->is_send_header = FALSE; - decoder->width = 1920; decoder->height = 1080; + // Should be 0 by default + decoder->visibleWidth = 0; + decoder->visibleHeight = 0; decoder->framerate_num = 2997; decoder->framerate_den = 100; @@ -249,13 +263,7 @@ static void gst_mfwrapper_dispose(GObject* object) { GstMFWrapper *decoder = GST_MFWRAPPER(object); - if (decoder->header != NULL) - { - delete[] decoder->header; - decoder->header = NULL; - decoder->header_size = 0; - } - + SafeRelease(&decoder->pDecoderInputType); SafeRelease(&decoder->pDecoderOutput); // No need to free pDecoderBuffer, it will be released when // pDecoderOutput is released. @@ -317,18 +325,34 @@ static void gst_mfwrapper_get_property(GObject *object, guint property_id, GValu static gboolean mfwrapper_is_decoder_by_codec_id_supported(GstMFWrapper *decoder, gint codec_id) { HRESULT hr = S_FALSE; + GstCaps *caps = NULL; switch (codec_id) { - case JFX_CODEC_ID_H265: - // Dummy caps to load H.265 decoder - GstCaps *caps = gst_caps_new_simple("video/x-h265", - "width", G_TYPE_INT, 1920, - "height", G_TYPE_INT, 1080, - NULL); + case JFX_CODEC_ID_H264: + { + // Dummy caps to load H.264 decoder + caps = gst_caps_new_simple("video/x-h264", + "width", G_TYPE_INT, 1920, + "height", G_TYPE_INT, 1080, + NULL); + break; + } + case JFX_CODEC_ID_HEVC: + { + // Dummy caps to load H.265 decoder + caps = gst_caps_new_simple("video/x-h265", + "width", G_TYPE_INT, 1920, + "height", G_TYPE_INT, 1080, + NULL); + break; + } + } + + if (caps) + { hr = mfwrapper_load_decoder_caps(decoder, caps); gst_caps_unref(caps); - break; } if (hr == S_OK) @@ -337,7 +361,8 @@ static gboolean mfwrapper_is_decoder_by_codec_id_supported(GstMFWrapper *decoder return FALSE; } -static HRESULT mfwrapper_create_sample(IMFSample **ppSample, DWORD dwSize, CMFGSTBuffer **ppMFGSTBuffer) +static HRESULT mfwrapper_create_sample(IMFSample **ppSample, DWORD dwSize, + CMFGSTBuffer **ppMFGSTBuffer) { if (ppSample == NULL || dwSize == 0 || ppMFGSTBuffer == NULL) return E_INVALIDARG; @@ -364,26 +389,89 @@ static HRESULT mfwrapper_create_sample(IMFSample **ppSample, DWORD dwSize, CMFGS return S_OK; } +static HRESULT mfwrapper_create_sample_from_gst_buffer(IMFSample **ppSample, + GstBuffer *buf, + gboolean &bDiscont) +{ + if (ppSample == NULL || buf == NULL) + return E_INVALIDARG; + + HRESULT hr = MFCreateSample(ppSample); + if (SUCCEEDED(hr)) + { + CMFGSTBuffer *pMFGSTBuffer = new (nothrow) CMFGSTBuffer(hr, buf); + if (pMFGSTBuffer == NULL) + return E_OUTOFMEMORY; + + if (FAILED(hr)) + { + delete pMFGSTBuffer; + return hr; + } + + IMFMediaBuffer *pBuffer = NULL; + hr = pMFGSTBuffer->QueryInterface(IID_IMFMediaBuffer, (void **)&pBuffer); + if (FAILED(hr) || pBuffer == NULL) + { + delete pMFGSTBuffer; + return E_NOINTERFACE; + } + + hr = (*ppSample)->AddBuffer(pBuffer); + SafeRelease(&pBuffer); + + // Copy metadata from GStreamer to Media Foundation sample + if (SUCCEEDED(hr) && GST_BUFFER_PTS_IS_VALID(buf)) + hr = (*ppSample)->SetSampleTime(GST_BUFFER_PTS(buf) / 100); + + if (SUCCEEDED(hr) && GST_BUFFER_DURATION_IS_VALID(buf)) + hr = (*ppSample)->SetSampleDuration(GST_BUFFER_DURATION(buf) / 100); + + if (SUCCEEDED(hr) && + (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT) || bDiscont)) + { + hr = (*ppSample)->SetUINT32(MFSampleExtension_Discontinuity, TRUE); + bDiscont = FALSE; + } + + if (SUCCEEDED(hr)) + { + hr = (*ppSample)->SetUINT32(MFSampleExtension_CleanPoint, + GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT) ? FALSE : TRUE); + } + } + + return hr; +} + static void mfwrapper_set_src_caps(GstMFWrapper *decoder) { GstCaps *srcCaps = NULL; HRESULT hr = S_OK; MFT_OUTPUT_STREAM_INFO outputStreamInfo; + gint strideY = decoder->defaultStride != 0 ? decoder->defaultStride : decoder->width; + gint strideU = strideY / 2; + gint strideV = strideY / 2; + + gint offsetY = 0; + gint offsetU = strideY * decoder->height; + gint offsetV = offsetU + strideU * (decoder->height / 2); + GstCaps *padCaps = gst_pad_get_current_caps(decoder->srcpad); if (padCaps == NULL) { srcCaps = gst_caps_new_simple("video/x-raw-yuv", "format", G_TYPE_STRING, "YV12", "framerate", GST_TYPE_FRACTION, decoder->framerate_num, decoder->framerate_den, - "width", G_TYPE_INT, decoder->width, - "height", G_TYPE_INT, decoder->height, - "offset-y", G_TYPE_INT, 0, - "offset-v", G_TYPE_INT, (decoder->width * decoder->height + ((decoder->width * decoder->height) / 4)), - "offset-u", G_TYPE_INT, decoder->width * decoder->height, - "stride-y", G_TYPE_INT, decoder->width, - "stride-v", G_TYPE_INT, decoder->width / 2, - "stride-u", G_TYPE_INT, decoder->width / 2, + "width", G_TYPE_INT, decoder->visibleWidth ? decoder->visibleWidth : decoder->width, + "height", G_TYPE_INT, decoder->visibleHeight ? decoder->visibleHeight : decoder->height, + "offset-y", G_TYPE_INT, offsetY, + "offset-v", G_TYPE_INT, offsetV, + "offset-u", G_TYPE_INT, offsetU, + "stride-y", G_TYPE_INT, strideY, + "stride-v", G_TYPE_INT, strideU, + "stride-u", G_TYPE_INT, strideV, NULL); } else @@ -394,14 +482,14 @@ static void mfwrapper_set_src_caps(GstMFWrapper *decoder) return; gst_caps_set_simple(srcCaps, - "width", G_TYPE_INT, decoder->width, - "height", G_TYPE_INT, decoder->height, - "offset-y", G_TYPE_INT, 0, - "offset-v", G_TYPE_INT, (decoder->width * decoder->height + ((decoder->width * decoder->height) / 4)), - "offset-u", G_TYPE_INT, decoder->width * decoder->height, - "stride-y", G_TYPE_INT, decoder->width, - "stride-v", G_TYPE_INT, decoder->width / 2, - "stride-u", G_TYPE_INT, decoder->width / 2, + "width", G_TYPE_INT, decoder->visibleWidth ? decoder->visibleWidth : decoder->width, + "height", G_TYPE_INT, decoder->visibleHeight ? decoder->visibleHeight : decoder->height, + "offset-y", G_TYPE_INT, offsetY, + "offset-v", G_TYPE_INT, offsetV, + "offset-u", G_TYPE_INT, offsetU, + "stride-y", G_TYPE_INT, strideY, + "stride-v", G_TYPE_INT, strideU, + "stride-u", G_TYPE_INT, strideV, NULL); } @@ -489,146 +577,36 @@ static void mfwrapper_print_output_media_formats(IMFTransform *pMFTrasnform, con } #endif // MEDIA_FORMAT_DEBUG -// Return S_FALSE if we fail to convert. S_FALSE is not critical error, -// but will signal caller, that something is wrong with buffer. -static HRESULT mfwrapper_nalu_to_start_code(BYTE *pbBuffer, gsize size) -{ - gsize leftSize = size; - - if (pbBuffer == NULL || size < NALU_LENGTH_SIZE) - return S_FALSE; - - do - { - guint naluLen = ((guint)*(guint8*)pbBuffer) << 24; - naluLen |= ((guint)*(guint8*)(pbBuffer + 1)) << 16; - naluLen |= ((guint)*(guint8*)(pbBuffer + 2)) << 8; - naluLen |= ((guint)*(guint8*)(pbBuffer + 3)); - - // Start code or something wrong. - // naluLen cannot be more then data we have to process. - if (naluLen <= 1 || naluLen > (leftSize - NALU_LENGTH_SIZE)) - return S_FALSE; - - pbBuffer[0] = 0x00; - pbBuffer[1] = 0x00; - pbBuffer[2] = 0x00; - pbBuffer[3] = 0x01; - - leftSize -= (naluLen + NALU_LENGTH_SIZE); - pbBuffer += (naluLen + NALU_LENGTH_SIZE); - - } while (leftSize > NALU_LENGTH_SIZE); - - return leftSize == 0 ? S_OK : S_FALSE; -} - -static gboolean mfwrapper_process_input(GstMFWrapper *decoder, GstBuffer *buf) +static gint mfwrapper_process_input(GstMFWrapper *decoder, GstBuffer *buf) { IMFSample *pSample = NULL; - IMFMediaBuffer *pBuffer = NULL; - DWORD dwBufferSize = 0; - BYTE *pbBuffer = NULL; - GstMapInfo info; - HRESULT hrDeliverBuffer = S_OK; - gboolean unmap_buf = FALSE; - gboolean unlock_buf = FALSE; if (!decoder->pDecoder) - return FALSE; - - HRESULT hr = MFCreateSample(&pSample); - - if (SUCCEEDED(hr) && decoder->is_force_discontinuity) - { - hr = pSample->SetUINT32(MFSampleExtension_Discontinuity, TRUE); - decoder->is_force_discontinuity = FALSE; - } - - if (SUCCEEDED(hr) && GST_BUFFER_PTS_IS_VALID(buf)) - hr = pSample->SetSampleTime(GST_BUFFER_PTS(buf) / 100); - - if (SUCCEEDED(hr) && GST_BUFFER_DURATION_IS_VALID(buf)) - hr = pSample->SetSampleDuration(GST_BUFFER_DURATION(buf) / 100); - - if (SUCCEEDED(hr) && gst_buffer_map(buf, &info, GST_MAP_READ)) - unmap_buf = TRUE; - else - hr = E_FAIL; - - if (SUCCEEDED(hr) && decoder->is_send_header && - decoder->header != NULL && decoder->header_size > 0) - dwBufferSize = (DWORD)decoder->header_size + (DWORD)info.size; - else if (SUCCEEDED(hr)) - dwBufferSize = (DWORD)info.size; + return PI_FAILED; - if (SUCCEEDED(hr)) - hr = MFCreateMemoryBuffer(dwBufferSize, &pBuffer); - - if (SUCCEEDED(hr)) - hr = pBuffer->SetCurrentLength(dwBufferSize); - - if (SUCCEEDED(hr)) - hr = pBuffer->Lock(&pbBuffer, NULL, NULL); + HRESULT hr = mfwrapper_create_sample_from_gst_buffer(&pSample, buf, + decoder->force_discontinuity); if (SUCCEEDED(hr)) - unlock_buf = TRUE; + hr = decoder->pDecoder->ProcessInput(0, pSample, 0); - if (SUCCEEDED(hr) && decoder->is_send_header && - decoder->header != NULL && decoder->header_size > 0) - { - decoder->is_send_header = FALSE; - if (dwBufferSize >= decoder->header_size) - { - memcpy_s(pbBuffer, dwBufferSize, decoder->header, decoder->header_size); - pbBuffer += decoder->header_size; - dwBufferSize -= decoder->header_size; + SafeRelease(&pSample); - if (dwBufferSize >= info.size) - { - memcpy_s(pbBuffer, dwBufferSize, info.data, info.size); - hrDeliverBuffer = mfwrapper_nalu_to_start_code(pbBuffer, info.size); - } - else - { - hr = E_FAIL; - } - } - else - { - hr = E_FAIL; - } - } - else if (SUCCEEDED(hr)) + if (hr == MF_E_NOTACCEPTING) { - memcpy_s(pbBuffer, dwBufferSize, info.data, info.size); - hrDeliverBuffer = mfwrapper_nalu_to_start_code(pbBuffer, info.size); + return PI_NOTACCEPTING; } - - if (unlock_buf) - hr = pBuffer->Unlock(); - - if (unmap_buf) - gst_buffer_unmap(buf, &info); - - // mfwrapper_nalu_to_start_code() can return S_FALSE, so deliver - // buffer only if we have S_OK. - if (SUCCEEDED(hr) && hrDeliverBuffer == S_OK) + else if (FAILED(hr)) // If ProcessInput() fails it is critical and we cannot decode { - hr = pSample->AddBuffer(pBuffer); - if (SUCCEEDED(hr)) - hr = decoder->pDecoder->ProcessInput(0, pSample, 0); + decoder->is_decoder_error = TRUE; + gst_element_message_full(GST_ELEMENT(decoder), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DECODE, + g_strdup_printf("Failed to decode stream (0x%X)", hr), NULL, + ("mfwrapper.c"), ("mfwrapper_process_input"), 0); + return PI_FAILED; } - gst_buffer_unref(buf); - - SafeRelease(&pBuffer); - SafeRelease(&pSample); - - if (SUCCEEDED(hr)) - return TRUE; - else - return FALSE; + return PI_DELIVERED; } static HRESULT mfwrapper_configure_colorconvert_input_type(GstMFWrapper *decoder, @@ -1020,6 +998,34 @@ static HRESULT mfwrapper_configure_buffer_pool(GstMFWrapper *decoder) return S_OK; } +static gboolean mfwrapper_get_minimum_display_aperture(IMFMediaType *pMediaType, + UINT32 *pVisibleWidth, + UINT32 *pVisibleHeight) +{ + if (pMediaType == NULL || pVisibleWidth == NULL || pVisibleHeight == NULL) + return FALSE; + + MFVideoArea area; + UINT32 cbBlobSize = 0; + + HRESULT hr = pMediaType->GetBlobSize(MF_MT_MINIMUM_DISPLAY_APERTURE, + &cbBlobSize); + if (FAILED(hr) || cbBlobSize != sizeof(MFVideoArea)) + return FALSE; + + hr = pMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, + reinterpret_cast(&area), + sizeof(MFVideoArea), + NULL); + if (FAILED(hr)) + return FALSE; + + *pVisibleWidth = area.Area.cx; + *pVisibleHeight = area.Area.cy; + + return TRUE; +} + static HRESULT mfwrapper_set_decoder_output_type(GstMFWrapper *decoder, IMFMediaType *pOutputType, gboolean bInitColorConverter) @@ -1030,6 +1036,8 @@ static HRESULT mfwrapper_set_decoder_output_type(GstMFWrapper *decoder, GUID currentSubType; guint width = 0; guint height = 0; + guint visibleWidth = 0; + guint visibleHeight = 0; if (decoder == NULL && pOutputType == NULL) return E_POINTER; @@ -1085,12 +1093,28 @@ static HRESULT mfwrapper_set_decoder_output_type(GstMFWrapper *decoder, } hr = S_OK; // Ok if we do not have above attribute + // Get visible resolution. It should be used instead for caps, since + // MF via MF_MT_FRAME_SIZE provides encoded resolution for H.264 in same cases. + if (mfwrapper_get_minimum_display_aperture( + pOutputType, &visibleWidth, &visibleHeight)) + { + if (decoder->visibleWidth != visibleWidth || decoder->visibleHeight != visibleHeight) + { + decoder->visibleWidth = visibleWidth; + decoder->visibleHeight = visibleHeight; + + decoder->is_set_caps = TRUE; // Only set caps if resolution changed, so + // we do not trigger it during decoder reload. + } + } + // Cache stride and pixel aspect ratio. Ok if we do not have it. UINT32 unDefaultStride = 0; hr = pOutputType->GetUINT32(MF_MT_DEFAULT_STRIDE, &unDefaultStride); if (SUCCEEDED(hr)) { - decoder->defaultStride = unDefaultStride; + // Based on doc should be cast to signed int 32 + decoder->defaultStride = (gint)unDefaultStride; } hr = S_OK; @@ -1219,7 +1243,7 @@ static HRESULT mfwrapper_configure_decoder_output_type(GstMFWrapper *decoder) // We should cache as much supported formats as possible. // Try them in order we prefered. if (pOutputTypeIYUV) - hr = mfwrapper_set_decoder_output_type(decoder, pOutputTypeIYUV, false); + hr = mfwrapper_set_decoder_output_type(decoder, pOutputTypeIYUV, true); // Try only if previous one failed if (hr != S_OK && pOutputTypeNV12) @@ -1341,13 +1365,12 @@ static GstFlowReturn mfwrapper_deliver_sample(GstMFWrapper *decoder, return GST_FLOW_ERROR; hr = pSample->GetSampleTime(&llTimestamp); - GST_BUFFER_TIMESTAMP(pGstBuffer) = llTimestamp * 100; + if (SUCCEEDED(hr)) + GST_BUFFER_TIMESTAMP(pGstBuffer) = llTimestamp * 100; + hr = pSample->GetSampleDuration(&llDuration); if (SUCCEEDED(hr)) - { - hr = pSample->GetSampleDuration(&llDuration); GST_BUFFER_DURATION(pGstBuffer) = llDuration * 100; - } if (SUCCEEDED(hr) && decoder->is_force_output_discontinuity) { @@ -1356,20 +1379,17 @@ static GstFlowReturn mfwrapper_deliver_sample(GstMFWrapper *decoder, decoder->is_force_output_discontinuity = FALSE; } -#if PTS_DEBUG - if (GST_BUFFER_TIMESTAMP_IS_VALID(pGstBuffer) && GST_BUFFER_DURATION_IS_VALID(pGstBuffer)) - g_print("JFXMEDIA H265 %I64u %I64u\n", GST_BUFFER_TIMESTAMP(pGstBuffer), GST_BUFFER_DURATION(pGstBuffer)); - else if (GST_BUFFER_TIMESTAMP_IS_VALID(pGstBuffer) && !GST_BUFFER_DURATION_IS_VALID(pGstBuffer)) - g_print("JFXMEDIA H265 %I64u -1\n", GST_BUFFER_TIMESTAMP(pGstBuffer)); - else - g_print("JFXMEDIA H265 -1\n"); -#endif + TRACE(DECODER_OUTPUT_PTS, "PTS OUT pts=%lld dur=%lld discont=%d\n", + GST_BUFFER_TIMESTAMP_IS_VALID(pGstBuffer) ? GST_BUFFER_TIMESTAMP(pGstBuffer) : -1, + GST_BUFFER_DURATION_IS_VALID(pGstBuffer) ? GST_BUFFER_DURATION(pGstBuffer) : -1, + GST_BUFFER_FLAG_IS_SET(pGstBuffer, GST_BUFFER_FLAG_DISCONT)); return gst_pad_push(decoder->srcpad, pGstBuffer); } static gint mfwrapper_process_output(GstMFWrapper *decoder) { + HRESULT hr = S_OK; MFT_OUTPUT_DATA_BUFFER outputDataBuffer; outputDataBuffer.dwStreamID = 0; outputDataBuffer.pSample = decoder->pDecoderOutput; @@ -1385,9 +1405,16 @@ static gint mfwrapper_process_output(GstMFWrapper *decoder) if (decoder->is_eos || decoder->is_flushing) return PO_FLUSHING; - HRESULT hr = decoder->pDecoder->GetOutputStatus(&dwFlags); - if (SUCCEEDED(hr) && dwFlags != MFT_OUTPUT_STATUS_SAMPLE_READY) - return PO_NEED_MORE_DATA; +// For H.264 MFT_OUTPUT_STATUS_SAMPLE_READY is never reported even if +// GetOutputStatus() returns S_OK. It should fail or report flag +// based on documentation. This code will be disabled for now. +// hr = decoder->pDecoder->GetOutputStatus(&dwFlags); +// if (SUCCEEDED(hr) && dwFlags != MFT_OUTPUT_STATUS_SAMPLE_READY) +// return PO_NEED_MORE_DATA; + + // Just in case reset decoder buffer to initial state + if (decoder->pDecoderBuffer) + decoder->pDecoderBuffer->SetCurrentLength(0); hr = decoder->pDecoder->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus); SafeRelease(&outputDataBuffer.pEvents); @@ -1445,27 +1472,58 @@ static gint mfwrapper_process_output(GstMFWrapper *decoder) // Processes input buffers static GstFlowReturn mfwrapper_chain(GstPad *pad, GstObject *parent, GstBuffer *buf) { - GstFlowReturn ret = GST_FLOW_OK; GstMFWrapper *decoder = GST_MFWRAPPER(parent); + gint pi_ret = PI_FAILED; + gint po_ret = PO_FAILED; + +#if TRACE_ENABLE + if (decoder->trace_first_input_sample) + { + TRACE(DECODER_FIRST_PTS, "First input sample PTS=%lld dur=%lld\n", + GST_BUFFER_TIMESTAMP_IS_VALID(buf) ? GST_BUFFER_TIMESTAMP(buf) : -1, + GST_BUFFER_DURATION_IS_VALID(buf) ? GST_BUFFER_DURATION(buf) : -1); + decoder->trace_first_input_sample = FALSE; + } +#endif + + TRACE(DECODER_INPUT_PTS, "PTS IN pad=%s pts=%lld dur=%lld discont=%d\n", + GST_PAD_NAME(pad), + GST_BUFFER_TIMESTAMP_IS_VALID(buf) ? GST_BUFFER_TIMESTAMP(buf) : -1, + GST_BUFFER_DURATION_IS_VALID(buf) ? GST_BUFFER_DURATION(buf) : -1, + GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT)); - if (decoder->is_flushing || decoder->is_eos_received) + if (decoder->is_flushing || decoder->is_eos_received || decoder->is_decoder_error) { // INLINE - gst_buffer_unref() gst_buffer_unref(buf); return GST_FLOW_FLUSHING; } - if (!mfwrapper_process_input(decoder, buf)) - return GST_FLOW_FLUSHING; + pi_ret = mfwrapper_process_input(decoder, buf); + // Process all output only if process input did not failed + if (pi_ret != PI_FAILED) + { + do + { + po_ret = mfwrapper_process_output(decoder); + } while (po_ret == PO_DELIVERED); + } - gint po_ret = mfwrapper_process_output(decoder); - if (po_ret != PO_DELIVERED && po_ret != PO_NEED_MORE_DATA) - return GST_FLOW_FLUSHING; + // Input might reject sample, so try again + if (pi_ret == PI_NOTACCEPTING) + pi_ret = mfwrapper_process_input(decoder, buf); - if (decoder->is_flushing) + // INLINE - gst_buffer_unref() + gst_buffer_unref(buf); + + // By now PI_FAILED or PI_NOTACCEPTING is critical + if (pi_ret != PI_DELIVERED || po_ret == PO_FAILED) + return GST_FLOW_ERROR; + + if (po_ret == PO_FLUSHING || decoder->is_flushing) return GST_FLOW_FLUSHING; - return ret; + return GST_FLOW_OK; } static gboolean mfwrapper_push_sink_event(GstMFWrapper *decoder, GstEvent *event) @@ -1480,6 +1538,47 @@ static gboolean mfwrapper_push_sink_event(GstMFWrapper *decoder, GstEvent *event return ret; } +static void mfwrapper_drain_output(GstMFWrapper *decoder) +{ + if (decoder == NULL || decoder->pDecoder == NULL) + return; + + if (decoder->is_decoder_error) + return; + + if (!decoder->is_decoder_initialized) + return; + + // Let decoder know that we got end of stream + HRESULT hr = decoder->pDecoder-> + ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); + + // Ask decoder to produce all remaining data + if (SUCCEEDED(hr)) + { + decoder->pDecoder-> + ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0); + } + + // Deliver remaining data + gint po_ret; + do + { + po_ret = mfwrapper_process_output(decoder); + } while (po_ret == PO_DELIVERED); + + for (int i = 0; i < MAX_COLOR_CONVERT; i++) + { + if (decoder->pColorConvert[i]) + { + hr = decoder->pColorConvert[i]-> + ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); + if (SUCCEEDED(hr)) + hr = decoder->pColorConvert[i]-> + ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + } + } +} // This function will unload old instance of decoder and will create a new one. // Input and Output media formats will be exactly same as old one. @@ -1490,39 +1589,61 @@ static gboolean mfwrapper_push_sink_event(GstMFWrapper *decoder, GstEvent *event static gboolean mfwrapper_reload_decoder(GstMFWrapper *decoder) { HRESULT hr = S_OK; - - IMFMediaType *pInputType = NULL; IMFMediaType *pOutputType = NULL; - DWORD dwStatus = 0; - GUID majorType; GUID subType; - if (decoder == NULL || decoder->pDecoder == NULL) + if (decoder == NULL || decoder->pDecoder == NULL || decoder->pDecoderInputType == NULL) return false; - // Save copy of old decoder - IMFTransform *pOldDecoder = decoder->pDecoder; + // Can required information from old decoder + hr = decoder->pDecoder->GetOutputCurrentType(0, &pOutputType); - decoder->pDecoder = NULL; + // Release old decoder. All needed information is cached. + SafeRelease(&decoder->pDecoder); - hr = pOldDecoder->GetInputCurrentType(0, &pInputType); - if (SUCCEEDED(hr)) - hr = pOldDecoder->GetOutputCurrentType(0, &pOutputType); if (SUCCEEDED(hr)) - hr = pInputType->GetGUID(MF_MT_MAJOR_TYPE, &majorType); + hr = decoder->pDecoderInputType->GetGUID(MF_MT_MAJOR_TYPE, &majorType); if (SUCCEEDED(hr)) - hr = pInputType->GetGUID(MF_MT_SUBTYPE, &subType); + hr = decoder->pDecoderInputType->GetGUID(MF_MT_SUBTYPE, &subType); // Load decoder based on media types of current one hr = mfwrapper_load_decoder_media_types(decoder, majorType, subType); - // Copy input and output types and we should be good to go + // Set input type from saved copy so we keep all infromation + IMFMediaType* pInputType = NULL; + if (SUCCEEDED(hr)) + hr = MFCreateMediaType(&pInputType); + + if (SUCCEEDED(hr)) + hr = decoder->pDecoderInputType->CopyAllItems(pInputType); + if (SUCCEEDED(hr)) hr = decoder->pDecoder->SetInputType(0, pInputType, 0); + + SafeRelease(&pInputType); + + // Set output format from old decoder and update resolution on + // it to match input. Resolution can change and H.264 will reject such + // format if different from input. H.265 is fine with both cases. + // We will update it anyway when streaming starts. if (SUCCEEDED(hr)) - hr = decoder->pDecoder->SetOutputType(0, pOutputType, 0); + { + // Copy resolution from input to output + UINT32 uiWidth = 0; + UINT32 uiHeight = 0; + hr = MFGetAttributeSize(decoder->pDecoderInputType, MF_MT_FRAME_SIZE, + &uiWidth, &uiHeight); + + if (SUCCEEDED(hr)) + hr = MFSetAttributeSize(pOutputType, MF_MT_FRAME_SIZE, uiWidth, uiHeight); + + if (SUCCEEDED(hr)) + hr = decoder->pDecoder->SetOutputType(0, pOutputType, 0); + } + + SafeRelease(&pOutputType); if (SUCCEEDED(hr)) hr = decoder->pDecoder->GetInputStatus(0, &dwStatus); @@ -1540,10 +1661,6 @@ static gboolean mfwrapper_reload_decoder(GstMFWrapper *decoder) if (SUCCEEDED(hr)) hr = decoder->pDecoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL); - SafeRelease(&pInputType); - SafeRelease(&pOutputType); - SafeRelease(&pOldDecoder); - if (FAILED(hr)) return false; @@ -1560,6 +1677,17 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e { case GST_EVENT_SEGMENT: { +#if TRACE_ENABLE + { + const GstSegment *segment = NULL; + gst_event_parse_segment(event, &segment); + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_SEGMENT start=%lld time=%lld position=%lld\n", + segment != NULL ? segment->start : -1, + segment != NULL ? segment->time : -1, + segment != NULL ? segment->position : -1); + decoder->trace_first_input_sample = TRUE; + } +#endif decoder->is_force_discontinuity = TRUE; ret = mfwrapper_push_sink_event(decoder, event); decoder->is_eos_received = FALSE; @@ -1568,6 +1696,7 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e break; case GST_EVENT_FLUSH_START: { + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_FLUSH_START\n"); decoder->is_flushing = TRUE; ret = mfwrapper_push_sink_event(decoder, event); @@ -1575,21 +1704,11 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e break; case GST_EVENT_FLUSH_STOP: { - if (!decoder->is_decoder_error) + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_FLUSH_STOP\n"); + if (decoder->pDecoder && !decoder->is_decoder_error) { - if (!mfwrapper_reload_decoder(decoder)) - { - decoder->is_decoder_error = TRUE; - gst_element_message_full(GST_ELEMENT(decoder), GST_MESSAGE_ERROR, - GST_STREAM_ERROR, GST_STREAM_ERROR_DECODE, - g_strdup("Failed to reload decoder"), NULL, - ("mfwrapper.c"), ("mfwrapper_sink_event"), 0); - } - else + if (mfwrapper_reload_decoder(decoder)) { - // Send header after reload - decoder->is_send_header = TRUE; - for (int i = 0; i < MAX_COLOR_CONVERT; i++) { if (decoder->pColorConvert[i]) @@ -1599,6 +1718,15 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e } } } + else + { + decoder->is_decoder_error = TRUE; + gst_element_message_full(GST_ELEMENT(decoder), GST_MESSAGE_ERROR, + GST_STREAM_ERROR, GST_STREAM_ERROR_DECODE, + g_strdup("Failed to reload decoder"), NULL, + ("mfwrapper.c"), ("mfwrapper_sink_event"), 0); + + } } // Even if reload failed with critical error push event to unblock @@ -1610,52 +1738,44 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e break; case GST_EVENT_EOS: { + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_EOS\n"); decoder->is_eos_received = TRUE; - if (!decoder->is_decoder_error) - { - // Let decoder know that we got end of stream - hr = decoder->pDecoder-> - ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); + mfwrapper_drain_output(decoder); - // Ask decoder to produce all remaining data - if (SUCCEEDED(hr)) - { - decoder->pDecoder-> - ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0); - } + // We done pushing all frames. Deliver EOS. + ret = mfwrapper_push_sink_event(decoder, event); - // Deliver remaining data - gint po_ret; - do - { - po_ret = mfwrapper_process_output(decoder); - } while (po_ret == PO_DELIVERED); + decoder->is_eos = TRUE; + } + break; + case GST_EVENT_CAPS: + { + GstCaps *caps = NULL; + gst_event_parse_caps(event, &caps); + +#if TRACE_ENABLE + { + gchar *caps_str = gst_caps_to_string(caps); + TRACE(DECODER_SINK_EVENTS, "GST_EVENT_CAPS: %s\n", caps_str); + g_free(caps_str); + } +#endif + if (decoder->pDecoder && !decoder->is_decoder_error) + { + decoder->pDecoder-> + ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); for (int i = 0; i < MAX_COLOR_CONVERT; i++) { if (decoder->pColorConvert[i]) { - hr = decoder->pColorConvert[i]-> - ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); - if (SUCCEEDED(hr)) - hr = decoder->pColorConvert[i]-> - ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + decoder->pColorConvert[i]-> + ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); } } } - // We done pushing all frames. Deliver EOS. - ret = mfwrapper_push_sink_event(decoder, event); - - decoder->is_eos = TRUE; - } - break; - case GST_EVENT_CAPS: - { - GstCaps *caps; - - gst_event_parse_caps(event, &caps); if (!mfwrapper_sink_set_caps(pad, parent, caps)) { gst_element_message_full(GST_ELEMENT(decoder), GST_MESSAGE_ERROR, @@ -1670,6 +1790,7 @@ static gboolean mfwrapper_sink_event(GstPad* pad, GstObject *parent, GstEvent *e } break; default: + TRACE(DECODER_SINK_EVENTS, "Received pass-thru event\n"); ret = mfwrapper_push_sink_event(decoder, event); break; } @@ -1691,7 +1812,14 @@ static gboolean mfwrapper_get_mf_media_types(GstCaps *caps, GUID *pMajorType, GU mimetype = gst_structure_get_name(s); if (mimetype != NULL) { - if (strstr(mimetype, "video/x-h265") != NULL) + if (strstr(mimetype, "video/x-h264") != NULL) + { + *pMajorType = MFMediaType_Video; + *pSubType = MFVideoFormat_H264; + + return TRUE; + } + else if (strstr(mimetype, "video/x-h265") != NULL) { *pMajorType = MFMediaType_Video; *pSubType = MFVideoFormat_HEVC; @@ -1760,81 +1888,57 @@ static HRESULT mfwrapper_load_decoder_media_types(GstMFWrapper *decoder, return hr; } -gsize mfwrapper_get_hevc_config(void *in, gsize in_size, BYTE *out, gsize out_size) +static HRESULT mfwrapper_set_mf_blob_from_caps(IMFMediaType *pMediaType, + GstStructure *structure, + const gchar *fieldName, + const GUID &guidKey) { - guintptr bdata = (guintptr)in; - guint8 arrayCount = 0; - guint16 nalUnitsCount = 0; - guint16 nalUnitLength = 0; - guint ii = 0; - guint jj = 0; - gsize in_bytes_count = 22; - gsize out_bytes_count = 0; - guint8 startCode[4] = { 0x00, 0x00, 0x00, 0x01 }; - - if (in_bytes_count > in_size) - return 0; - - // Skip first 22 bytes - bdata += in_bytes_count; - - // Get array count - arrayCount = *(guint8*)bdata; - bdata++; in_bytes_count++; - - for (ii = 0; ii < arrayCount; ii++) { - if ((in_bytes_count + 3) > in_size) - return 0; - - // Skip 1 byte, not needed - bdata++; in_bytes_count++; - - // 2 bytes number of nal units in array - nalUnitsCount = ((guint16)*(guint8*)bdata) << 8; - bdata++; in_bytes_count++; - nalUnitsCount |= (guint16)*(guint8*)bdata; - bdata++; in_bytes_count++; - - for (jj = 0; jj < nalUnitsCount; jj++) { - if ((in_bytes_count + 2) > in_size) - return 0; - - nalUnitLength = ((guint16)*(guint8*)bdata) << 8; - bdata++; in_bytes_count++; - nalUnitLength |= (guint16)*(guint8*)bdata; - bdata++; in_bytes_count++; - - if ((out_bytes_count + 4) > out_size) - return 0; - - // Set start code - memcpy(out, &startCode[0], sizeof(startCode)); - out += sizeof(startCode); out_bytes_count += sizeof(startCode); - - if ((out_bytes_count + nalUnitLength) > out_size) - return 0; - - if ((in_bytes_count + nalUnitLength) > in_size) - return 0; - - // Copy nal unit - memcpy(out, (guint8*)bdata, nalUnitLength); - bdata += nalUnitLength; in_bytes_count += nalUnitLength; - out += nalUnitLength; out_bytes_count += nalUnitLength; - } + const GValue *value = NULL; + GstBuffer *buffer = NULL; + GstMapInfo info; + HRESULT hr = S_OK; + + if (pMediaType == NULL || structure == NULL || fieldName == NULL) + return E_INVALIDARG; + + value = gst_structure_get_value(structure, fieldName); + if (value == NULL) + return S_OK; + + buffer = gst_value_get_buffer(value); + if (buffer == NULL) + return S_OK; + + if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) + return E_FAIL; + + if (info.size > G_MAXUINT) + { + hr = E_FAIL; + } + else if (info.size > 0) + { + hr = pMediaType->SetBlob(guidKey, info.data, (UINT32)info.size); } - return out_bytes_count; + gst_buffer_unmap(buffer, &info); + + return hr; } static HRESULT mfwrapper_set_input_media_type(GstMFWrapper *decoder, GstCaps *caps) { HRESULT hr = S_OK; - IMFMediaType *pInputType = NULL; GUID majorType; GUID subType; GstStructure *s = NULL; + const gchar *streamFormat = NULL; + guint value = 0; + gint numerator = 0; + gint denominator = 0; + + SafeRelease(&decoder->pDecoderInputType); s = gst_caps_get_structure(caps, 0); if (s == NULL) @@ -1843,19 +1947,54 @@ static HRESULT mfwrapper_set_input_media_type(GstMFWrapper *decoder, GstCaps *ca if (!mfwrapper_get_mf_media_types(caps, &majorType, &subType)) return E_FAIL; - hr = MFCreateMediaType(&pInputType); + hr = MFCreateMediaType(&decoder->pDecoderInputType); + + if (SUCCEEDED(hr)) + hr = decoder->pDecoderInputType->SetGUID(MF_MT_MAJOR_TYPE, majorType); if (SUCCEEDED(hr)) - hr = pInputType->SetGUID(MF_MT_MAJOR_TYPE, majorType); + hr = decoder->pDecoderInputType->SetGUID(MF_MT_SUBTYPE, subType); + + if (SUCCEEDED(hr) && gst_structure_get_int(s, "width", (gint*)&decoder->width) + && gst_structure_get_int(s, "height", (gint*)&decoder->height)) + hr = MFSetAttributeSize(decoder->pDecoderInputType, MF_MT_FRAME_SIZE, + decoder->width, decoder->height); + + if (SUCCEEDED(hr) && gst_structure_get_fraction(s, "framerate", + (gint*)&decoder->framerate_num, (gint*)&decoder->framerate_den)) + hr = MFSetAttributeRatio(decoder->pDecoderInputType, MF_MT_FRAME_RATE, + decoder->framerate_num, decoder->framerate_den); + + if (SUCCEEDED(hr) && gst_structure_get_uint(s, "mf-interlace-mode", &value)) + hr = decoder->pDecoderInputType->SetUINT32(MF_MT_INTERLACE_MODE, value); + + if (SUCCEEDED(hr) && gst_structure_get_fraction(s, "pixel-aspect-ratio", + &numerator, &denominator)) + { + decoder->pixel_num = numerator; + decoder->pixel_den = denominator; + hr = MFSetAttributeRatio(decoder->pDecoderInputType, MF_MT_PIXEL_ASPECT_RATIO, + numerator, denominator); + } + + if (SUCCEEDED(hr) && gst_structure_get_uint(s, "mf-mpeg2-profile", &value)) + hr = decoder->pDecoderInputType->SetUINT32(MF_MT_MPEG2_PROFILE, value); + + if (SUCCEEDED(hr) && gst_structure_get_uint(s, "mf-mpeg2-level", &value)) + hr = decoder->pDecoderInputType->SetUINT32(MF_MT_MPEG2_LEVEL, value); if (SUCCEEDED(hr)) - hr = pInputType->SetGUID(MF_MT_SUBTYPE, subType); + { + hr = mfwrapper_set_mf_blob_from_caps(decoder->pDecoderInputType, s, + "mf-mpeg-sequence-header", MF_MT_MPEG_SEQUENCE_HEADER); + } - if (SUCCEEDED(hr) && gst_structure_get_int(s, "width", (gint*)&decoder->width) && gst_structure_get_int(s, "height", (gint*)&decoder->height)) - hr = MFSetAttributeSize(pInputType, MF_MT_FRAME_SIZE, decoder->width, decoder->height); + IMFMediaType* pInputType = NULL; + if (SUCCEEDED(hr)) + hr = MFCreateMediaType(&pInputType); - if (SUCCEEDED(hr) && gst_structure_get_fraction(s, "framerate", (gint*)&decoder->framerate_num, (gint*)&decoder->framerate_den)) - hr = MFSetAttributeRatio(pInputType, MF_MT_FRAME_RATE, decoder->framerate_num, decoder->framerate_den); + if (SUCCEEDED(hr)) + hr = decoder->pDecoderInputType->CopyAllItems(pInputType); if (SUCCEEDED(hr)) hr = decoder->pDecoder->SetInputType(0, pInputType, 0); @@ -1865,7 +2004,7 @@ static HRESULT mfwrapper_set_input_media_type(GstMFWrapper *decoder, GstCaps *ca return hr; } -static HRESULT mfwrapper_set_output_media_type(GstMFWrapper *decoder, GstCaps *caps) +static HRESULT mfwrapper_set_output_media_type(GstMFWrapper *decoder, gboolean setCaps) { HRESULT hr = S_OK; @@ -1889,7 +2028,8 @@ static HRESULT mfwrapper_set_output_media_type(GstMFWrapper *decoder, GstCaps *c hr = decoder->pDecoder->SetOutputType(0, pOutputType, 0); // Set srcpad caps - mfwrapper_set_src_caps(decoder); + if (setCaps) + mfwrapper_set_src_caps(decoder); SafeRelease(&pOutputType); @@ -1913,7 +2053,11 @@ static gboolean mfwrapper_init_mf(GstMFWrapper *decoder, GstCaps *caps) hr = mfwrapper_set_input_media_type(decoder, caps); if (SUCCEEDED(hr)) - hr = mfwrapper_set_output_media_type(decoder, caps); + hr = mfwrapper_set_output_media_type(decoder, TRUE); + + // H.264 might not send MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE + if (SUCCEEDED(hr)) + hr = mfwrapper_configure_decoder_output_type(decoder); if (SUCCEEDED(hr)) hr = decoder->pDecoder->GetInputStatus(0, &dwStatus); @@ -1929,44 +2073,6 @@ static gboolean mfwrapper_init_mf(GstMFWrapper *decoder, GstCaps *caps) if (s == NULL) return FALSE; - // Get HEVC Config - GstMapInfo info; - - if (SUCCEEDED(hr)) - codec_data_value = gst_structure_get_value(s, "codec_data"); - - if (codec_data_value) - codec_data = gst_value_get_buffer(codec_data_value); - - if (codec_data != NULL) - { - if (gst_buffer_map(codec_data, &info, GST_MAP_READ) && info.size > 0) - { - // Free old one if exist - if (decoder->header) - delete[] decoder->header; - - decoder->header = new BYTE[info.size * 2]; // Should be enough, since we will only add several 4 bytes start codes to 3 nal units - if (decoder->header == NULL) - { - gst_buffer_unmap(codec_data, &info); - return FALSE; - } - - decoder->header_size = mfwrapper_get_hevc_config(info.data, info.size, decoder->header, info.size * 2); - gst_buffer_unmap(codec_data, &info); - - if (decoder->header_size <= 0) - { - delete[] decoder->header; - decoder->header = NULL; - return FALSE; - } - - decoder->is_send_header = TRUE; - } - } - if (!decoder->is_decoder_initialized) { if (SUCCEEDED(hr)) diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.h b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.h index 3a7c4911cb9..7a1184e4293 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.h +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/mfwrapper/mfwrapper.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ #include +#include "mftrace.h" #include "mfgstbuffer.h" #include @@ -73,16 +74,21 @@ struct _GstMFWrapper gboolean is_eos_received; gboolean is_eos; gboolean is_decoder_initialized; + gboolean force_discontinuity; // If set to true do not call decoder it might hang. // This flag should be set if decoder calls failed. gboolean is_decoder_error; gboolean is_force_discontinuity; gboolean is_force_output_discontinuity; +#if TRACE_ENABLE + gboolean trace_first_input_sample; +#endif HRESULT hr_mfstartup; IMFTransform *pDecoder; + IMFMediaType *pDecoderInputType; IMFSample *pDecoderOutput; CMFGSTBuffer *pDecoderBuffer; @@ -92,16 +98,14 @@ struct _GstMFWrapper GstBufferPool *pool; - BYTE *header; - gsize header_size; - gboolean is_send_header; - guint width; guint height; + guint visibleWidth; + guint visibleHeight; guint framerate_num; guint framerate_den; - guint defaultStride; + gint defaultStride; guint pixel_num; guint pixel_den; diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/cache.h b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/cache.h index bf447082b3d..b1ebf26e65e 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/cache.h +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/cache.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,6 +51,12 @@ gint64 cache_read_buffer(Cache* cache, GstBuffer** buffer); */ GstFlowReturn cache_read_buffer_from_position(Cache* cache, gint64 start_position, guint size, GstBuffer** buffer); +/* Reads a buffer of the specified size or less and start position. + * Returns GST_FLOW_OK if the seek operation and subsequent read operation + * were successfull. GST_FLOW_ERROR otherwise. + */ +GstFlowReturn cache_read_buffer_from_position2(Cache* cache, gint64 start_position, guint size, GstBuffer** buffer); + // Sets a new write position gboolean cache_set_write_position(Cache* cache, gint64 position); @@ -60,4 +66,11 @@ gboolean cache_set_read_position(Cache* cache, gint64 position); // Returns true if the cache has enough data for fluent reading, but we can't expect more than total. gboolean cache_has_enough_data(Cache* cache); +// Returns true if the cache has enough data for size bytes. +gboolean cache_has_enough_data2(Cache* cache, guint64 read_position, guint size); + +// Returns number of bytes available to read or 0 if no bytes left from +// read_position. +guint64 cache_bytes_available(Cache* cache, guint64 read_position); + #endif // __CACHE_H__ diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/hlsprogressbuffer.c b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/hlsprogressbuffer.c index 49798259ee1..9095d6eb722 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/hlsprogressbuffer.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/hlsprogressbuffer.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include "hlsprogressbuffer.h" #include "cache.h" +#include "fxplugins_common.h" /*********************************************************************************** * Debug category init @@ -53,6 +54,7 @@ struct _HLSProgressBuffer Cache* cache[NUM_OF_CACHED_SEGMENTS]; guint cache_size[NUM_OF_CACHED_SEGMENTS]; gboolean cache_write_ready[NUM_OF_CACHED_SEGMENTS]; + gboolean cache_read_ready[NUM_OF_CACHED_SEGMENTS]; gboolean cache_discont[NUM_OF_CACHED_SEGMENTS]; gint cache_write_index; gint cache_read_index; @@ -66,6 +68,9 @@ struct _HLSProgressBuffer GstFlowReturn srcresult; GstClockTime buffer_pts; + + gboolean is_pull_mode; + gboolean send_segment_ready_event; }; struct _HLSProgressBufferClass @@ -123,9 +128,15 @@ static GstStateChangeReturn hls_progress_buffer_change_state (GstElement *elemen static GstFlowReturn hls_progress_buffer_chain(GstPad *pad, GstObject *parent, GstBuffer *data); static gboolean hls_progress_buffer_activatemode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active); static gboolean hls_progress_buffer_activatepush_src(GstPad *pad, GstObject *parent, gboolean active); +static gboolean hls_progress_buffer_activatepull_src(GstPad *pad, GstObject *parent, gboolean active); static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, GstEvent *event); +static gboolean hls_progress_buffer_src_event(GstPad *pad, GstObject *parent, GstEvent *event); static void hls_progress_buffer_loop(void *data); static void hls_progress_buffer_flush_data(HLSProgressBuffer *buffer); +static GstFlowReturn hls_progress_buffer_getrange(GstPad *pad, GstObject *parent, + guint64 start_position, guint length, GstBuffer **data); +static gboolean hls_progress_buffer_query(GstPad *pad, GstObject *parent, GstQuery *query); +static void hls_send_segment_ready_event(HLSProgressBuffer *element, gboolean bForce); /** * hls_progress_buffer_class_init() @@ -162,14 +173,17 @@ static void hls_progress_buffer_class_init (HLSProgressBufferClass *klass) */ static void hls_progress_buffer_init(HLSProgressBuffer *element) { - element->sinkpad = gst_pad_new_from_template (gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "sink"), "sink"); + element->sinkpad = gst_pad_new_from_template(gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "sink"), "sink"); gst_pad_set_chain_function(element->sinkpad, hls_progress_buffer_chain); gst_pad_set_event_function(element->sinkpad, hls_progress_buffer_sink_event); - gst_element_add_pad (GST_ELEMENT (element), element->sinkpad); + gst_element_add_pad(GST_ELEMENT (element), element->sinkpad); - element->srcpad = gst_pad_new_from_template (gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "src"), "src"); - gst_pad_set_activatemode_function (element->srcpad, hls_progress_buffer_activatemode); - gst_element_add_pad (GST_ELEMENT (element), element->srcpad); + element->srcpad = gst_pad_new_from_template(gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "src"), "src"); + gst_pad_set_activatemode_function(element->srcpad, hls_progress_buffer_activatemode); + gst_pad_set_getrange_function(element->srcpad, hls_progress_buffer_getrange); + gst_pad_set_query_function(element->srcpad, hls_progress_buffer_query); + gst_pad_set_event_function(element->srcpad, hls_progress_buffer_src_event); + gst_element_add_pad(GST_ELEMENT (element), element->srcpad); g_mutex_init(&element->lock); g_cond_init(&element->add_cond); @@ -180,6 +194,7 @@ static void hls_progress_buffer_init(HLSProgressBuffer *element) element->cache[i] = create_cache(); element->cache_size[i] = 0; element->cache_write_ready[i] = TRUE; + element->cache_read_ready[i] = FALSE; element->cache_discont[i] = FALSE; } @@ -195,6 +210,9 @@ static void hls_progress_buffer_init(HLSProgressBuffer *element) element->srcresult = GST_FLOW_OK; element->buffer_pts = GST_CLOCK_TIME_NONE; + + element->is_pull_mode = FALSE; + element->send_segment_ready_event = FALSE; } /** @@ -223,16 +241,23 @@ static void hls_progress_buffer_finalize (GObject *object) /** * hls_progress_buffer_activatepush_src() * - * Set the source pad's push mode. + * Set the source pad's push or pull mode. */ static gboolean hls_progress_buffer_activatemode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active) { gboolean res = FALSE; + HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(parent); switch (mode) { case GST_PAD_MODE_PUSH: + element->is_pull_mode = FALSE; res = hls_progress_buffer_activatepush_src(pad, parent, active); break; + case GST_PAD_MODE_PULL: + element->is_pull_mode = TRUE; + element->send_segment_ready_event = TRUE; + res = hls_progress_buffer_activatepull_src(pad, parent, active); + break; default: /* unknown scheduling mode */ res = FALSE; @@ -269,6 +294,28 @@ static gboolean hls_progress_buffer_activatepush_src(GstPad *pad, GstObject *par } } +static gboolean hls_progress_buffer_activatepull_src(GstPad *pad, GstObject *parent, gboolean active) +{ + HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(parent); + + if (active) + { + g_mutex_lock(&element->lock); + element->srcresult = GST_FLOW_OK; + g_mutex_unlock(&element->lock); + } + else + { + g_mutex_lock(&element->lock); + element->srcresult = GST_FLOW_FLUSHING; + g_cond_signal(&element->add_cond); + g_cond_signal(&element->del_cond); + g_mutex_unlock(&element->lock); + } + + return TRUE; +} + /*********************************************************************************** * Internal functions ***********************************************************************************/ @@ -285,6 +332,7 @@ static void hls_progress_buffer_flush_data(HLSProgressBuffer *element) element->cache_write_index = -1; element->cache_read_index = 0; + element->send_segment_ready_event = element->is_pull_mode; for (i = 0; i < NUM_OF_CACHED_SEGMENTS; i++) { if (element->cache[i]) @@ -293,6 +341,8 @@ static void hls_progress_buffer_flush_data(HLSProgressBuffer *element) cache_set_read_position(element->cache[i], 0); element->cache_size[i] = 0; element->cache_write_ready[i] = TRUE; + element->cache_read_ready[i] = FALSE; + element->cache_discont[i] = FALSE; } } @@ -320,12 +370,10 @@ static GstFlowReturn hls_progress_buffer_chain(GstPad *pad, GstObject *parent, G } g_mutex_lock(&element->lock); - if (element->srcresult != GST_FLOW_FLUSHING) + if (element->srcresult != GST_FLOW_FLUSHING && element->cache_write_index >= 0) { if (GST_BUFFER_FLAG_IS_SET(data, GST_BUFFER_FLAG_DISCONT)) - { element->cache_discont[element->cache_write_index] = TRUE; - } cache_write_buffer(element->cache[element->cache_write_index], data); g_cond_signal(&element->add_cond); @@ -463,6 +511,146 @@ static void hls_progress_buffer_loop(void *data) gst_pad_pause_task(element->srcpad); } +static GstFlowReturn hls_progress_buffer_getrange(GstPad *pad, GstObject *parent, + guint64 start_position, guint size, + GstBuffer **buffer) +{ + HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(parent); + GstFlowReturn result = GST_FLOW_OK; + + g_mutex_lock(&element->lock); // Use one lock for push and pull modes + if (!element->cache_read_ready[element->cache_read_index]) + { + send_hls_not_full_message(element); + gboolean is_eos = element->is_eos; + g_mutex_unlock(&element->lock); + if (is_eos) + return GST_FLOW_EOS; + else + return GST_FLOW_FLUSHING; + } + else + { + // Read data as requested or less. + result = cache_read_buffer_from_position2( + element->cache[element->cache_read_index], start_position, + size, buffer); + } + + // Set flag GST_BUFFER_FLAG_DISCONT if needed + if (element->cache_discont[element->cache_read_index]) + { + if (result == GST_FLOW_OK && (*buffer) != NULL) + { + (*buffer) = gst_buffer_make_writable((*buffer)); + GST_BUFFER_FLAG_SET((*buffer), GST_BUFFER_FLAG_DISCONT); + } + element->cache_discont[element->cache_read_index] = FALSE; + } + + g_mutex_unlock(&element->lock); + + return result; +} + +static gboolean hls_progress_buffer_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(parent); + gboolean result = FALSE; + + switch (GST_QUERY_TYPE(query)) + { + case GST_QUERY_DURATION: + { + GstFormat format; + + gst_query_parse_duration(query, &format, NULL); + + // If we asked for duration in bytes report size of current read + // segment. "mfdemux" design in such way that it needs to know size + // in bytes of data it will pull. Once it read segment it will + // query duration again for second segment. In HLS we report new + // segment in time units, so "mfdemux" cannot use it. "javasource" + // will handle duration query in GST_FORMAT_TIME and will report + // length of entire HLS stream in time units. With such design down + // stream can figure out length of stream in time units and length + // of segment in bytes for parsing. + if (format == GST_FORMAT_BYTES) + { + g_mutex_lock(&element->lock); + if (element->cache_read_ready[element->cache_read_index]) + { + gst_query_set_duration(query, GST_FORMAT_BYTES, + element->cache_size[element->cache_read_index]); + result = TRUE; + } + else + { + result = FALSE; + } + g_mutex_unlock(&element->lock); + + return result; + } + else + { + result = gst_pad_query_default(pad, parent, query); + } + break; + } + default: + result = gst_pad_query_default(pad, parent, query); + break; + } + + return result; +} + +/** + * hls_send_segment_ready_event() + * + * Sends FX_EVENT_SEGMENT_READY if needed. + * + * Note: Lock needs to be acquired when this function is called. + */ +static void hls_send_segment_ready_event(HLSProgressBuffer *element, gboolean bForce) +{ + if (element->send_segment_ready_event || bForce) + { + element->send_segment_ready_event = FALSE; + GstStructure *s = gst_structure_new("HLSSegmentInfo", + "size", G_TYPE_INT64, + element->cache_size[element->cache_read_index], NULL); + + g_mutex_unlock(&element->lock); + gst_pad_push_event(element->srcpad, + gst_event_new_custom(FX_EVENT_SEGMENT_READY, s)); + g_mutex_lock(&element->lock); + } +} + +/** + * hls_finalize_read_segment() + * + * Marks segment as read ready in pull mode. In pull mode we need to call it + * for EOS as well to finalize last segment. + * + * Note: Lock needs to be acquired when this function is called. + */ +static void hls_finalize_read_segment(HLSProgressBuffer *element) +{ + if (element && element->is_pull_mode) + { + if (element->cache_write_index != -1) + { + element->cache_read_ready[element->cache_write_index] = TRUE; + + // Let downstream know that we have segment ready to read + hls_send_segment_ready_event(element, FALSE); + } + } +} + /** * hls_progress_buffer_sink_event() * @@ -481,7 +669,8 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G GstSegment segment; g_mutex_lock(&element->lock); - if (element->srcresult != GST_FLOW_OK) + if (element->srcresult != GST_FLOW_OK && + !(element->srcresult == GST_FLOW_FLUSHING && element->is_eos)) { // INLINE - gst_event_unref() gst_event_unref(event); @@ -494,7 +683,7 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G { element->is_eos = FALSE; element->srcresult = GST_FLOW_OK; - if (gst_pad_is_linked(element->srcpad)) + if (!element->is_pull_mode && gst_pad_is_linked(element->srcpad)) gst_pad_start_task(element->srcpad, hls_progress_buffer_loop, element, NULL); } @@ -525,6 +714,7 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G // Get and prepare next write segment g_mutex_lock(&element->lock); + hls_finalize_read_segment(element); element->cache_write_index = (element->cache_write_index + 1) % NUM_OF_CACHED_SEGMENTS; while (element->srcresult == GST_FLOW_OK && !element->cache_write_ready[element->cache_write_index]) @@ -557,7 +747,7 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G ret = gst_pad_push_event(element->srcpad, event); hls_progress_buffer_flush_data(element); - if (gst_pad_is_linked(element->srcpad)) + if (!element->is_pull_mode && gst_pad_is_linked(element->srcpad)) gst_pad_pause_task(element->srcpad); break; @@ -570,7 +760,7 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G element->is_flushing = FALSE; element->srcresult = GST_FLOW_OK; - if (!element->is_eos && gst_pad_is_linked(element->srcpad)) + if (!element->is_pull_mode && !element->is_eos && gst_pad_is_linked(element->srcpad)) gst_pad_start_task(element->srcpad, hls_progress_buffer_loop, element, NULL); g_mutex_unlock(&element->lock); @@ -580,9 +770,11 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G send_hls_eos_message(element); // Just in case we stall g_mutex_lock(&element->lock); + hls_finalize_read_segment(element); element->is_eos = TRUE; g_cond_signal(&element->add_cond); g_mutex_unlock(&element->lock); + // INLINE - gst_event_unref() gst_event_unref(event); ret = TRUE; @@ -596,6 +788,66 @@ static gboolean hls_progress_buffer_sink_event(GstPad *pad, GstObject *parent, G return ret; } +/** + * hls_progress_buffer_src_event() + * + * Receives event from the src pad (currently, data from mfdemux). When an event comes in, + * we get the data from the pad by getting at the ProgressBuffer* object associated with the pad. + */ +static gboolean hls_progress_buffer_src_event(GstPad *pad, GstObject *parent, GstEvent *event) +{ + HLSProgressBuffer *element = HLS_PROGRESS_BUFFER(parent); + gboolean ret = FALSE; + + switch (GST_EVENT_TYPE (event)) + { + case FX_EVENT_NEXT_SEGMENT: + if (element->is_pull_mode) + { + g_mutex_lock(&element->lock); + + element->cache_write_ready[element->cache_read_index] = TRUE; + element->cache_read_ready[element->cache_read_index] = FALSE; + element->cache_read_index = (element->cache_read_index + 1) % NUM_OF_CACHED_SEGMENTS; + + if (element->cache_read_ready[element->cache_read_index]) + { + hls_send_segment_ready_event(element, TRUE); + } + else + { + element->send_segment_ready_event = TRUE; + } + + // Signal EOS if set and nothing to read. + if (!element->cache_read_ready[element->cache_read_index]) + { + if (element->is_eos) + { + g_mutex_unlock(&element->lock); + gst_pad_push_event(element->srcpad, gst_event_new_eos()); + g_mutex_lock(&element->lock); + } + } + + send_hls_not_full_message(element); + g_cond_signal(&element->del_cond); + g_mutex_unlock(&element->lock); + } + + // INLINE - gst_event_unref() + gst_event_unref(event); + ret = TRUE; + + break; + default: + ret = gst_pad_push_event(element->sinkpad, event); + break; + } + + return ret; +} + /*********************************************************************************** * State change handler ***********************************************************************************/ diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/posix/filecache.c b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/posix/filecache.c index 3e8933ba140..f4384b1abde 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/posix/filecache.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/posix/filecache.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -132,35 +132,53 @@ gint64 cache_read_buffer(Cache* cache, GstBuffer** buffer) return 0; } -GstFlowReturn cache_read_buffer_from_position(Cache* cache, gint64 start_position, guint size, GstBuffer** buffer) +GstFlowReturn cache_read_buffer_from_position(Cache *cache, gint64 start_position, guint size, GstBuffer **buffer) { GstFlowReturn result = GST_FLOW_ERROR; *buffer = NULL; if (cache_set_read_position(cache, start_position)) { - guint8 *data = (guint8*)g_try_malloc(size); + guint8 *data = (guint8 *)g_try_malloc(size); if (data) - { - ssize_t read_bytes = read(cache->readHandle, data, size); + { + ssize_t read_bytes = read(cache->readHandle, data, size); if (read_bytes == size) { *buffer = gst_buffer_new_wrapped_full(0, data, size, 0, read_bytes, data, g_free); if (*buffer != NULL) { GST_BUFFER_OFFSET(*buffer) = cache->read_position; + // Adjust read position, only if we returning valid buffer. + cache->read_position += read_bytes; result = GST_FLOW_OK; } } else - g_free(data); // Wrong size, deleting buffer to avoid leaking. - - cache->read_position += read_bytes; - } + g_free(data); // Wrong size, deleting buffer to avoid leaking. + } } return result; } +GstFlowReturn cache_read_buffer_from_position2(Cache* cache, gint64 start_position, + guint size, GstBuffer** buffer) +{ + GstFlowReturn result = GST_FLOW_ERROR; + + if (cache == NULL) + return GST_FLOW_ERROR; + + guint64 bytes_available = cache_bytes_available(cache, start_position); + if (bytes_available == 0) + return GST_FLOW_FLUSHING; + + if ((guint64)size < bytes_available) + size = bytes_available; + + return cache_read_buffer_from_position(cache, start_position, size, buffer); +} + static inline gboolean cache_set_handler_position(int handle, guint64 position) { return lseek(handle, position, SEEK_SET) >= 0; @@ -194,3 +212,19 @@ gboolean cache_has_enough_data(Cache* cache) { return cache->read_position < cache->write_position; } + +gboolean cache_has_enough_data2(Cache* cache, guint64 read_position, guint size) +{ + if ((read_position + size) <= cache->write_position) + return TRUE; + + return FALSE; +} + +guint64 cache_bytes_available(Cache* cache, guint64 read_position) +{ + if (read_position < cache->write_position) + return (guint64)(cache->write_position - read_position); + else + return 0; +} diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/progressbuffer.c b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/progressbuffer.c index c3eec695614..88efda4b3e5 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/progressbuffer.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/progressbuffer.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -166,7 +166,6 @@ static gboolean progress_buffer_src_event(GstPad *pad, GstObject *parent static void progress_buffer_loop(void *data); static void progress_buffer_flush_data(ProgressBuffer *buffer); -static gboolean progress_buffer_checkgetrange(GstPad *pad); static GstFlowReturn progress_buffer_getrange(GstPad *pad, GstObject *parent, guint64 start_position, guint length, GstBuffer **data); #if ENABLE_PULL_MODE @@ -254,7 +253,12 @@ static void progress_buffer_init(ProgressBuffer *element) gst_pad_set_event_function (element->sinkpad, GST_DEBUG_FUNCPTR(progress_buffer_sink_event)); gst_element_add_pad (GST_ELEMENT (element), element->sinkpad); - element->srcpad = NULL; + element->srcpad = gst_pad_new_from_template (gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "src"), "src"); + gst_pad_set_activatemode_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_activatemode)); + gst_pad_set_event_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_src_event)); + gst_pad_set_getrange_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_getrange)); + gst_element_add_pad (GST_ELEMENT (element), element->srcpad); + element->cache = NULL; element->cache_read_offset = 0; g_mutex_init(&element->lock); @@ -467,29 +471,6 @@ static gboolean progress_buffer_activatemode(GstPad *pad, GstObject *parent, Gst return res; } -/** - * progress_buffer_create_sourcepad() - * - */ -static void progress_buffer_create_sourcepad(ProgressBuffer *element) -{ - element->srcpad = gst_pad_new_from_template (gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS(element), "src"), "src"); - - gst_pad_set_activatemode_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_activatemode)); - gst_pad_set_event_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_src_event)); - gst_pad_set_getrange_function (element->srcpad, GST_DEBUG_FUNCPTR(progress_buffer_getrange)); - GST_PAD_UNSET_FLUSHING(element->srcpad); - - // Add pad - gst_element_add_pad (GST_ELEMENT (element), element->srcpad); - - // Activate pad - gst_pad_set_active(element->srcpad, TRUE); - - // Send "no-more-pads" - gst_element_no_more_pads(GST_ELEMENT (element)); -} - /*********************************************************************************** * Internal functions ***********************************************************************************/ @@ -635,27 +616,9 @@ static GstFlowReturn progress_buffer_enqueue_item(ProgressBuffer *element, GstMi return GST_FLOW_ERROR; } - if ((segment.flags & GST_SEGMENT_FLAG_UPDATE) == GST_SEGMENT_FLAG_UPDATE) // Updating segments create new cache. - { - if (element->cache) - destroy_cache(element->cache); - - element->cache = create_cache(); - if (!element->cache) - { - gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ_WRITE, - g_strdup("Couldn't create backing cache"), NULL, - ("progressbuffer.c"), ("progress_buffer_enqueue_item"), 0); - gst_event_unref(event); // INLINE - gst_event_unref() - return GST_FLOW_ERROR; - } - } - else - { - cache_set_write_position(element->cache, 0); - cache_set_read_position(element->cache, 0); - element->cache_read_offset = segment.start; - } + cache_set_write_position(element->cache, 0); + cache_set_read_position(element->cache, 0); + element->cache_read_offset = segment.start; gst_segment_copy_into (&segment, &element->sink_segment); progress_buffer_set_pending_event(element, event); @@ -664,6 +627,11 @@ static GstFlowReturn progress_buffer_enqueue_item(ProgressBuffer *element, GstMi signal = send_position_message(element, TRUE); break; } + case GST_EVENT_CAPS: + { + gst_pad_push_event(element->srcpad, event); + break; + } default: gst_event_unref(event); // INLINE - gst_event_unref() @@ -816,10 +784,6 @@ static GstFlowReturn progress_buffer_chain(GstPad *pad, GstObject *parent, GstBu // INLINE - gst_buffer_unref() gst_buffer_unref(data); - // Here we can maintain some prebuffering strategy. - if (result != GST_FLOW_ERROR && !element->srcpad) - progress_buffer_create_sourcepad(element); - return result; } @@ -979,7 +943,6 @@ static gboolean progress_buffer_sink_event(GstPad *pad, GstObject *parent, GstEv result = gst_pad_push_event(element->srcpad, event); return result; - } static gboolean progress_buffer_src_event(GstPad *pad, GstObject *parent, GstEvent *event) @@ -1061,6 +1024,14 @@ static GstFlowReturn progress_buffer_getrange(GstPad *pad, GstObject *parent, gu g_mutex_lock(&element->lock); // Use one lock for push and pull modes + if (!cache_has_enough_data2(element->cache, start_position, size)) + { + element->range_start = start_position; + element->range_stop = element->range_start + size; + g_mutex_unlock(&element->lock); + return GST_FLOW_FLUSHING; + } + if (element->sink_segment.stop < (gint64)end_position) result = GST_FLOW_EOS; else if (element->sink_segment.start <= (gint64)start_position && @@ -1106,23 +1077,6 @@ static GstFlowReturn progress_buffer_getrange(GstPad *pad, GstObject *parent, gu #endif } -static gboolean progress_buffer_checkgetrange(GstPad *pad) -{ - ProgressBuffer *element = PROGRESS_BUFFER(GST_PAD_PARENT(pad)); -#if ENABLE_PULL_MODE - gboolean result = FALSE; - GstStructure *s = gst_structure_new(GETRANGE_QUERY_NAME, NULL, NULL); - GstQuery *query = gst_query_new_custom(GST_QUERY_CUSTOM, s); - if (gst_pad_peer_query(pad, query)) - result = gst_structure_get_boolean(s, GETRANGE_QUERY_SUPPORTS_FIELDNANE, &result) && result; -// INLINE - gst_query_unref() - gst_query_unref(query); - return result; -#else - return gst_pad_check_pull_range(element->sinkpad); -#endif -} - /*********************************************************************************** * State change handler ***********************************************************************************/ @@ -1130,13 +1084,39 @@ static GstStateChangeReturn progress_buffer_change_state (GstElement *e, GstStateChange transition) { ProgressBuffer *element = PROGRESS_BUFFER(e); - GstStateChangeReturn ret = GST_ELEMENT_CLASS (parent_class)->change_state (e, transition); + switch (transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: + element->cache = create_cache(); + if (!element->cache) + { + gst_element_message_full(GST_ELEMENT(element), GST_MESSAGE_ERROR, + GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ_WRITE, + g_strdup("Couldn't create backing cache"), NULL, + ("progressbuffer.c"), ("progress_buffer_change_state"), 0); + return GST_STATE_CHANGE_FAILURE; + } + break; + + default: + break; + } + + GstStateChangeReturn ret = GST_ELEMENT_CLASS (parent_class)->change_state (e, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + if (element->cache) + { + destroy_cache(element->cache); + element->cache = NULL; + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: g_mutex_lock(&element->lock); element->srcresult = GST_FLOW_FLUSHING; @@ -1148,6 +1128,7 @@ static GstStateChangeReturn progress_buffer_change_state (GstElement *e, default: break; } + return ret; } diff --git a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/win32/filecache.c b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/win32/filecache.c index c6cf7a8bd3d..e006f868d48 100644 --- a/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/win32/filecache.c +++ b/modules/javafx.media/src/main/native/gstreamer/plugins/progressbuffer/win32/filecache.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -140,13 +140,13 @@ GstFlowReturn cache_read_buffer_from_position(Cache* cache, gint64 start_positio if (*buffer != NULL) { GST_BUFFER_OFFSET(*buffer) = cache->read_position; + // Adjust read position, only if we returning valid buffer. + cache->read_position += read; result = GST_FLOW_OK; } } else g_free(data); // Wrong size, deleting buffer to avoid leaking. - - cache->read_position += read; } else if (data) // ReadError, deleting buffer to avoid leaking. g_free(data); @@ -154,6 +154,24 @@ GstFlowReturn cache_read_buffer_from_position(Cache* cache, gint64 start_positio return result; } +GstFlowReturn cache_read_buffer_from_position2(Cache* cache, gint64 start_position, + guint size, GstBuffer** buffer) +{ + GstFlowReturn result = GST_FLOW_ERROR; + + if (cache == NULL) + return GST_FLOW_ERROR; + + guint64 bytes_available = cache_bytes_available(cache, start_position); + if (bytes_available == 0) + return GST_FLOW_FLUSHING; + + if ((guint64)size > bytes_available) + size = bytes_available; + + return cache_read_buffer_from_position(cache, start_position, size, buffer); +} + static gboolean cache_set_handler_position(HANDLE handle, guint64 position) { LARGE_INTEGER li; @@ -184,6 +202,7 @@ gboolean cache_set_read_position(Cache* cache, gint64 position) if (result) cache->read_position = position; } + return result; } @@ -191,3 +210,19 @@ gboolean cache_has_enough_data(Cache* cache) { return cache->read_position < cache->write_position; } + +gboolean cache_has_enough_data2(Cache* cache, guint64 read_position, guint size) +{ + if ((read_position + size) <= cache->write_position) + return TRUE; + + return FALSE; +} + +gint64 cache_bytes_available(Cache* cache, guint64 read_position) +{ + if (read_position < cache->write_position) + return (guint64)(cache->write_position - read_position); + else + return 0; +} diff --git a/modules/javafx.media/src/main/native/gstreamer/projects/mac/gstreamer-lite/Makefile b/modules/javafx.media/src/main/native/gstreamer/projects/mac/gstreamer-lite/Makefile index 9e6ac3bf67e..d856a5a10f5 100644 --- a/modules/javafx.media/src/main/native/gstreamer/projects/mac/gstreamer-lite/Makefile +++ b/modules/javafx.media/src/main/native/gstreamer/projects/mac/gstreamer-lite/Makefile @@ -28,7 +28,6 @@ DIRLIST = gstreamer/gst \ gst-plugins-base/gst/typefind \ gst-plugins-base/gst/audioconvert \ gst-plugins-good/gst/audioparsers \ - gst-plugins-good/gst/isomp4 \ gst-plugins-good/gst/audiofx \ gst-plugins-good/gst/equalizer \ gst-plugins-good/gst/spectrum \ @@ -253,16 +252,6 @@ C_SOURCES = gstreamer/gst/gst.c \ gst-plugins-good/gst/equalizer/gstiirequalizer.c \ gst-plugins-good/gst/equalizer/gstiirequalizernbands.c \ gst-plugins-good/gst/equalizer/gstiirequalizerplugin.c \ - gst-plugins-good/gst/isomp4/isomp4-plugin.c \ - gst-plugins-good/gst/isomp4/gstisomp4element.c \ - gst-plugins-good/gst/isomp4/qtdemux-webvtt.c \ - gst-plugins-good/gst/isomp4/qtdemux.c \ - gst-plugins-good/gst/isomp4/gstisoff.c \ - gst-plugins-good/gst/isomp4/qtdemux_dump.c \ - gst-plugins-good/gst/isomp4/qtdemux_lang.c \ - gst-plugins-good/gst/isomp4/qtdemux_tags.c \ - gst-plugins-good/gst/isomp4/qtdemux_tree.c \ - gst-plugins-good/gst/isomp4/qtdemux_types.c \ gst-plugins-good/gst/spectrum/gstspectrum.c \ gst-plugins-good/gst/wavparse/gstwavparse.c \ gstreamer/plugins/elements/gstcoreelementsplugin.c \ diff --git a/modules/javafx.media/src/main/native/gstreamer/projects/win/fxplugins/Makefile b/modules/javafx.media/src/main/native/gstreamer/projects/win/fxplugins/Makefile index 19390843dab..881d8db4315 100644 --- a/modules/javafx.media/src/main/native/gstreamer/projects/win/fxplugins/Makefile +++ b/modules/javafx.media/src/main/native/gstreamer/projects/win/fxplugins/Makefile @@ -27,7 +27,8 @@ SYSTEM_LIBS = glib-lite.lib \ oleaut32.lib \ strmiids.lib \ Mfplat.lib \ - mfuuid.lib + mfuuid.lib \ + Mfreadwrite.lib LDFLAGS = -out:$(shell cygpath -ma $(TARGET)) -nologo -incremental:no -libpath:$(shell cygpath -ma $(BUILD_DIR)) -dll $(SYSTEM_LIBS) \ -nodefaultlib:libcmt -manifest -manifestfile:$(MANIFEST) -manifestuac:"level='asInvoker' uiAccess='false'" -implib:$(IMPLIB) \ @@ -60,6 +61,8 @@ CPP_SOURCES = dshowwrapper/Allocator.cpp \ dshowwrapper/Sink.cpp \ dshowwrapper/Src.cpp \ mfwrapper/mfwrapper.cpp \ + mfwrapper/mfdemux.cpp \ + mfwrapper/mfgstbytestream.cpp \ mfwrapper/mfgstbuffer.cpp COMPILER_FLAGS = -nologo -W3 -WX- -EHsc -GS -fp:precise \ diff --git a/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite.def b/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite.def index 083e69d1957..3ae195e0118 100644 --- a/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite.def +++ b/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite.def @@ -33,124 +33,131 @@ gst_caps_get_size @31 NONAME gst_caps_get_structure @32 NONAME gst_caps_new_simple @33 NONAME gst_caps_set_simple @34 NONAME -gst_child_proxy_get_child_by_index @35 NONAME -gst_child_proxy_get_type @36 NONAME -gst_core_error_quark @37 NONAME -gst_element_add_pad @38 NONAME -gst_element_class_add_pad_template @39 NONAME -gst_element_class_get_pad_template @40 NONAME -gst_element_class_set_metadata @41 NONAME -gst_element_class_set_static_metadata @42 NONAME -gst_element_factory_make @43 NONAME -gst_element_get_factory @44 NONAME -gst_element_get_state @45 NONAME -gst_element_get_static_pad @46 NONAME -gst_element_get_type @47 NONAME -gst_element_link @48 NONAME -gst_element_link_many @49 NONAME -gst_element_message_full @50 NONAME -gst_element_no_more_pads @51 NONAME -gst_element_post_message @52 NONAME -gst_element_provide_clock @53 NONAME -gst_element_query_duration @54 NONAME -gst_element_query_position @55 NONAME -gst_element_register @56 NONAME -gst_element_remove_pad @57 NONAME -gst_element_seek @58 NONAME -gst_element_set_state @59 NONAME -gst_element_sync_state_with_parent @60 NONAME -gst_event_copy_segment @61 NONAME -gst_event_get_seqnum @62 NONAME -gst_event_new_caps @63 NONAME -gst_event_new_custom @64 NONAME -gst_event_new_eos @65 NONAME -gst_event_new_flush_start @66 NONAME -gst_event_new_flush_stop @67 NONAME -gst_event_new_seek @68 NONAME -gst_event_new_segment @69 NONAME -gst_event_new_stream_start @70 NONAME -gst_event_parse_caps @71 NONAME -gst_event_parse_seek @72 NONAME -gst_event_set_group_id @73 NONAME -gst_event_set_seqnum @74 NONAME -gst_ghost_pad_new @75 NONAME -gst_init_check @76 NONAME -gst_iterator_free @77 NONAME -gst_iterator_next @78 NONAME -gst_iterator_resync @79 NONAME -gst_message_get_structure @80 NONAME -gst_message_new_application @81 NONAME -gst_message_new_error @82 NONAME -gst_message_parse_error @83 NONAME -gst_message_parse_info @84 NONAME -gst_message_parse_state_changed @85 NONAME -gst_message_parse_warning @86 NONAME -gst_mini_object_copy @87 NONAME -gst_mini_object_make_writable @88 NONAME -gst_mini_object_ref @89 NONAME -gst_mini_object_unref @90 NONAME -gst_object_get_type @91 NONAME -gst_object_ref @92 NONAME -gst_object_unref @93 NONAME -gst_pad_activate_mode @94 NONAME -gst_pad_add_probe @95 NONAME -gst_pad_create_stream_id @96 NONAME -gst_pad_event_default @97 NONAME -gst_pad_get_current_caps @98 NONAME -gst_pad_is_active @99 NONAME -gst_pad_is_linked @100 NONAME -gst_pad_link @101 NONAME -gst_pad_new_from_static_template @102 NONAME -gst_pad_new_from_template @103 NONAME -gst_pad_pause_task @104 NONAME -gst_pad_peer_query_convert @105 NONAME -gst_pad_peer_query_duration @106 NONAME -gst_pad_push @107 NONAME -gst_pad_push_event @108 NONAME -gst_pad_query_default @109 NONAME -gst_pad_remove_probe @110 NONAME -gst_pad_set_activate_function_full @111 NONAME -gst_pad_set_activatemode_function_full @112 NONAME -gst_pad_set_active @113 NONAME -gst_pad_set_chain_function_full @114 NONAME -gst_pad_set_event_function_full @115 NONAME -gst_pad_set_getrange_function_full @116 NONAME -gst_pad_set_query_function_full @117 NONAME -gst_pad_start_task @118 NONAME -gst_pad_stop_task @119 NONAME -gst_pad_use_fixed_caps @120 NONAME -gst_pipeline_get_bus @121 NONAME -gst_pipeline_get_type @122 NONAME -gst_pipeline_new @123 NONAME -gst_pipeline_set_clock @124 NONAME -gst_query_add_scheduling_mode @125 NONAME -gst_query_parse_duration @126 NONAME -gst_query_parse_position @127 NONAME -gst_query_parse_seeking @128 NONAME -gst_query_set_duration @129 NONAME -gst_query_set_position @130 NONAME -gst_query_set_scheduling @131 NONAME -gst_query_set_seeking @132 NONAME -gst_resource_error_quark @133 NONAME -gst_sample_get_buffer @134 NONAME -gst_sample_get_caps @135 NONAME -gst_sample_new @136 NONAME -gst_segment_copy_into @137 NONAME -gst_segment_init @138 NONAME -gst_segtrap_set_enabled @139 NONAME -gst_static_pad_template_get @140 NONAME -gst_stream_error_quark @141 NONAME -gst_structure_get_boolean @142 NONAME -gst_structure_get_clock_time @143 NONAME -gst_structure_get_fraction @144 NONAME -gst_structure_get_int @145 NONAME -gst_structure_get_name @146 NONAME -gst_structure_get_string @147 NONAME -gst_structure_get_value @148 NONAME -gst_structure_has_name @149 NONAME -gst_structure_new @150 NONAME -gst_structure_new_empty @151 NONAME -gst_structure_set @152 NONAME -gst_util_group_id_next @153 NONAME -gst_value_list_get_value @154 NONAME -gst_video_alignment_reset @155 NONAME +gst_caps_to_string @35 NONAME +gst_child_proxy_get_child_by_index @36 NONAME +gst_child_proxy_get_type @37 NONAME +gst_core_error_quark @38 NONAME +gst_element_add_pad @39 NONAME +gst_element_class_add_pad_template @40 NONAME +gst_element_class_get_pad_template @41 NONAME +gst_element_class_set_metadata @42 NONAME +gst_element_class_set_static_metadata @43 NONAME +gst_element_factory_make @44 NONAME +gst_element_get_factory @45 NONAME +gst_element_get_state @46 NONAME +gst_element_get_static_pad @47 NONAME +gst_element_get_type @48 NONAME +gst_element_link @49 NONAME +gst_element_link_many @50 NONAME +gst_element_message_full @51 NONAME +gst_element_no_more_pads @52 NONAME +gst_element_post_message @53 NONAME +gst_element_provide_clock @54 NONAME +gst_element_query_duration @55 NONAME +gst_element_query_position @56 NONAME +gst_element_register @57 NONAME +gst_element_remove_pad @58 NONAME +gst_element_seek @59 NONAME +gst_element_set_state @60 NONAME +gst_element_sync_state_with_parent @61 NONAME +gst_event_copy_segment @62 NONAME +gst_event_get_seqnum @63 NONAME +gst_event_get_structure @64 NONAME +gst_event_new_caps @65 NONAME +gst_event_new_custom @66 NONAME +gst_event_new_eos @67 NONAME +gst_event_new_flush_start @68 NONAME +gst_event_new_flush_stop @69 NONAME +gst_event_new_seek @70 NONAME +gst_event_new_segment @71 NONAME +gst_event_new_stream_start @72 NONAME +gst_event_parse_caps @73 NONAME +gst_event_parse_seek @74 NONAME +gst_event_parse_segment @75 NONAME +gst_event_set_group_id @76 NONAME +gst_event_set_seqnum @77 NONAME +gst_ghost_pad_new @78 NONAME +gst_init_check @79 NONAME +gst_iterator_free @80 NONAME +gst_iterator_next @81 NONAME +gst_iterator_resync @82 NONAME +gst_message_get_structure @83 NONAME +gst_message_new_application @84 NONAME +gst_message_new_error @85 NONAME +gst_message_parse_error @86 NONAME +gst_message_parse_info @87 NONAME +gst_message_parse_state_changed @88 NONAME +gst_message_parse_warning @89 NONAME +gst_mini_object_copy @90 NONAME +gst_mini_object_make_writable @91 NONAME +gst_mini_object_ref @92 NONAME +gst_mini_object_unref @93 NONAME +gst_object_get_type @94 NONAME +gst_object_ref @95 NONAME +gst_object_unref @96 NONAME +gst_pad_activate_mode @97 NONAME +gst_pad_add_probe @98 NONAME +gst_pad_create_stream_id @99 NONAME +gst_pad_event_default @100 NONAME +gst_pad_get_current_caps @101 NONAME +gst_pad_is_active @102 NONAME +gst_pad_is_linked @103 NONAME +gst_pad_link @104 NONAME +gst_pad_new_from_static_template @105 NONAME +gst_pad_new_from_template @106 NONAME +gst_pad_pause_task @107 NONAME +gst_pad_peer_query_convert @108 NONAME +gst_pad_peer_query_duration @109 NONAME +gst_pad_pull_range @110 NONAME +gst_pad_push @111 NONAME +gst_pad_push_event @112 NONAME +gst_pad_query_default @113 NONAME +gst_pad_remove_probe @114 NONAME +gst_pad_set_activate_function_full @115 NONAME +gst_pad_set_activatemode_function_full @116 NONAME +gst_pad_set_active @117 NONAME +gst_pad_set_chain_function_full @118 NONAME +gst_pad_set_event_function_full @119 NONAME +gst_pad_set_getrange_function_full @120 NONAME +gst_pad_set_query_function_full @121 NONAME +gst_pad_start_task @122 NONAME +gst_pad_stop_task @123 NONAME +gst_pad_use_fixed_caps @124 NONAME +gst_pipeline_get_bus @125 NONAME +gst_pipeline_get_type @126 NONAME +gst_pipeline_new @127 NONAME +gst_pipeline_set_clock @128 NONAME +gst_query_add_scheduling_mode @129 NONAME +gst_query_parse_duration @130 NONAME +gst_query_parse_position @131 NONAME +gst_query_parse_seeking @132 NONAME +gst_query_set_duration @133 NONAME +gst_query_set_position @134 NONAME +gst_query_set_scheduling @135 NONAME +gst_query_set_seeking @136 NONAME +gst_resource_error_quark @137 NONAME +gst_sample_get_buffer @138 NONAME +gst_sample_get_caps @139 NONAME +gst_sample_new @140 NONAME +gst_segment_copy_into @141 NONAME +gst_segment_init @142 NONAME +gst_segtrap_set_enabled @143 NONAME +gst_static_pad_template_get @144 NONAME +gst_stream_error_quark @145 NONAME +gst_structure_get_boolean @146 NONAME +gst_structure_get_clock_time @147 NONAME +gst_structure_get_fraction @148 NONAME +gst_structure_get_int @149 NONAME +gst_structure_get_int64 @150 NONAME +gst_structure_get_name @151 NONAME +gst_structure_get_string @152 NONAME +gst_structure_get_uint @153 NONAME +gst_structure_get_value @154 NONAME +gst_structure_has_name @155 NONAME +gst_structure_new @156 NONAME +gst_structure_new_empty @157 NONAME +gst_structure_set @158 NONAME +gst_util_group_id_next @159 NONAME +gst_value_list_get_value @160 NONAME +gst_video_alignment_reset @161 NONAME +gst_caps_is_equal @162 NONAME diff --git a/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins b/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins index 3ae65987a4a..09eb592bf1c 100644 --- a/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins +++ b/modules/javafx.media/src/main/native/gstreamer/projects/win/gstreamer-lite/Makefile.gstplugins @@ -16,7 +16,6 @@ DIRLIST = gst-plugins-bad/gst/aiff/ \ gst-plugins-good/gst/audioparsers/ \ gst-plugins-good/sys/directsound/ \ gst-plugins-good/gst/equalizer/ \ - gst-plugins-good/gst/isomp4/ \ gst-plugins-good/gst/spectrum/ \ gst-plugins-good/gst/wavparse/ \ gstreamer/plugins/elements/ \ @@ -41,16 +40,6 @@ C_SOURCES = gst-plugins-bad/gst/aiff/aiff.c \ gst-plugins-good/gst/equalizer/gstiirequalizer.c \ gst-plugins-good/gst/equalizer/gstiirequalizernbands.c \ gst-plugins-good/gst/equalizer/gstiirequalizerplugin.c \ - gst-plugins-good/gst/isomp4/isomp4-plugin.c \ - gst-plugins-good/gst/isomp4/gstisomp4element.c \ - gst-plugins-good/gst/isomp4/qtdemux-webvtt.c \ - gst-plugins-good/gst/isomp4/qtdemux.c \ - gst-plugins-good/gst/isomp4/gstisoff.c \ - gst-plugins-good/gst/isomp4/qtdemux_dump.c \ - gst-plugins-good/gst/isomp4/qtdemux_lang.c \ - gst-plugins-good/gst/isomp4/qtdemux_tags.c \ - gst-plugins-good/gst/isomp4/qtdemux_tree.c \ - gst-plugins-good/gst/isomp4/qtdemux_types.c \ gst-plugins-good/gst/spectrum/gstspectrum.c \ gst-plugins-good/gst/wavparse/gstwavparse.c \ gstreamer/plugins/elements/gstcoreelementsplugin.c \ diff --git a/modules/javafx.media/src/main/native/jfxmedia/Common/ProductFlags.h b/modules/javafx.media/src/main/native/jfxmedia/Common/ProductFlags.h index 9ea3a0086ef..4ce2648ea4b 100644 --- a/modules/javafx.media/src/main/native/jfxmedia/Common/ProductFlags.h +++ b/modules/javafx.media/src/main/native/jfxmedia/Common/ProductFlags.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,10 +58,6 @@ #define ENABLE_INSTRUMENTS 0 #define ENABLE_PROGRESS_BUFFER 1 -#define ENABLE_BREAK_MY_DATA 0 -#define BREAK_MY_DATA_SKIP 250000 -#define BREAK_MY_DATA_PROBABILITY 0.0001 - // Enable detection of memory leaks using Visual Studio // This option will only work in Debug mode #define ENABLE_VISUAL_STUDIO_MEMORY_LEAKS_DETECTION 0 diff --git a/modules/javafx.media/src/main/native/jfxmedia/PipelineManagement/PipelineOptions.h b/modules/javafx.media/src/main/native/jfxmedia/PipelineManagement/PipelineOptions.h index f1c961163bf..4b003745397 100644 --- a/modules/javafx.media/src/main/native/jfxmedia/PipelineManagement/PipelineOptions.h +++ b/modules/javafx.media/src/main/native/jfxmedia/PipelineManagement/PipelineOptions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ class CPipelineOptions CPipelineOptions(int pipelineType = kSingleSourcePipeline) : m_PipelineType(pipelineType), m_bBufferingEnabled(false), + m_bParserPullModeOnly(false), m_StreamMimeType(-1), m_AudioStreamMimeType(-1), m_bHLSModeEnabled(false), @@ -56,7 +57,11 @@ class CPipelineOptions inline int GetPipelineType() { return m_PipelineType; } inline void SetBufferingEnabled(bool enabled) { m_bBufferingEnabled = enabled; } - inline bool GetBufferingEnabled() { return m_bBufferingEnabled; } + // Forced pull mode will use buffer, so always return true for pull mode only + inline bool GetBufferingEnabled() { return m_bBufferingEnabled || m_bParserPullModeOnly; } + + inline void SetParserPullModeOnly(bool enabled) { m_bParserPullModeOnly = enabled; } + inline bool GetParserPullModeOnly() { return m_bParserPullModeOnly; } inline void SetContentType(string contentType) { m_ContentType = contentType; } inline const string GetContentType() { return m_ContentType; } @@ -80,10 +85,10 @@ class CPipelineOptions // of IDs except they shoule be unique. inline bool ForceDefaultTrackID() { return (m_PipelineType == kAudioSourcePipeline); } - inline CPipelineOptions* SetStreamParser(string streamParser) { m_StreamParser = streamParser; return this;} - inline CPipelineOptions* SetAudioStreamParser(string ausioStreamParser) { m_AudioStreamParser = ausioStreamParser; return this;} - inline CPipelineOptions* SetVideoDecoder(string videoDecoder) { m_VideoDecoder = videoDecoder; return this;} - inline CPipelineOptions* SetAudioDecoder(string audioDecoder) { m_AudioDecoder = audioDecoder; return this;} + inline CPipelineOptions* SetStreamParser(string streamParser) { m_StreamParser = streamParser; return this; } + inline CPipelineOptions* SetAudioStreamParser(string audioStreamParser) { m_AudioStreamParser = audioStreamParser; return this; } + inline CPipelineOptions* SetVideoDecoder(string videoDecoder) { m_VideoDecoder = videoDecoder; return this; } + inline CPipelineOptions* SetAudioDecoder(string audioDecoder) { m_AudioDecoder = audioDecoder; return this; } inline const char* GetStreamParser() { return GetCharFromString(&m_StreamParser); } inline const char* GetAudioStreamParser() { return GetCharFromString(&m_AudioStreamParser); } @@ -100,6 +105,10 @@ class CPipelineOptions private: int m_PipelineType; bool m_bBufferingEnabled; + // If true parser only supports pull mode. We can detect dynamically to + // query parser properties, but we know in advanced this information anyway, + // so we will set it manually. + bool m_bParserPullModeOnly; // ContentType based on content type of main URL. string m_ContentType; // Main stream mime type, might be different than ContentType for HLS. diff --git a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstAVPlaybackPipeline.cpp b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstAVPlaybackPipeline.cpp index 743633601ff..ea0384e6c48 100644 --- a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstAVPlaybackPipeline.cpp +++ b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstAVPlaybackPipeline.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -186,7 +186,7 @@ bool CGstAVPlaybackPipeline::IsCodecSupported(GstCaps *pCaps) if (strstr(mimetype, "video/x-h264") != NULL) // H.264 { gboolean is_supported = FALSE; - g_object_set(m_Elements[VIDEO_DECODER], "codec-id", (gint)JFX_CODEC_ID_AVC1, NULL); // Check for AVC1 (MP4). For HLS we should get error early + g_object_set(m_Elements[VIDEO_DECODER], "codec-id", (gint)JFX_CODEC_ID_H264, NULL); // Check for H.264 with start codes. g_object_get(m_Elements[VIDEO_DECODER], "is-supported", &is_supported, NULL); if (is_supported) { @@ -201,7 +201,7 @@ bool CGstAVPlaybackPipeline::IsCodecSupported(GstCaps *pCaps) else if (strstr(mimetype, "video/x-h265") != NULL) // H.265 { gboolean is_supported = FALSE; - g_object_set(m_Elements[VIDEO_DECODER], "codec-id", (gint)JFX_CODEC_ID_H265, NULL); // Check for H265 (MP4) + g_object_set(m_Elements[VIDEO_DECODER], "codec-id", (gint)JFX_CODEC_ID_HEVC, NULL); // Check for H265 (MP4) g_object_get(m_Elements[VIDEO_DECODER], "is-supported", &is_supported, NULL); if (is_supported) { @@ -271,7 +271,7 @@ bool CGstAVPlaybackPipeline::LoadDecoder(GstCaps *pCaps) { if (strstr(mimetype, "video/x-h264") != NULL) // H.264 { - strVideoDecoderName = "dshowwrapper"; + strVideoDecoderName = "mfwrapper"; } else if (strstr(mimetype, "video/x-h265") != NULL) // H.265 { diff --git a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp index aaba230b271..32f25fba9f5 100644 --- a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp +++ b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,15 @@ #define HLS_VALUE_MIMETYPE_FMP4 3 #define HLS_VALUE_MIMETYPE_AAC 4 +// Queue settings common +#define QUEUE_MAX_BYTES 0 +#define QUEUE_MAX_BUFFERS 10 +#define QUEUE_MAX_TIME 0 + +// Queue settings HLS Live +#define QUEUE_MAX_BYTES_HLSL 0 +#define QUEUE_MAX_BUFFERS_HLSL 180 +#define QUEUE_MAX_TIME_HLSL (3 * GST_SECOND) //************************************************************************************************* //********** class CGstPipelineFactory @@ -84,6 +93,10 @@ uint32_t CGstPipelineFactory::CreatePlayerPipeline(CLocator* locator, CPipelineO // Save content type to options pOptions->SetContentType(locator->GetContentType()); + uRetCode = ConfigurePipelineOptions(pOptions); + if (uRetCode != ERROR_NONE) + return uRetCode; + CLocatorStream* streamLocator = (CLocatorStream*)locator; CStreamCallbacks *callbacks = streamLocator->GetCallbacks(); CStreamCallbacks *audioCallbacks = streamLocator->GetAudioCallbacks(); @@ -141,6 +154,23 @@ uint32_t CGstPipelineFactory::CreatePlayerPipeline(CLocator* locator, CPipelineO return uRetCode; } +// TODO: Move all configuration before creating pipeline +uint32_t CGstPipelineFactory::ConfigurePipelineOptions(CPipelineOptions *pOptions) +{ + if (NULL == pOptions) + return ERROR_FUNCTION_PARAM_NULL; + +#if TARGET_OS_WIN32 + if (CONTENT_TYPE_MP4 == pOptions->GetContentType() || + CONTENT_TYPE_M4A == pOptions->GetContentType() || + CONTENT_TYPE_M4V == pOptions->GetContentType()) { + pOptions->SetParserPullModeOnly(true); + } +#endif + + return ERROR_NONE; +} + // Creates pipeline based on options provided. // Basically calls Create*Pipeline() based on options. uint32_t CGstPipelineFactory::CreatePipeline(CPipelineOptions *pOptions, GstElementContainer* pElements, CPipeline** ppPipeline) @@ -262,6 +292,15 @@ uint32_t CGstPipelineFactory::CreateSourceElement(CLocator *locator, CStreamCall g_object_set(javaSource, "mimetype", CONTENT_TYPE_FMP4, NULL); else if (streamMimeType == HLS_VALUE_MIMETYPE_AAC) g_object_set(javaSource, "mimetype", CONTENT_TYPE_AAC, NULL); + // Use CONTENT_TYPE_FMP4 for CONTENT_TYPE_MP4 and CONTENT_TYPE_M4V. + // qtdemux only accepts "video/quicktime" and "audio/x-m4a", so + // we convert "video/mp4" and "video/x-m4v" to "video/quicktime". + else if (CONTENT_TYPE_MP4 == locator->GetContentType() || + CONTENT_TYPE_M4V == locator->GetContentType()) + g_object_set(javaSource, "mimetype", CONTENT_TYPE_FMP4, NULL); + else // Set mimetype to content type of locator anyway, so javasource + // can send caps event which is required by GStreamer. + g_object_set (javaSource, "mimetype", locator->GetContentType().c_str(), NULL); g_object_set(javaSource, "size", (gint64)locator->GetSizeHint(), @@ -273,6 +312,10 @@ uint32_t CGstPipelineFactory::CreateSourceElement(CLocator *locator, CStreamCall bool needBuffer = callbacks->NeedBuffer(); pOptions->SetBufferingEnabled(needBuffer); + // Check if we need to add buffer anyway for pull only parser + if (!isRandomAccess && pOptions->GetParserPullModeOnly()) + needBuffer = true; + if (needBuffer) { g_object_set(javaSource, "stop-on-pause", FALSE, NULL); @@ -361,104 +404,31 @@ GstElement* CGstPipelineFactory::CreateAudioSinkElement() #endif } -void CGstPipelineFactory::OnBufferPadAdded(GstElement* element, GstPad* pad, GstElement* peer) +uint32_t CGstPipelineFactory::AttachToSource(GstBin* bin, GstElement* source, GstElement* buffer, GstElement* element) { - uint32_t uErrorCode = ERROR_NONE; - - GstElement* source_bin = GST_ELEMENT_PARENT(element); - GstElement* pipeline = GST_ELEMENT_PARENT(source_bin); - - GstPad *src_pad = gst_ghost_pad_new("src", pad); - if (NULL == src_pad) - uErrorCode = ERROR_GSTREAMER_CREATE_GHOST_PAD; - - if (ERROR_NONE == uErrorCode) + // Look for progressbuffer element in the source if buffer is not provided + if (buffer == NULL) { - if (!gst_pad_set_active(src_pad, TRUE) || !gst_element_add_pad(source_bin, src_pad)) - uErrorCode = ERROR_GSTREAMER_ELEMENT_ADD_PAD; - - if (ERROR_NONE == uErrorCode) + buffer = GetByFactoryName(source, "progressbuffer"); + // Look for hlsprogressbuffer + if (buffer == NULL) { - if (!gst_bin_add(GST_BIN(pipeline), peer)) - uErrorCode = ERROR_GSTREAMER_BIN_ADD_ELEMENT; - - if (ERROR_NONE == uErrorCode) - { - if (GST_STATE_CHANGE_FAILURE == gst_element_set_state(peer, GST_STATE_READY)) - uErrorCode = ERROR_GSTREAMER_PIPELINE_STATE_CHANGE; - - if (ERROR_NONE == uErrorCode) - { - if (!gst_element_link(source_bin, peer)) - uErrorCode = ERROR_GSTREAMER_ELEMENT_LINK; - - if (ERROR_NONE == uErrorCode) - if (!gst_element_sync_state_with_parent(peer)) - uErrorCode = ERROR_GSTREAMER_PIPELINE_STATE_CHANGE; - } - } + buffer = GetByFactoryName(source, "hlsprogressbuffer"); } } - - if (ERROR_NONE != uErrorCode) - { - GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE (pipeline)); - GError* error = g_error_new (0, uErrorCode, "%s", - "Error in CGstPipelineFactory::OnBufferPadAdded()."); - GstMessage* message = gst_message_new_error (GST_OBJECT (pipeline), error, - "Error in CGstPipelineFactory::OnBufferPadAdded()."); - gst_bus_post (bus, message); - gst_object_unref (bus); - } - - g_signal_handlers_disconnect_by_func(element, (void*)G_CALLBACK(OnBufferPadAdded), peer); -} - -uint32_t CGstPipelineFactory::AttachToSource(GstBin* bin, GstElement* source, GstElement* buffer, GstElement* element) -{ - // Look for progressbuffer element in the source - GstElement* progressbuffer = GetByFactoryName(source, "progressbuffer"); - if (progressbuffer) + else { -#if ENABLE_BREAK_MY_DATA - GstElement* dataBreaker = CreateElement ("breakmydata"); - g_object_set (G_OBJECT (dataBreaker), "skip", BREAK_MY_DATA_SKIP, "probability", BREAK_MY_DATA_PROBABILITY, NULL); - if (!gst_bin_add (bin, dataBreaker)) - return ERROR_GSTREAMER_BIN_ADD_ELEMENT; - if (!gst_element_link(dataBreaker, element)) - return ERROR_GSTREAMER_ELEMENT_LINK; - g_signal_connect (progressbuffer, "pad-added", G_CALLBACK (OnBufferPadAdded), dataBreaker); -#else - g_signal_connect (progressbuffer, "pad-added", G_CALLBACK (OnBufferPadAdded), element); -#endif - gst_object_unref(progressbuffer); - return ERROR_NONE; + // Ref provided buffer, since we will unref it + gst_object_ref(buffer); } - // Source does not contain "progressbuffer". if (!gst_bin_add(bin, element)) return ERROR_GSTREAMER_BIN_ADD_ELEMENT; -#if ENABLE_BREAK_MY_DATA - GstElement* dataBreaker = CreateElement ("breakmydata"); - g_object_set (G_OBJECT (dataBreaker), "skip", BREAK_MY_DATA_SKIP, "probability", BREAK_MY_DATA_PROBABILITY, NULL); - gst_bin_add (GST_BIN (pipeline), dataBreaker, NULL); - gst_element_link_many(source, dataBreaker, element); -#else - - // Create src pad on source bin if we have hlsprogressbuffer - GstElement* hlsprogressbuffer = NULL; + // Create src pad on source bin if we have buffer if (buffer) { - gst_object_ref(buffer); - hlsprogressbuffer = buffer; - } - else - hlsprogressbuffer = GetByFactoryName(source, "hlsprogressbuffer"); - - if (hlsprogressbuffer) - { - GstPad* src_pad = gst_element_get_static_pad(hlsprogressbuffer, "src"); + GstPad* src_pad = gst_element_get_static_pad(buffer, "src"); if (NULL == src_pad) return ERROR_GSTREAMER_ELEMENT_GET_PAD; @@ -478,12 +448,11 @@ uint32_t CGstPipelineFactory::AttachToSource(GstBin* bin, GstElement* source, Gs gst_object_unref(src_pad); - gst_object_unref(hlsprogressbuffer); + gst_object_unref(buffer); } if (!gst_element_link(source, element)) return ERROR_GSTREAMER_ELEMENT_LINK; -#endif return ERROR_NONE; } @@ -508,7 +477,7 @@ uint32_t CGstPipelineFactory::CreateMP4Pipeline(GstElement* pVideoSink, #if TARGET_OS_WIN32 // We need to load dshowwrapper (H.264) or mfwrapper (H.265), but we do not know which one based on .mp4 // extension, so intead we will load video decoder dynamically when qtdemux will signal video pad added. - pOptions->SetStreamParser("qtdemux")->SetAudioDecoder("dshowwrapper"); + pOptions->SetStreamParser("mfdemux")->SetAudioDecoder("dshowwrapper"); return CreateAVPipeline(true, pVideoSink, pOptions, pElements, ppPipeline); #elif TARGET_OS_MAC return ERROR_PLATFORM_UNSUPPORTED; @@ -566,13 +535,13 @@ uint32_t CGstPipelineFactory::CreateHLSPipeline(GstElement* pVideoSink, CPipelin if (pOptions->GetStreamMimeType() == HLS_VALUE_MIMETYPE_MP2T) pOptions->SetStreamParser("dshowwrapper")->SetVideoDecoder("dshowwrapper"); else if (pOptions->GetStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4) - pOptions->SetStreamParser("qtdemux"); // Video decoder loaded dynamically + pOptions->SetStreamParser("mfdemux"); // Video decoder loaded dynamically else return ERROR_PLATFORM_UNSUPPORTED; // Audio stream can be FMP4 or AAC if (pOptions->GetAudioStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4) - pOptions->SetAudioStreamParser("qtdemux")->SetAudioDecoder("dshowwrapper"); + pOptions->SetAudioStreamParser("mfdemux")->SetAudioDecoder("dshowwrapper"); else if (pOptions->GetAudioStreamMimeType() == HLS_VALUE_MIMETYPE_AAC) pOptions->SetAudioDecoder("dshowwrapper"); else @@ -600,7 +569,7 @@ uint32_t CGstPipelineFactory::CreateHLSPipeline(GstElement* pVideoSink, CPipelin else if (pOptions->GetStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4) { // Video decoder is loaded dynamically - pOptions->SetStreamParser("qtdemux")->SetAudioDecoder("dshowwrapper"); + pOptions->SetStreamParser("mfdemux")->SetAudioDecoder("dshowwrapper"); return CreateAVPipeline(true, pVideoSink, pOptions, pElements, ppPipeline); } else @@ -689,7 +658,7 @@ uint32_t CGstPipelineFactory::CreateAudioPipeline(bool bConvertFormat, CPipeline int flags = 0; GstElement* audiobin; - uRetCode = CreateAudioBin(pOptions->GetStreamParser(), + uRetCode = CreateAudioBin(pOptions, pOptions->GetStreamParser(), pOptions->GetAudioDecoder(), bConvertFormat, pElements, &flags, &audiobin); if (ERROR_NONE != uRetCode) @@ -752,6 +721,10 @@ uint32_t CGstPipelineFactory::CreateAVPipeline(bool bConvertFormat, GstElement* if (bAudioStream) { g_object_set(demuxer, "disable-mp2t-pts-reset", TRUE, NULL); } + if (pOptions->GetHLSModeEnabled() && + strcmp(pOptions->GetStreamParser(), "mfdemux") == 0) { + g_object_set(demuxer, "hls-mode", TRUE, NULL); + } if (!gst_bin_add (GST_BIN (pipeline), source)) return ERROR_GSTREAMER_BIN_ADD_ELEMENT; uRetCode = AttachToSource(GST_BIN (pipeline), source, (*pElements)[SOURCE_BUFFER], demuxer); @@ -770,6 +743,15 @@ uint32_t CGstPipelineFactory::CreateAVPipeline(bool bConvertFormat, GstElement* if (NULL == audioDemuxer) return ERROR_GSTREAMER_ELEMENT_CREATE; + // Configure audio demuxer if needed + if (bAudioStream) { + g_object_set(audioDemuxer, "disable-mp2t-pts-reset", TRUE, NULL); + } + if (pOptions->GetHLSModeEnabled() && + strcmp(pOptions->GetAudioStreamParser(), "mfdemux") == 0) { + g_object_set(audioDemuxer, "hls-mode", TRUE, NULL); + } + uRetCode = AttachToSource(GST_BIN (pipeline), audioSource, (*pElements)[AUDIO_SOURCE_BUFFER], audioDemuxer); if (ERROR_NONE != uRetCode) return uRetCode; @@ -778,7 +760,7 @@ uint32_t CGstPipelineFactory::CreateAVPipeline(bool bConvertFormat, GstElement* int audioFlags = 0; GstElement *audiobin = NULL; - uRetCode = CreateAudioBin(NULL, pOptions->GetAudioDecoder(), bConvertFormat, + uRetCode = CreateAudioBin(pOptions, NULL, pOptions->GetAudioDecoder(), bConvertFormat, pElements, &audioFlags, &audiobin); if (ERROR_NONE != uRetCode) return uRetCode; @@ -807,7 +789,7 @@ uint32_t CGstPipelineFactory::CreateAVPipeline(bool bConvertFormat, GstElement* } GstElement *videobin; - uRetCode = CreateVideoBin(pOptions->GetVideoDecoder(), pVideoSink, pElements, &videobin); + uRetCode = CreateVideoBin(pOptions, pOptions->GetVideoDecoder(), pVideoSink, pElements, &videobin); if (ERROR_NONE != uRetCode) return uRetCode; @@ -823,12 +805,12 @@ uint32_t CGstPipelineFactory::CreateAVPipeline(bool bConvertFormat, GstElement* return uRetCode; } -uint32_t CGstPipelineFactory::CreateAudioBin(const char* strParserName, const char* strDecoderName, - bool bConvertFormat, +uint32_t CGstPipelineFactory::CreateAudioBin(CPipelineOptions *pOptions, const char* strParserName, + const char* strDecoderName, bool bConvertFormat, GstElementContainer* elements, int* pFlags, GstElement** ppAudiobin) { - if ((NULL == strParserName && NULL == strDecoderName) || NULL == elements || NULL == pFlags || NULL == ppAudiobin) + if (NULL == pOptions || (NULL == strParserName && NULL == strDecoderName) || NULL == elements || NULL == pFlags || NULL == ppAudiobin) return ERROR_FUNCTION_PARAM_NULL; *ppAudiobin = gst_bin_new(NULL); @@ -963,15 +945,37 @@ uint32_t CGstPipelineFactory::CreateAudioBin(const char* strParserName, const ch *pFlags |= AUDIO_DECODER_HAS_SOURCE_PROBE | AUDIO_DECODER_HAS_SINK_PROBE; } - // Switch off limiting of the audioqueue for bytes and buffers. - g_object_set(audioqueue, "max-size-bytes", (guint)0, "max-size-buffers", (guint)10, "max-size-time", (guint64)0, NULL); + // Configure audio queue + guint max_bytes = QUEUE_MAX_BYTES; + guint max_buffers = QUEUE_MAX_BUFFERS; + guint64 max_time = QUEUE_MAX_TIME; + +#if TARGET_OS_WIN32 + // Special case for HLS on Windows due to source reader reload for + // each segment. We need to queue more data to avoid glitching when + // reload. + if (pOptions->GetHLSModeEnabled() && + (pOptions->GetStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4 || + pOptions->GetAudioStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4)) + { + max_bytes = QUEUE_MAX_BYTES_HLSL; + max_buffers = QUEUE_MAX_BUFFERS_HLSL; + max_time = QUEUE_MAX_TIME_HLSL; + } +#endif + + g_object_set(audioqueue, "max-size-bytes", max_bytes, "max-size-buffers", + max_buffers, "max-size-time", max_time, NULL); return ERROR_NONE; } -uint32_t CGstPipelineFactory::CreateVideoBin(const char* strDecoderName, GstElement* pVideoSink, +uint32_t CGstPipelineFactory::CreateVideoBin(CPipelineOptions *pOptions, const char* strDecoderName, GstElement* pVideoSink, GstElementContainer* elements, GstElement** ppVideobin) { + if (NULL == pOptions) + return ERROR_FUNCTION_PARAM_NULL; + *ppVideobin = gst_bin_new(NULL); if (NULL == *ppVideobin) return ERROR_GSTREAMER_BIN_CREATE; @@ -1041,8 +1045,26 @@ uint32_t CGstPipelineFactory::CreateVideoBin(const char* strDecoderName, GstElem add(VIDEO_DECODER, videodec). add(VIDEO_SINK, pVideoSink); - // Switch off limiting of the videoqueue for bytes and buffers. - g_object_set(videoqueue, "max-size-bytes", (guint)0, "max-size-buffers", (guint)10, "max-size-time", (guint64)0, NULL); + // Configure audio queue + guint max_bytes = QUEUE_MAX_BYTES; + guint max_buffers = QUEUE_MAX_BUFFERS; + guint64 max_time = QUEUE_MAX_TIME; + +#if TARGET_OS_WIN32 + // Special case for HLS on Windows due to source reader reload for + // each segment. We need to queue more data to avoid glitching when + // reload. + if (pOptions->GetHLSModeEnabled() && + pOptions->GetStreamMimeType() == HLS_VALUE_MIMETYPE_FMP4) + { + max_bytes = QUEUE_MAX_BYTES_HLSL; + max_buffers = QUEUE_MAX_BUFFERS_HLSL; + max_time = QUEUE_MAX_TIME_HLSL; + } +#endif + + g_object_set(videoqueue, "max-size-bytes", max_bytes, "max-size-buffers", + max_buffers, "max-size-time", max_time, NULL); g_object_set(pVideoSink, "qos", TRUE, NULL); return ERROR_NONE; diff --git a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h index c1ea6696cd8..cfd68f68fc5 100644 --- a/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h +++ b/modules/javafx.media/src/main/native/jfxmedia/platform/gstreamer/GstPipelineFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,6 +54,8 @@ class CGstPipelineFactory : public CPipelineFactory GstCaps* FrameTypeToCaps(CVideoFrame::FrameType format); void NegotiatePixelFormat(GstElement* pVideoSink, CPipelineOptions* pOptions); + uint32_t ConfigurePipelineOptions(CPipelineOptions *pOptions); + uint32_t CreatePipeline(CPipelineOptions* pOptions, GstElementContainer* pElements, CPipeline** ppPipeline); uint32_t CreateMP4Pipeline(GstElement* videosink, CPipelineOptions* pOptions, GstElementContainer* pElements, CPipeline** ppPipeline); @@ -71,16 +73,16 @@ class CGstPipelineFactory : public CPipelineFactory uint32_t CreateAVPipeline(bool bConvertFormat, GstElement* pVideoSink, CPipelineOptions* pOptions, GstElementContainer* pElements, CPipeline** ppPipeline); - uint32_t CreateAudioBin(const char* strParserName, const char* strDecoderName, bool bConvertFormat, - GstElementContainer* elements, int* pFlags, GstElement** pAudiobin); - uint32_t CreateVideoBin(const char* strDecoderName, GstElement* pVideoSink, - GstElementContainer* elements, GstElement** ppVideobin); + uint32_t CreateAudioBin(CPipelineOptions *pOptions, const char* strParserName, + const char* strDecoderName, bool bConvertFormat, + GstElementContainer* elements, int* pFlags, + GstElement** pAudiobin); + uint32_t CreateVideoBin(CPipelineOptions *pOptions, const char* strDecoderName, + GstElement* pVideoSink, GstElementContainer* elements, + GstElement** ppVideobin); GstElement* CreateElement(const char* strFactoryName); - // progressbuffer on-pad-added - static void OnBufferPadAdded(GstElement* element, GstPad* pad, GstElement* peer); - // javasource signals static gint SourceReadNextBlock(GstElement *src, gpointer data); static gint SourceReadBlock(GstElement *src, guint64 position, guint size, gpointer data); diff --git a/modules/javafx.media/src/main/native/vs_project/glib/glib.vcxproj b/modules/javafx.media/src/main/native/vs_project/glib/glib.vcxproj index f73138e4e03..cb9b1449373 100644 --- a/modules/javafx.media/src/main/native/vs_project/glib/glib.vcxproj +++ b/modules/javafx.media/src/main/native/vs_project/glib/glib.vcxproj @@ -397,21 +397,21 @@ G_DISABLE_DEPRECATED;_MBCS {520F67C2-7C7E-48F6-B559-457F84CA2DF9} Win32Proj glib - 10.0.16299.0 + 10.0 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj b/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj index db6ca04e25d..7596020710d 100644 --- a/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj +++ b/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj @@ -197,25 +197,6 @@ WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - - - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - - - - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - - - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - - - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - - - - - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) - WIN32;_WINDOWS;LIBGSTELEMENTS_EXPORTS;HAVE_CONFIG_H;_WIN32_DCOM;COBJMACROS;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;_USE_MATH_DEFINES;_USRDLL;_WINDLL;_MBCS;%(PreprocessorDefinitions) @@ -517,19 +498,6 @@ - - - - - - - - - - - - - @@ -538,21 +506,21 @@ {FC533698-F232-42DC-980E-2171DFF660E3} Win32Proj gstreamer - 10.0.16299.0 + 10.0 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj.filters b/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj.filters index a79263d4e6a..8412a48dea3 100644 --- a/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj.filters +++ b/modules/javafx.media/src/main/native/vs_project/gstreamer/gstreamer.vcxproj.filters @@ -88,9 +88,6 @@ {e01f3cfc-073f-43d4-9039-74c194e074a3} - - {3d024713-6312-4014-a287-bd8ef46b4b53} - {50937d4e-1cf1-4d9b-87a5-97384bdda02f} @@ -477,21 +474,6 @@ gst-plugins-good\gst\equalizer - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - gst-plugins-good\gst\spectrum @@ -672,18 +654,6 @@ gst-plugins-base\gst-libs\gst\video - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - @@ -824,45 +794,6 @@ gst-plugins-base\gst-libs\gst\video - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - - - gst-plugins-good\gst\isomp4 - gst-plugins-good\gst\audioparsers diff --git a/modules/javafx.media/src/main/native/vs_project/jfxmedia/jfxmedia.vcxproj b/modules/javafx.media/src/main/native/vs_project/jfxmedia/jfxmedia.vcxproj index bbc43305eb8..901f7293d37 100644 --- a/modules/javafx.media/src/main/native/vs_project/jfxmedia/jfxmedia.vcxproj +++ b/modules/javafx.media/src/main/native/vs_project/jfxmedia/jfxmedia.vcxproj @@ -100,21 +100,21 @@ {41E7B612-A478-4FD5-9B2D-1B591FC6EE8A} Win32Proj jfxmedia - 10.0.16299.0 + 10.0 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj b/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj index 1a9d8ee82d1..50030f9cdf3 100644 --- a/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj +++ b/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj @@ -32,6 +32,8 @@ WIN32;_WINDOWS;_USRDLL;ENABLE_PULL_MODE=1;ENABLE_SOURCE_SEEKING=1;GSTREAMER_LITE;GST_REMOVE_DEPRECATED;GST_REMOVE_DISABLED;GST_DISABLE_GST_DEBUG;GST_DISABLE_LOADSAVE;G_DISABLE_DEPRECATED;G_DISABLE_ASSERT;_WINDLL;_MBCS;INITGUID;%(PreprocessorDefinitions) + + @@ -45,6 +47,8 @@ + + @@ -60,21 +64,21 @@ {87CF0F6E-87BD-4CB5-A6A1-841F81F17842} Win32Proj plugins - 10.0.16299.0 + 10.0 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj.filters b/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj.filters index 2dd011850c8..b2d8c8b6a84 100644 --- a/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj.filters +++ b/modules/javafx.media/src/main/native/vs_project/plugins/plugins.vcxproj.filters @@ -1,34 +1,18 @@  - - {bb6f275d-3492-4001-a1d8-bd300341db59} - - - {a2baa68b-83bd-4126-81a2-068ff08d9c98} - - - {56564e42-f8d2-4a48-8b90-8d65e262180a} - - - {a396eb3d-d2e5-418e-999c-38fcdc5473a5} - - - - - javasource - - - javasource + + + mfwrapper - - progressbuffer + + mfwrapper - - progressbuffer + + mfwrapper - - progressbuffer + + mfwrapper dshowwrapper @@ -42,15 +26,33 @@ dshowwrapper - - - mfwrapper + + progressbuffer - - mfwrapper + + progressbuffer + + + progressbuffer\win32 + + + javasource + + + javasource + + + mfwrapper + + + mfwrapper + + + mfwrapper + mfwrapper @@ -66,12 +68,6 @@ dshowwrapper - - - mfwrapper - - - progressbuffer @@ -82,4 +78,21 @@ progressbuffer + + + {cd030290-f4d1-48ea-baed-22e512ed1d47} + + + {4f39dd0f-2632-440d-b238-083d0ec4c9ed} + + + {bee46b16-0101-41ac-9e87-35f0ba732976} + + + {9b6f392c-0e37-4697-a9be-28aa18ae5a8b} + + + {6cb7f151-1911-4df9-8bfb-5cc1e57ec447} + + \ No newline at end of file