diff --git a/girs/GstMse-1.0.gir b/girs/GstMse-1.0.gir new file mode 100644 index 0000000000..4818df5a86 --- /dev/null +++ b/girs/GstMse-1.0.gir @@ -0,0 +1,1135 @@ + + + + + + + + #GstMediaSource is the entry point into the W3C Media Source API. It offers +functionality similar to #GstAppSrc for client-side web or JavaScript +applications decoupling the source of media from its processing and playback. + +To interact with a Media Source, connect it to a #GstMseSrc that is in some +#GstPipeline using gst_media_source_attach(). Then create at least one +#GstSourceBuffer using gst_media_source_add_source_buffer(). Finally, feed +some media data to the Source Buffer(s) using +gst_source_buffer_append_buffer() and play the pipeline. + + + Creates a new #GstMediaSource instance. The instance is in the +%GST_MEDIA_SOURCE_READY_STATE_CLOSED state and is not associated with any +media player. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-constructor) + + + a new #GstMediaSource instance + + + + + Determines whether the current Media Source configuration can process media +of the supplied @type. + + + `TRUE` when supported, `FALSE` otherwise + + + + + A MIME type value + + + + + + Add a #GstSourceBuffer to this #GstMediaSource of the specified media type. +The Media Source must be in the #GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-addsourcebuffer) + + + a new #GstSourceBuffer instance on success, otherwise `NULL` + + + + + #GstMediaSource instance + + + + A MIME type describing the format of the incoming media + + + + + + Associates @self with @element. +Normally, the Element will be part of a #GstPipeline that plays back the data +submitted to the Media Source's Source Buffers. + +#GstMseSrc is a special source element that is designed to consume media from +a #GstMediaSource. + +[Specification](https://www.w3.org/TR/media-source-2/#dfn-attaching-to-a-media-element) + + + + + + + #GstMediaSource instance + + + + #GstMseSrc source Element + + + + + + Clear the live seekable range for @self. This will inform the component +playing this Media Source that there is no seekable time range. + +If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, it will fail +and set an error. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-clearliveseekablerange) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstMediaSource instance + + + + + + Detaches @self from any #GstMseSrc element that it may be associated with. + + + + + + + #GstMediaSource instance + + + + + + Mark @self as reaching the end of stream, disallowing new data inputs. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-endofstream) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstMediaSource instance + + + + The error type, if any + + + + + + Gets a #GstSourceBufferList containing all the Source Buffers currently +associated with this Media Source that are considered "active." +For a Source Buffer to be considered active, either its video track is +selected, its audio track is enabled, or its text track is visible or hidden. +This object will reflect any future changes to the parent Media Source as +well. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers) + + + a new #GstSourceBufferList instance + + + + + #GstMediaSource instance + + + + + + Gets the current duration of @self. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + + + the current duration as a #GstClockTime + + + + + #GstMediaSource instance + + + + + + Get the live seekable range of @self. Will fill in the supplied @range with +the current live seekable range. + + + + + + + #GstMediaSource instance + + + + time range + + + + + + Gets the current playback position of the Media Source. + + + the current playback position as a #GstClockTime + + + + + #GstMediaSource instance + + + + + + Gets the current Ready State of the Media Source. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate) + + + the current #GstMediaSourceReadyState value + + + + + #GstMediaSource instance + + + + + + Gets a #GstSourceBufferList containing all the Source Buffers currently +associated with this Media Source. This object will reflect any future +changes to the parent Media Source as well. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers) + + + a #GstSourceBufferList instance + + + + + #GstMediaSource instance + + + + + + Remove @buffer from @self. + +@buffer must have been created as a child of @self and @self must be in the +#GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-removesourcebuffer) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstMediaSource instance + + + + #GstSourceBuffer instance + + + + + + Sets the duration of @self. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstMediaSource instance + + + + The new duration to apply to @self. + + + + + + Set the live seekable range for @self. This range informs the component +playing this Media Source what it can allow the user to seek through. + +If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, or the supplied +@start time is later than @end it will fail and set an error. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-setliveseekablerange) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstMediaSource instance + + + + The earliest point in the stream considered seekable + + + + The latest point in the stream considered seekable + + + + + + A #GstSourceBufferList of every #GstSourceBuffer in this Media Source that +is considered active + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers) + + + + The Duration of the Media Source as a #GstClockTime + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + + + + The position of the player consuming from the Media Source + + + + The Ready State of the Media Source + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate) + + + + A #GstSourceBufferList of every #GstSourceBuffer in this Media Source + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers) + + + + + + + + + Emitted when @self has ended, normally through +gst_media_source_end_of_stream(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceended) + + + + + + Emitted when @self has been opened. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceopen) + + + + + + + + + + + + + Reasons for ending a #GstMediaSource using gst_media_source_end_of_stream(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-endofstreamerror) + + End the stream successfully + + + End the stream due to a networking error + + + End the stream due to a decoding error + + + + Any error that can occur within #GstMediaSource or #GstSourceBuffer APIs. +These values correspond directly to those in the Web IDL specification. + +[Specification](https://webidl.spec.whatwg.org/#idl-DOMException-error-names) + + + + + + + + + + + + Any error type that can be reported by the Media Source API. + + + + + + + A structure describing a simplified version of the TimeRanges concept in the +HTML specification, only representing a single @start and @end time. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#timeranges) + + + The start of this range. + + + + The end of this range. + + + + + Describes the possible states of the Media Source. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-readystate) + + The #GstMediaSource is not connected to +any playback element. + + + The #GstMediaSource is connected to a +playback element and ready to append data to its #GstSourceBuffer (s). + + + gst_media_source_end_of_stream() has +been called on the current #GstMediaSource + + + + #GstMseSrc is a source Element that interacts with a #GstMediaSource to +consume #GstSample<!-- -->s processed by the Media Source and supplies them +to the containing #GstPipeline. In the perspective of the Media Source API, +this element fulfills the basis of the Media Element's role relating to +working with a Media Source. The remaining responsibilities are meant to be +fulfilled by the application and #GstPlay can be used to satisfy many of +them. + +Once added to a Pipeline, this element should be attached to a Media Source +using gst_media_source_attach(). + + + + Gets the duration of @self. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration) + + + The duration of this stream as a #GstClockTime + + + + + #GstMseSrc instance + + + + + + + + the number of audio tracks available from this source + + + + + #GstMseSrc instance + + + + + + + + the number of text tracks available from this source + + + + + #GstMseSrc instance + + + + + + + + the number of video tracks available from this source + + + + + #GstMseSrc instance + + + + + + Gets the current playback position of @self. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position) + + + The playback position of this Element as a #GstClockTime + + + + + #GstMseSrc instance + + + + + + The Ready State of @self, describing to what level it can supply content for +the current #GstMseSrc:position. This is a separate concept from +#GstMediaSource:ready-state: and corresponds to the HTML Media Element's +Ready State. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + + + the current #GstMseSrcReadyState + + + + + #GstMseSrc instance + + + + + + The duration of the stream as a #GstClockTime + +[Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration) + + + + The number of audio tracks in the Media Source + + + + The number of text tracks in the Media Source + + + + The number of video tracks in the Media Source + + + + The playback position as a #GstClockTime + +[Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position) + + + + The Ready State of this element, describing to what level it can supply +content for the current #GstMseSrc:position. This is a separate concept +from #GstMediaSource:ready-state: and corresponds to the HTML Media +Element's Ready State. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + + + + + + + + + + + + + + + + + + + + Describes how much information a #GstMseSrc has about the media it is playing +back at the current playback #GstMseSrc:position. This type corresponds +directly to the ready state of a HTML Media Element and is a separate concept +from #GstMediaSourceReadyState. + +[Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + + No information is available about the +stream + + + The duration is available and video +dimensions are available if the stream contains video + + + The current playback position can +be presented but future information is not available + + + There is data for the current +position and some amount in the future and any text tracks are ready. + + + Either there is enough data to +play the stream through at the current playback and input rate or the input +buffer is full. + + + + The Source Buffer is the primary means of data flow between an application +and the Media Source API. It represents a single timeline of media, +containing some combination of audio, video, and text tracks. +An application is responsible for feeding raw data into the Source Buffer +using gst_source_buffer_append_buffer() and the Source Buffer will +asynchronously process the data into tracks of time-coded multimedia samples. + +The application as well as the associated playback component can then select +to play media from any subset of tracks across all Source Buffers of a Media +Source. + +A few control points are also provided to customize the behavior. + + - #GstSourceBuffer:append-mode controls how timestamps of processed samples are + interpreted. They are either inserted in the timeline directly where the + decoded media states they should, or inserted directly after the previously + encountered sample. + + - #GstSourceBuffer:append-window-start / #GstSourceBuffer:append-window-end + control the planned time window where media from appended data can be added + to the current timeline. Any samples outside that range may be ignored. + + - #GstSourceBuffer:timestamp-offset is added to the start time of any sample + processed. + + + Attempts to end any processing of the currently pending data and reset the +media parser. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-abort) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + + + Schedules the bytes inside @buf to be processed by @self. When it is possible +to accept the supplied data, it will be processed asynchronously and fill in +the track buffers for playback purposes. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendbuffer) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + The media data to append + + + + + + Attempts to change the content type of @self to @type. Any new data appended +to the Source Buffer must be of the supplied @type afterward. + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + the desired content type + + + + + + [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + + + The current #GstSourceBufferAppendMode + + + + + #GstSourceBuffer instance + + + + + + Returns the current append window end time. Any segment processed that starts +after this value will be ignored. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + + + The current Append Window end time as a #GstClockTime + + + + + #GstSourceBuffer instance + + + + + + Returns the current append window start time. Any segment processed that ends +earlier than this value will be ignored. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + + + The current Append Window start time as a #GstClockTime + + + + + #GstSourceBuffer instance + + + + + + Returns a sequence of #GstMediaSourceRange values representing which segments +of @self are buffered in memory. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered) + + + a #GArray of #GstMediaSourceRange values. + + + + + + + #GstSourceBuffer instance + + + + + + Returns the current content type of @self. + + + a string representing the content type + + + + + #GstSourceBuffer instance + + + + + + [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + + + The current timestamp offset as a #GstClockTime + + + + + #GstSourceBuffer instance + + + + + + [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating) + + + Whether @self is currently adding or removing media content. + + + + + #GstSourceBuffer instance + + + + + + Attempts to remove any parsed data between @start and @end from @self. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-remove) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + The beginning timestamp of data to remove + + + + The end timestamp of data to remove + + + + + + Changes the Append Mode of @self. This influences what timestamps will be +assigned to media processed by this Source Buffer. In Segment mode, the +timestamps in each segment determine the position of each sample after it +is processed. In Sequence mode, the timestamp of each processed sample is +generated based on the end of the most recently processed segment. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + #GstSourceBufferAppendMode the desired Append Mode + + + + + + Modifies the current append window end of @self. If successful, samples +processed after setting this value that start after this point will be +ignored. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + the append window end + + + + + + Modifies the current append window start of @self. If successful, samples +processed after setting this value that end before this point will be +ignored. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + the append window end + + + + + + Attempt to set the timestamp offset of @self. Any media processed after this +value is set will have this value added to its start time. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + + + `TRUE` on success, `FALSE` otherwise + + + + + #GstSourceBuffer instance + + + + The new timestamp offset + + + + + + Affects how timestamps of processed media segments are interpreted. +In %GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, the start timestamp of a +processed media segment is used directly along with +#GstSourceBuffer:timestamp-offset . +In %GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, the timestamp of a +processed media segment is ignored and replaced with the end time of the +most recently appended segment. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + + + + Any segments processed which have a start time greater than this value will +be ignored by this Source Buffer. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + + + + Any segments processed which end before this value will be ignored by this +Source Buffer. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + + + + The set of Time Intervals that have been loaded into the current Source +Buffer + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered) + + + + + + The MIME content-type of the data stream + + + + The next media segment appended to the current Source Buffer will have its +start timestamp increased by this amount. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + + + + Whether the current source buffer is still asynchronously processing +previously issued commands. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating) + + + + Emitted when @self was aborted after a call to gst_source_buffer_abort(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onabort) + + + + + + Emitted when @self has encountered an error after a call to +gst_source_buffer_append_buffer(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onerror) + + + + + + Emitted when @self has successfully processed data after a call to +gst_source_buffer_append_buffer(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdate) + + + + + + Emitted when @self is no longer in the updating state after a call to +gst_source_buffer_append_buffer(). This can happen after a successful or +unsuccessful append. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdateend) + + + + + + Emitted when @self has begun to process data after a call to +gst_source_buffer_append_buffer(). + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdatestart) + + + + + + + [Specification](https://www.w3.org/TR/media-source-2/#dom-appendmode) + + + + + + + + + + + + + + + + + + + + + + The Source Buffer List is a list of #GstSourceBuffer<!-- -->s that can be +indexed numerically and monitored for changes. The list itself cannot be +modified through this interface, though the Source Buffers it holds can be +modified after retrieval. + +It is used by #GstMediaSource to provide direct access to its child +#GstSourceBuffer<!-- -->s through #GstMediaSource:source-buffers as well as +informing clients which of the Source Buffers are active through +#GstMediaSource:active-source-buffers. + + + [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length) + + + The number of #GstSourceBuffer objects in the list + + + + + #GstSourceBufferList instance + + + + + + Retrieves the #GstSourceBuffer at @index from @self. If @index is greater than +the highest index in the list, it will return `NULL`. + +[Specification](https://www.w3.org/TR/media-source-2/#dfn-sourcebufferlist-getter) + + + The requested #GstSourceBuffer or `NULL` + + + + + #GstSourceBufferList instance + + + + index of requested Source Buffer + + + + + + The number of #GstSourceBuffer<!-- -->s contained by this structure + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length) + + + + Emitted when a #GstSourceBuffer has been added to this list. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onaddsourcebuffer) + + + + + + Emitted when a #GstSourceBuffer has been removed from this list. + +[Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onremovesourcebuffer) + + + + + + + + + + + + + Any error type that can be reported by the Media Source API. + + + + + + diff --git a/subprojects/gst-plugins-bad/docs/libs/mse/index.md b/subprojects/gst-plugins-bad/docs/libs/mse/index.md new file mode 100644 index 0000000000..95f019eca5 --- /dev/null +++ b/subprojects/gst-plugins-bad/docs/libs/mse/index.md @@ -0,0 +1,3 @@ +# W3C Media Source Extensions Library + +> NOTE: This library API is considered *unstable* diff --git a/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt b/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt new file mode 100644 index 0000000000..4f91fcd8a3 --- /dev/null +++ b/subprojects/gst-plugins-bad/docs/libs/mse/sitemap.txt @@ -0,0 +1 @@ +gi-index diff --git a/subprojects/gst-plugins-bad/docs/meson.build b/subprojects/gst-plugins-bad/docs/meson.build index 582de7d0fd..3b884e6af2 100644 --- a/subprojects/gst-plugins-bad/docs/meson.build +++ b/subprojects/gst-plugins-bad/docs/meson.build @@ -134,6 +134,7 @@ if build_gir {'name': 'codecs', 'gir': codecs_gir, 'lib': gstcodecs_dep}, {'name': 'cuda', 'gir': gst_cuda_gir, 'lib': gstcuda_dep, 'c_source_patterns': ['*.h', '*.cpp']}, {'name': 'dxva', 'gir': dxva_gir, 'lib': gstdxva_dep, 'c_source_patterns': ['*.h', '*.cpp']}, + {'name': 'mse', 'gir': mse_gir, 'lib': gstmse_dep, 'suffix': 'lib'}, ] if gstopencv_dep.found() diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index e5a8fe8c41..80e64a24ba 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -220390,6 +220390,126 @@ "tracers": {}, "url": "Unknown package origin" }, + "mse": { + "description": "W3C Media Source Extensions Support", + "elements": { + "msesrc": { + "author": "Collabora", + "description": "Implements a GStreamer Source for the gstreamer-mse API", + "hierarchy": [ + "GstMseSrc", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstURIHandler" + ], + "klass": "Generic/Source", + "pad-templates": { + "src_%%s": { + "caps": "ANY", + "direction": "src", + "presence": "sometimes", + "type": "GstMseSrcPad" + } + }, + "properties": { + "duration": { + "blurb": "The duration of the stream as a GstClockTime", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": true + }, + "n-audio": { + "blurb": "The number of audio tracks in the Media Source", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "2147483647", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "n-text": { + "blurb": "The number of text tracks in the Media Source", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "2147483647", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "n-video": { + "blurb": "The number of video tracks in the Media Source", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "2147483647", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "position": { + "blurb": "The playback position as a GstClockTime", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "18446744073709551615", + "max": "18446744073709551615", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint64", + "writable": false + }, + "ready-state": { + "blurb": "The Ready State of this Element", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "nothing (0)", + "mutable": "null", + "readable": true, + "type": "GstMseSrcReadyState", + "writable": false + } + }, + "rank": "none" + } + }, + "filename": "gstmse", + "license": "LGPL", + "other-types": {}, + "package": "GStreamer Bad Plug-ins", + "source": "gst-plugins-bad", + "tracers": {}, + "url": "Unknown package origin" + }, "musepack": { "description": "Musepack decoder", "elements": { diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/meson.build b/subprojects/gst-plugins-bad/gst-libs/gst/meson.build index fc4ba6942b..7772d8c2b1 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/meson.build +++ b/subprojects/gst-plugins-bad/gst-libs/gst/meson.build @@ -13,6 +13,7 @@ subdir('insertbin') subdir('interfaces') subdir('isoff') subdir('mpegts') +subdir('mse') subdir('opencv') subdir('play') subdir('player') diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h new file mode 100644 index 0000000000..c44a3440fb --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline-private.h @@ -0,0 +1,102 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2016, 2017 Metrological Group B.V. + * Copyright (C) 2016, 2017 Igalia S.L + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_APPEND_PIPELINE (gst_append_pipeline_get_type()) + +GST_MSE_PRIVATE +G_DECLARE_FINAL_TYPE (GstAppendPipeline, gst_append_pipeline, GST, + APPEND_PIPELINE, GstObject); + +typedef struct +{ + void (*received_init_segment) (GstAppendPipeline * self, + gpointer user_data); + void (*duration_changed) (GstAppendPipeline * self, + gpointer user_data); + void (*new_sample) (GstAppendPipeline * self, + GstMediaSourceTrack * track, + GstSample * sample, + gpointer user_data); + void (*eos) (GstAppendPipeline * self, + GstMediaSourceTrack * track, + gpointer user_data); + void (*error) (GstAppendPipeline * self, + gpointer user_data); +} GstAppendPipelineCallbacks; + +GST_MSE_PRIVATE +GstAppendPipeline * gst_append_pipeline_new ( + GstAppendPipelineCallbacks * callbacks, gpointer user_data, + GError ** error); + +GST_MSE_PRIVATE +GstFlowReturn gst_append_pipeline_append (GstAppendPipeline * self, + GstBuffer * buffer); + +GST_MSE_PRIVATE +GstFlowReturn gst_append_pipeline_eos (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gboolean gst_append_pipeline_stop (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gboolean gst_append_pipeline_reset (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gsize gst_append_pipeline_n_tracks (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gboolean gst_append_pipeline_has_init_segment (GstAppendPipeline * self); + +GST_MSE_PRIVATE +GstClockTime gst_append_pipeline_get_duration (GstAppendPipeline * self); + +GST_MSE_PRIVATE +GPtrArray *gst_append_pipeline_get_audio_tracks (GstAppendPipeline * self); + +GST_MSE_PRIVATE +GPtrArray *gst_append_pipeline_get_text_tracks (GstAppendPipeline * self); + +GST_MSE_PRIVATE +GPtrArray *gst_append_pipeline_get_video_tracks (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gboolean gst_append_pipeline_get_eos (GstAppendPipeline * self); + +GST_MSE_PRIVATE +void gst_append_pipeline_fail (GstAppendPipeline * self); + +GST_MSE_PRIVATE +gboolean gst_append_pipeline_get_failed (GstAppendPipeline * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c new file mode 100644 index 0000000000..db9cb25181 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstappendpipeline.c @@ -0,0 +1,978 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2016, 2017 Metrological Group B.V. + * Copyright (C) 2016, 2017 Igalia S.L + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstappendpipeline-private.h" + +#include "mse.h" + +#include "gstmselogging-private.h" +#include "gstmsemediatype-private.h" +#include "gstmediasourcetrack-private.h" + +#include +#include +#include + +typedef enum +{ + PARSE_ERROR, + IGNORED, + ADDED, +} AddTrackResult; + +typedef struct +{ + GstAppendPipeline *pipeline; + GstTask *task; + GRecMutex mutex; + GstBus *bus; +} BackgroundTask; + +typedef struct +{ + GstAppendPipelineCallbacks callbacks; + gpointer user_data; +} Callbacks; + +typedef struct +{ + GstAppendPipeline *parent; + GstPad *src_pad; + GstAppSink *sink; + GstMediaSourceTrack *mse_track; + GstStream *stream; + GstClockTime previous_pts; +} Track; + +typedef struct +{ + GstClockTime duration; + GPtrArray *video_tracks; + GPtrArray *audio_tracks; + GPtrArray *text_tracks; +} InitSegment; + +struct _GstAppendPipeline +{ + GstObject parent_instance; + + GstPipeline *pipeline; + GstAppSrc *src; + GstElement *parsebin; + GstBus *bus; + + GstStreamCollection *streams; + GArray *tracks; + + gboolean received_init_segment; + gboolean have_outstanding_samples; + InitSegment init_segment; + + gboolean encountered_error; + + BackgroundTask *task; + + Callbacks callbacks; +}; + +G_DEFINE_TYPE (GstAppendPipeline, gst_append_pipeline, GST_TYPE_OBJECT); + +#define END_OF_APPEND "end-of-append" +#define ABORT "abort" +#define SHUTDOWN "shutdown" + +static void process_init_segment (GstAppendPipeline *); + +static gboolean +send_abort (GstAppendPipeline * self) +{ + return gst_bus_post (self->bus, gst_message_new_application (NULL, + gst_structure_new_empty (ABORT))); +} + +static gboolean +send_shutdown (GstAppendPipeline * self) +{ + return gst_bus_post (self->bus, gst_message_new_application (NULL, + gst_structure_new_empty (SHUTDOWN))); +} + +static GstEvent * +new_end_of_append_event (void) +{ + GstStructure *structure = gst_structure_new_empty (END_OF_APPEND); + return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure); +} + +static gboolean +is_end_of_append_event (GstEvent * event) +{ + return GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM + && gst_event_has_name (event, END_OF_APPEND); +} + +static inline guint +n_tracks (GstAppendPipeline * self) +{ + return self->tracks->len; +} + +static inline Track * +index_track (GstAppendPipeline * self, guint i) +{ + return &g_array_index (self->tracks, Track, i); +} + +static inline void +call_parse_error (GstAppendPipeline * self) +{ + GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks; + gpointer user_data = self->callbacks.user_data; + + if (callbacks->error) { + callbacks->error (self, user_data); + GST_TRACE_OBJECT (self, "done"); + } else { + GST_TRACE_OBJECT (self, "dropping"); + } +} + +static inline void +call_received_init_segment (GstAppendPipeline * self) +{ + GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks; + gpointer user_data = self->callbacks.user_data; + + if (callbacks->received_init_segment) { + callbacks->received_init_segment (self, user_data); + GST_TRACE_OBJECT (self, "done"); + } else { + GST_TRACE_OBJECT (self, "dropping"); + } +} + +static inline void +call_new_sample (GstAppendPipeline * self, GstMediaSourceTrack * track, + GstSample * sample) +{ + GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks; + gpointer user_data = self->callbacks.user_data; + + if (callbacks->new_sample) { + callbacks->new_sample (self, track, sample, user_data); + GST_TRACE_OBJECT (self, "done"); + } else { + GST_TRACE_OBJECT (self, "dropping"); + } +} + +static inline void +call_duration_changed (GstAppendPipeline * self) +{ + GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks; + gpointer user_data = self->callbacks.user_data; + + if (callbacks->duration_changed) { + callbacks->duration_changed (self, user_data); + GST_TRACE_OBJECT (self, "done"); + } else { + GST_TRACE_OBJECT (self, "dropping"); + } +} + +static inline void +call_eos (GstAppendPipeline * self, GstMediaSourceTrack * track) +{ + GstAppendPipelineCallbacks *callbacks = &self->callbacks.callbacks; + gpointer user_data = self->callbacks.user_data; + + if (callbacks->eos) { + callbacks->eos (self, track, user_data); + GST_TRACE_OBJECT (self, "done"); + } else { + GST_TRACE_OBJECT (self, "dropping"); + } +} + +static inline GstSample * +patch_missing_duration (GstAppendPipeline * self, GstSample * sample) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { + GST_BUFFER_DURATION (buffer) = GST_SECOND / 60; + GST_TRACE_OBJECT (self, "sample is missing duration, patched to %" + GST_TIMEP_FORMAT, &buffer->duration); + } + return sample; +} + +static inline GstSample * +patch_missing_pts (GstAppendPipeline * self, GstSample * sample, GstClockTime + fallback) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + if (!GST_BUFFER_PTS_IS_VALID (buffer) && GST_CLOCK_TIME_IS_VALID (fallback)) { + GST_TRACE_OBJECT (self, "sample is missing pts, patching with %" + GST_TIMEP_FORMAT, &fallback); + GST_BUFFER_PTS (buffer) = fallback; + } + return sample; +} + +static inline GstSample * +patch_missing_dts (GstAppendPipeline * self, GstSample * sample) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + if (!GST_BUFFER_DTS_IS_VALID (buffer) && GST_BUFFER_PTS_IS_VALID (buffer)) { + GST_TRACE_OBJECT (self, "sample is missing dts, patching with pts %" + GST_TIMEP_FORMAT, &buffer->pts); + GST_BUFFER_DTS (buffer) = GST_BUFFER_PTS (buffer); + } + return sample; +} + +static gboolean +consume_sample_from_track (GstAppendPipeline * self, Track * track) +{ + GstSample *sample = gst_app_sink_try_pull_sample (track->sink, 0); + if (sample == NULL) { + return FALSE; + } + GstBuffer *buffer = gst_sample_get_buffer (sample); + if (!GST_IS_BUFFER (buffer)) { + GST_WARNING_OBJECT (self, "got null buffer in sample"); + goto done; + } + sample = patch_missing_pts (self, sample, track->previous_pts); + sample = patch_missing_duration (self, sample); + sample = patch_missing_dts (self, sample); + track->previous_pts = GST_BUFFER_PTS (buffer); + call_new_sample (self, track->mse_track, sample); + +done: + gst_clear_sample (&sample); + return TRUE; +} + +static void +consume_all_samples (GstAppendPipeline * self) +{ + if (!self->received_init_segment) { + GST_DEBUG_OBJECT (self, "not all tracks are available, delaying"); + self->have_outstanding_samples = TRUE; + return; + } + guint track_count = n_tracks (self); + while (TRUE) { + gboolean sample_consumed = FALSE; + for (guint i = 0; i < track_count; i++) { + Track *track = index_track (self, i); + sample_consumed |= consume_sample_from_track (self, track); + } + if (!sample_consumed) { + break; + } + } + call_duration_changed (self); + self->have_outstanding_samples = FALSE; +} + +static void +handle_shutdown (BackgroundTask * task) +{ + gst_task_stop (task->task); + GstAppendPipeline *self = task->pipeline; + guint track_count = n_tracks (self); + for (guint i = 0; i < track_count; i++) { + Track *track = index_track (self, i); + call_eos (self, track->mse_track); + } + call_eos (self, NULL); +} + +static void +handle_abort (BackgroundTask * task) +{ + gst_task_stop (task->task); +} + +static void +task_function (gpointer user_data) +{ + BackgroundTask *task = (BackgroundTask *) user_data; + GstAppendPipeline *self = task->pipeline; + GstMessage *message = gst_bus_timed_pop (task->bus, GST_CLOCK_TIME_NONE); + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_APPLICATION:{ + if (gst_message_has_name (message, END_OF_APPEND)) { + GST_TRACE_OBJECT (self, "end of append"); + consume_all_samples (self); + goto done; + } + if (gst_message_has_name (message, SHUTDOWN)) { + GST_DEBUG_OBJECT (self, "shutdown"); + handle_shutdown (task); + goto done; + } + if (gst_message_has_name (message, ABORT)) { + GST_DEBUG_OBJECT (self, "abort"); + handle_abort (task); + goto done; + } + g_error ("received unsupported application message"); + } + case GST_MESSAGE_STREAM_COLLECTION:{ + GST_DEBUG_OBJECT (self, "stream collection"); + GstStreamCollection *streams; + gst_message_parse_stream_collection (message, &streams); + gst_clear_object (&self->streams); + self->streams = streams; + process_init_segment (self); + goto done; + } + case GST_MESSAGE_EOS: + GST_DEBUG_OBJECT (self, "end of stream"); + if (self->have_outstanding_samples) { + GST_DEBUG_OBJECT (self, "consuming remaining samples before EOS"); + consume_all_samples (self); + } + handle_shutdown (task); + goto done; + case GST_MESSAGE_ERROR: + GST_DEBUG_OBJECT (self, "error: %" GST_PTR_FORMAT, message); + self->encountered_error = TRUE; + call_parse_error (self); + handle_shutdown (task); + goto done; + default: + GST_TRACE_OBJECT (self, "ignoring message %" GST_PTR_FORMAT, message); + goto done; + } +done: + gst_message_unref (message); +} + +static inline GstAppSink * +new_appsink (GstAppendPipeline * self, GstStreamType type) +{ + const gchar *type_name = gst_stream_type_get_name (type); + gchar *name = g_strdup_printf ("%s-%u", type_name, n_tracks (self)); + GstAppSink *appsink = + GST_APP_SINK (gst_element_factory_make ("appsink", name)); + gst_base_sink_set_sync (GST_BASE_SINK (appsink), FALSE); + gst_base_sink_set_async_enabled (GST_BASE_SINK (appsink), FALSE); + gst_base_sink_set_drop_out_of_segment (GST_BASE_SINK (appsink), FALSE); + gst_base_sink_set_last_sample_enabled (GST_BASE_SINK (appsink), FALSE); + g_free (name); + return appsink; +} + +static GstPadProbeReturn +black_hole_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + g_return_val_if_fail (GST_PAD_PROBE_INFO_TYPE (info) & + GST_PAD_PROBE_TYPE_BUFFER, GST_PAD_PROBE_DROP); + return GST_PAD_PROBE_DROP; +} + +static GstPadProbeReturn +event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstAppendPipeline *self = GST_APPEND_PIPELINE (user_data); + GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info); + + if (is_end_of_append_event (event)) { + GST_TRACE_OBJECT (self, "end of append event"); + if (gst_bus_post (self->bus, gst_message_new_application (NULL, + gst_structure_new_empty (END_OF_APPEND)))) { + return GST_PAD_PROBE_DROP; + } else { + GST_ERROR_OBJECT (self, "failed to post end of append"); + goto error; + } + } + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GST_DEBUG_OBJECT (self, "eos event"); + if (send_shutdown (self)) { + return GST_PAD_PROBE_OK; + } else { + GST_ERROR_OBJECT (self, "failed to post shutdown"); + goto error; + } + } + + return GST_PAD_PROBE_OK; + +error: + GST_PAD_PROBE_INFO_FLOW_RETURN (info) = GST_FLOW_ERROR; + gst_event_unref (event); + return GST_PAD_PROBE_HANDLED; +} + +static AddTrackResult +add_track (GstAppendPipeline * self, GstPad * pad, GstStream * stream, + GstCaps * caps, Track * added_track) +{ + GstStreamType type = gst_stream_get_stream_type (stream); + GstMediaSourceTrackType track_type = + gst_media_source_track_type_from_stream_type (type); + + switch (type) { + case GST_STREAM_TYPE_AUDIO: + case GST_STREAM_TYPE_TEXT: + case GST_STREAM_TYPE_VIDEO: + break; + default:{ + GST_DEBUG_OBJECT (self, "unexpected caps %" GST_PTR_FORMAT + ", using black hole probe", caps); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, black_hole_probe, self, + NULL); + return IGNORED; + } + } + + if (type != GST_STREAM_TYPE_TEXT && + !gst_media_source_media_type_is_caps_supported (caps)) { + GST_ERROR_OBJECT (self, "unsupported caps: %" GST_PTR_FORMAT, caps); + return PARSE_ERROR; + } + + GstAppSink *appsink = new_appsink (self, type); + gst_bin_add (GST_BIN (self->pipeline), GST_ELEMENT (appsink)); + gst_element_sync_state_with_parent (GST_ELEMENT (appsink)); + + GstPad *appsink_pad = + gst_element_get_static_pad (GST_ELEMENT (appsink), "sink"); + GstPadLinkReturn link_result = gst_pad_link (pad, appsink_pad); + gst_clear_object (&appsink_pad); + if (GST_PAD_LINK_FAILED (link_result)) { + g_error ("failed to link parser to appsink: %s", + gst_pad_link_get_name (link_result)); + } + + Track track_template = { + .parent = self, + .sink = gst_object_ref (appsink), + .src_pad = gst_object_ref (pad), + .stream = gst_object_ref (stream), + .mse_track = gst_media_source_track_new_with_initial_caps (track_type, + GST_OBJECT_NAME (appsink), caps), + .previous_pts = GST_CLOCK_TIME_NONE, + }; + g_array_append_val (self->tracks, track_template); + + GST_TRACE_OBJECT (self, "added appsink %s to pad %s", + GST_OBJECT_NAME (appsink), GST_OBJECT_NAME (pad)); + + *added_track = track_template; + + return ADDED; +} + +static void +clear_track (Track * track) +{ + gst_clear_object (&track->sink); + gst_clear_object (&track->src_pad); + gst_clear_object (&track->mse_track); + gst_clear_object (&track->stream); +} + +static inline GPtrArray * +init_segment_tracks_for (InitSegment * self, GstStreamType type) +{ + switch (type) { + case GST_STREAM_TYPE_AUDIO: + return self->audio_tracks; + case GST_STREAM_TYPE_TEXT: + return self->text_tracks; + case GST_STREAM_TYPE_VIDEO: + return self->video_tracks; + default: + g_assert_not_reached (); + return NULL; + } +} + +static void +process_init_segment_track (GstPad * pad, GstAppendPipeline * self) +{ + GST_OBJECT_LOCK (self); + InitSegment *init_segment = &self->init_segment; + GstStream *stream = gst_pad_get_stream (pad); + GstCaps *caps = gst_stream_get_caps (stream); + GST_DEBUG_OBJECT (self, "%" GST_PTR_FORMAT " got caps %" GST_PTR_FORMAT, pad, + caps); + + if (gst_pad_is_linked (pad)) { + GST_TRACE_OBJECT (self, "%" GST_PTR_FORMAT " is already linked, skipping", + pad); + goto done; + } + + if (!GST_IS_CAPS (caps)) { + GST_ERROR_OBJECT (self, "no caps on %" GST_PTR_FORMAT + " after stream collection", pad); + call_parse_error (self); + goto done; + } + + Track track; + AddTrackResult result = add_track (self, pad, stream, caps, &track); + GstMediaSourceTrack *mse_track = track.mse_track; + switch (result) { + case ADDED:{ + GstStreamType type = gst_stream_get_stream_type (stream); + GPtrArray *tracks = init_segment_tracks_for (init_segment, type); + if (tracks->len < 1) { + gst_media_source_track_set_active (mse_track, TRUE); + } + g_ptr_array_add (tracks, gst_object_ref (mse_track)); + break; + } + case IGNORED: + break; + case PARSE_ERROR: + call_parse_error (self); + break; + } + +done: + gst_clear_object (&stream); + gst_clear_caps (&caps); + GST_OBJECT_UNLOCK (self); +} + +static void +on_pad_added (GstElement * parsebin, GstPad * pad, gpointer user_data) +{ + GstAppendPipeline *self = GST_APPEND_PIPELINE (user_data); + process_init_segment_track (pad, self); + process_init_segment (self); +} + +static gboolean +has_track_for_stream (GstAppendPipeline * self, GstStream * stream) +{ + guint track_count = n_tracks (self); + for (guint i = 0; i < track_count; i++) { + Track *track = index_track (self, i); + if (track->stream == stream) { + return TRUE; + } + } + return FALSE; +} + +static gboolean +has_all_tracks (GstAppendPipeline * self) +{ + if (!GST_IS_STREAM_COLLECTION (self->streams)) { + return FALSE; + } + for (guint i = 0; i < gst_stream_collection_get_size (self->streams); i++) { + GstStream *stream = gst_stream_collection_get_stream (self->streams, i); + switch (gst_stream_get_stream_type (stream)) { + case GST_STREAM_TYPE_AUDIO: + case GST_STREAM_TYPE_VIDEO: + case GST_STREAM_TYPE_TEXT: + break; + default: + continue; + } + if (!has_track_for_stream (self, stream)) { + return FALSE; + } + } + return TRUE; +} + +static void +process_init_segment (GstAppendPipeline * self) +{ + gint64 duration; + InitSegment *init_segment = &self->init_segment; + + GST_OBJECT_LOCK (self); + + if (!has_all_tracks (self)) { + goto done; + } + + if (gst_element_query_duration (self->parsebin, GST_FORMAT_TIME, &duration)) { + init_segment->duration = MAX (0, duration); + } else { + init_segment->duration = GST_CLOCK_TIME_NONE; + } + + GST_DEBUG_OBJECT (self, "init segment says duration=%" GST_TIME_FORMAT, + GST_TIME_ARGS ((GstClockTime) duration)); + + self->received_init_segment = TRUE; + + call_received_init_segment (self); + +done: + GST_OBJECT_UNLOCK (self); +} + +static inline void +init_segment_init (InitSegment * self) +{ + self->audio_tracks = g_ptr_array_new_with_free_func (gst_object_unref); + self->text_tracks = g_ptr_array_new_with_free_func (gst_object_unref); + self->video_tracks = g_ptr_array_new_with_free_func (gst_object_unref); + self->duration = GST_CLOCK_TIME_NONE; +} + +static inline void +init_segment_finalize (InitSegment * self) +{ + g_ptr_array_free (self->audio_tracks, TRUE); + g_ptr_array_free (self->text_tracks, TRUE); + g_ptr_array_free (self->video_tracks, TRUE); +} + +static GArray * +new_tracks_array (void) +{ + GArray *tracks = g_array_new (TRUE, TRUE, sizeof (Track)); + g_array_set_clear_func (tracks, (GDestroyNotify) clear_track); + return tracks; +} + +static BackgroundTask * +background_task_new (GstAppendPipeline * pipeline) +{ + BackgroundTask *task = g_new0 (BackgroundTask, 1); + g_rec_mutex_init (&task->mutex); + task->task = gst_task_new (task_function, task, NULL); + task->pipeline = pipeline; + task->bus = gst_object_ref (pipeline->bus); + gst_task_set_lock (task->task, &task->mutex); + return task; +} + +static gboolean +background_task_start (BackgroundTask * task) +{ + gst_bus_set_flushing (task->bus, FALSE); + return gst_task_start (task->task); +} + +static gboolean +background_task_stop (BackgroundTask * task) +{ + send_abort (task->pipeline); + gst_task_join (task->task); + gst_bus_set_flushing (task->bus, TRUE); + return TRUE; +} + +static void +background_task_cleanup (gpointer ptr) +{ + BackgroundTask *task = (BackgroundTask *) ptr; + background_task_stop (task); + task->pipeline = NULL; + gst_clear_object (&task->task); + gst_clear_object (&task->bus); + g_rec_mutex_clear (&task->mutex); + g_free (task); +} + +static void +gst_append_pipeline_init (GstAppendPipeline * self) +{ + GstElement *appsrc = GST_ELEMENT (gst_element_factory_make ("appsrc", "src")); + GstElement *parsebin = + GST_ELEMENT (gst_element_factory_make ("parsebin", "parse")); + GstElement *pipeline = gst_pipeline_new ("append-pipeline"); + + GstPad *appsrc_pad = GST_PAD (gst_element_get_static_pad (appsrc, "src")); + gst_pad_add_probe (appsrc_pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + event_probe, self, NULL); + gst_object_unref (appsrc_pad); + + gst_bin_add_many (GST_BIN (pipeline), appsrc, parsebin, NULL); + if (!gst_element_link (appsrc, parsebin)) { + g_error ("failed to link appsrc to parsebin"); + } + + self->bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + self->pipeline = gst_object_ref_sink (GST_PIPELINE (pipeline)); + self->src = GST_APP_SRC (appsrc); + self->parsebin = parsebin; + + self->task = background_task_new (self); + + g_signal_connect_object (parsebin, "pad-added", G_CALLBACK (on_pad_added), + self, 0); + + self->received_init_segment = FALSE; + self->encountered_error = FALSE; + self->tracks = new_tracks_array (); + init_segment_init (&self->init_segment); +} + +static void +gst_append_pipeline_dispose (GObject * object) +{ + GstAppendPipeline *self = (GstAppendPipeline *) object; + + send_shutdown (self); + g_clear_pointer (&self->task, background_task_cleanup); + + gst_element_set_state (GST_ELEMENT (self->pipeline), GST_STATE_NULL); + + G_OBJECT_CLASS (gst_append_pipeline_parent_class)->dispose (object); +} + +static void +gst_append_pipeline_finalize (GObject * object) +{ + GstAppendPipeline *self = (GstAppendPipeline *) object; + + gst_clear_object (&self->pipeline); + gst_clear_object (&self->bus); + + init_segment_finalize (&self->init_segment); + + g_array_free (self->tracks, TRUE); + gst_clear_object (&self->streams); + + G_OBJECT_CLASS (gst_append_pipeline_parent_class)->finalize (object); +} + +static void +gst_append_pipeline_class_init (GstAppendPipelineClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = GST_DEBUG_FUNCPTR (gst_append_pipeline_dispose); + oclass->finalize = GST_DEBUG_FUNCPTR (gst_append_pipeline_finalize); +} + +GstAppendPipeline * +gst_append_pipeline_new (GstAppendPipelineCallbacks * callbacks, + gpointer user_data, GError ** error) +{ + gst_mse_init_logging (); + GstAppendPipeline *self = g_object_new (GST_TYPE_APPEND_PIPELINE, NULL); + GstStateChangeReturn started = + gst_element_set_state (GST_ELEMENT (self->pipeline), GST_STATE_PLAYING); + if (started != GST_STATE_CHANGE_SUCCESS) { + GST_ERROR_OBJECT (self, "failed to start: %s", + gst_element_state_change_return_get_name (started)); + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "failed to start append pipeline"); + goto error; + } + if (callbacks) { + self->callbacks.callbacks = *callbacks; + self->callbacks.user_data = user_data; + } + if (!background_task_start (self->task)) { + GST_ERROR_OBJECT (self, "failed to start background task"); + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "failed to start append pipeline's background task"); + goto error; + } + return gst_object_ref_sink (self); +error: + gst_clear_object (&self); + return NULL; +} + +GstFlowReturn +gst_append_pipeline_append (GstAppendPipeline * self, GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_FLOW_ERROR); + GstFlowReturn push_result = gst_app_src_push_buffer (self->src, buffer); + if (push_result != GST_FLOW_OK) + return push_result; + + if (!gst_element_send_event (GST_ELEMENT_CAST (self->src), + new_end_of_append_event ())) { + GST_ERROR_OBJECT (self, "failed to push end-of-append event"); + return GST_FLOW_ERROR; + } + return GST_FLOW_OK; +} + +GstFlowReturn +gst_append_pipeline_eos (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_FLOW_ERROR); + return gst_app_src_end_of_stream (self->src); +} + +gboolean +gst_append_pipeline_stop (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE); + + GstElement *pipeline = GST_ELEMENT (self->pipeline); + + GstStateChangeReturn stopped = + gst_element_set_state (pipeline, GST_STATE_NULL); + if (stopped != GST_STATE_CHANGE_SUCCESS) { + GST_ERROR_OBJECT (self, "failed to stop: %s", + gst_element_state_change_return_get_name (stopped)); + return FALSE; + } + self->received_init_segment = FALSE; + self->encountered_error = FALSE; + + return TRUE; +} + +gboolean +gst_append_pipeline_reset (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE); + + GstElement *pipeline = GST_ELEMENT (self->pipeline); + + GstStateChangeReturn stopped = + gst_element_set_state (pipeline, GST_STATE_READY); + if (stopped != GST_STATE_CHANGE_SUCCESS) { + GST_ERROR_OBJECT (self, "failed to stop: %s", + gst_element_state_change_return_get_name (stopped)); + return FALSE; + } + + background_task_stop (self->task); + + init_segment_finalize (&self->init_segment); + gst_clear_object (&self->streams); + g_array_free (self->tracks, TRUE); + + self->received_init_segment = FALSE; + self->have_outstanding_samples = FALSE; + self->encountered_error = FALSE; + self->tracks = new_tracks_array (); + init_segment_init (&self->init_segment); + + if (!background_task_start (self->task)) { + GST_ERROR_OBJECT (self, "failed to start background task"); + return FALSE; + } + + GstStateChangeReturn started = + gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (started == GST_STATE_CHANGE_SUCCESS) { + return TRUE; + } else { + GST_ERROR_OBJECT (self, "failed to start: %s", + gst_element_state_change_return_get_name (started)); + return FALSE; + } +} + +gsize +gst_append_pipeline_n_tracks (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), 0); + return n_tracks (self); +} + +gboolean +gst_append_pipeline_has_init_segment (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE); + return self->received_init_segment; +} + +GstClockTime +gst_append_pipeline_get_duration (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), GST_CLOCK_TIME_NONE); + if (self->received_init_segment) { + return self->init_segment.duration; + } else { + return GST_CLOCK_TIME_NONE; + } +} + +GPtrArray * +gst_append_pipeline_get_audio_tracks (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL); + if (self->received_init_segment) { + return self->init_segment.audio_tracks; + } else { + return NULL; + } +} + +GPtrArray * +gst_append_pipeline_get_text_tracks (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL); + if (self->received_init_segment) { + return self->init_segment.text_tracks; + } else { + return NULL; + } +} + +GPtrArray * +gst_append_pipeline_get_video_tracks (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), NULL); + if (self->received_init_segment) { + return self->init_segment.video_tracks; + } else { + return NULL; + } +} + +gboolean +gst_append_pipeline_get_eos (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE); + return gst_task_get_state (self->task->task) != GST_TASK_STARTED; +} + +void +gst_append_pipeline_fail (GstAppendPipeline * self) +{ + g_return_if_fail (GST_IS_APPEND_PIPELINE (self)); + gst_bus_post (self->bus, gst_message_new_error (NULL, NULL, NULL)); +} + +gboolean +gst_append_pipeline_get_failed (GstAppendPipeline * self) +{ + g_return_val_if_fail (GST_IS_APPEND_PIPELINE (self), FALSE); + return self->encountered_error; +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h new file mode 100644 index 0000000000..0b4e1d08cf --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource-private.h @@ -0,0 +1,58 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstmediasource.h" +#include "gstmseeventqueue-private.h" + +G_BEGIN_DECLS + +GST_MSE_PRIVATE +gboolean gst_media_source_is_attached (GstMediaSource * self); + +GST_MSE_PRIVATE +void gst_media_source_open (GstMediaSource * self); + +GST_MSE_PRIVATE +GstMseSrc * gst_media_source_get_source_element (GstMediaSource * self); + +GST_MSE_PRIVATE +void gst_media_source_seek (GstMediaSource * self, GstClockTime time); + +struct _GstMediaSource +{ + GstObject parent_instance; + + GstMseSrc *element; + GstMseEventQueue *event_queue; + + GstSourceBufferList *buffers; + GstSourceBufferList *active_buffers; + + GstMediaSourceRange live_seekable_range; + + GstClockTime duration; + GstMediaSourceReadyState ready_state; +}; + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c new file mode 100644 index 0000000000..f499f54f24 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.c @@ -0,0 +1,1063 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2013 Google Inc. All rights reserved. + * Copyright (C) 2013 Orange + * Copyright (C) 2013-2017 Apple Inc. All rights reserved. + * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015, 2016 Igalia, S.L + * Copyright (C) 2015, 2016 Metrological Group B.V. + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstmediasource + * @title: GstMediaSource + * @short_description: Media Source + * @symbols: + * - GstMediaSource + * + * #GstMediaSource is the entry point into the W3C Media Source API. It offers + * functionality similar to #GstAppSrc for client-side web or JavaScript + * applications decoupling the source of media from its processing and playback. + * + * To interact with a Media Source, connect it to a #GstMseSrc that is in some + * #GstPipeline using gst_media_source_attach(). Then create at least one + * #GstSourceBuffer using gst_media_source_add_source_buffer(). Finally, feed + * some media data to the Source Buffer(s) using + * gst_source_buffer_append_buffer() and play the pipeline. + * + * Since: 1.24 + */ + +/** + * GstMediaSource: + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstmediasource.h" +#include "gstmediasource-private.h" + +#include "gstmselogging-private.h" +#include "gstmsemediatype-private.h" +#include "gstsourcebuffer-private.h" +#include "gstsourcebufferlist-private.h" + +#include "gstmsesrc.h" +#include "gstmsesrc-private.h" + +G_DEFINE_TYPE (GstMediaSource, gst_media_source, GST_TYPE_OBJECT); +G_DEFINE_QUARK (gst_media_source_error_quark, gst_media_source_error); + +enum +{ + PROP_0, + + PROP_SOURCE_BUFFERS, + PROP_ACTIVE_SOURCE_BUFFERS, + PROP_READY_STATE, + PROP_POSITION, + PROP_DURATION, + + N_PROPS, +}; + +typedef enum +{ + ON_SOURCE_OPEN, + ON_SOURCE_ENDED, + ON_SOURCE_CLOSE, + + N_SIGNALS, +} MediaSourceEvent; + +typedef struct +{ + GstDataQueueItem item; + MediaSourceEvent event; +} MediaSourceEventItem; + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; + +#define DEFAULT_READY_STATE GST_MEDIA_SOURCE_READY_STATE_CLOSED +#define DEFAULT_POSITION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE + +static void rebuild_active_source_buffers (GstMediaSource * self); + +/** + * gst_media_source_is_type_supported: + * @type: (transfer none): A MIME type value + * + * Determines whether the current Media Source configuration can process media + * of the supplied @type. + * + * Returns: `TRUE` when supported, `FALSE` otherwise + * + * Since: 1.24 + */ +gboolean +gst_media_source_is_type_supported (const gchar * type) +{ + gst_mse_init_logging (); + g_return_val_if_fail (type != NULL, FALSE); + + if (g_strcmp0 (type, "") == 0) { + return FALSE; + } + + GstMediaSourceMediaType media_type = GST_MEDIA_SOURCE_MEDIA_TYPE_INIT; + if (!gst_media_source_media_type_parse (&media_type, type)) { + return FALSE; + } + + gboolean supported = gst_media_source_media_type_is_supported (&media_type); + + gst_media_source_media_type_reset (&media_type); + + return supported; +} + +/** + * gst_media_source_new: + * + * Creates a new #GstMediaSource instance. The instance is in the + * %GST_MEDIA_SOURCE_READY_STATE_CLOSED state and is not associated with any + * media player. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-constructor) + * + * Returns: (transfer full): a new #GstMediaSource instance + * Since: 1.24 + */ +GstMediaSource * +gst_media_source_new (void) +{ + gst_mse_init_logging (); + return g_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE, NULL)); +} + +static inline void +empty_buffers (GstMediaSource * self) +{ + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + break; + } + gst_object_unparent (GST_OBJECT_CAST (buf)); + gst_object_unref (buf); + } + gst_source_buffer_list_remove_all (self->buffers); +} + +static void +gst_media_source_dispose (GObject * object) +{ + GstMediaSource *self = (GstMediaSource *) object; + + gst_media_source_detach (self); + + g_clear_object (&self->active_buffers); + + if (self->buffers) { + empty_buffers (self); + } + gst_clear_object (&self->buffers); + + gst_clear_object (&self->event_queue); + + G_OBJECT_CLASS (gst_media_source_parent_class)->dispose (object); +} + +static void +gst_media_source_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstMediaSource *self = GST_MEDIA_SOURCE (object); + + switch (prop_id) { + case PROP_SOURCE_BUFFERS: + g_value_take_object (value, gst_media_source_get_source_buffers (self)); + break; + case PROP_ACTIVE_SOURCE_BUFFERS: + g_value_take_object (value, + gst_media_source_get_active_source_buffers (self)); + break; + case PROP_READY_STATE: + g_value_set_enum (value, gst_media_source_get_ready_state (self)); + break; + case PROP_POSITION: + g_value_set_uint64 (value, gst_media_source_get_position (self)); + break; + case PROP_DURATION: + g_value_set_uint64 (value, gst_media_source_get_duration (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_media_source_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMediaSource *self = GST_MEDIA_SOURCE (object); + + switch (prop_id) { + case PROP_DURATION:{ + GstClockTime duration = (GstClockTime) g_value_get_uint64 (value); + gst_media_source_set_duration (self, duration, NULL); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_media_source_class_init (GstMediaSourceClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = GST_DEBUG_FUNCPTR (gst_media_source_dispose); + oclass->get_property = GST_DEBUG_FUNCPTR (gst_media_source_get_property); + oclass->set_property = GST_DEBUG_FUNCPTR (gst_media_source_set_property); + + /** + * GstMediaSource:source-buffers: + * + * A #GstSourceBufferList of every #GstSourceBuffer in this Media Source + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers) + * + * Since: 1.24 + */ + properties[PROP_SOURCE_BUFFERS] = g_param_spec_object ("source-buffers", + "Source Buffers", + "A SourceBufferList of all SourceBuffers in this Media Source", + GST_TYPE_SOURCE_BUFFER_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstMediaSource:active-source-buffers: + * + * A #GstSourceBufferList of every #GstSourceBuffer in this Media Source that + * is considered active + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers) + * + * Since: 1.24 + */ + properties[PROP_ACTIVE_SOURCE_BUFFERS] = + g_param_spec_object ("active-source-buffers", "Active Source Buffers", + "A SourceBufferList of all SourceBuffers that are active in this Media Source", + GST_TYPE_SOURCE_BUFFER_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstMediaSource:ready-state: + * + * The Ready State of the Media Source + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate) + * + * Since: 1.24 + */ + properties[PROP_READY_STATE] = g_param_spec_enum ("ready-state", + "Ready State", + "The Ready State of the Media Source", + GST_TYPE_MEDIA_SOURCE_READY_STATE, DEFAULT_READY_STATE, G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + /** + * GstMediaSource:position: + * + * The position of the player consuming from the Media Source + * + * Since: 1.24 + */ + properties[PROP_POSITION] = g_param_spec_uint64 ("position", + "Position", + "The Position of the Media Source as a GstClockTime", + GST_CLOCK_TIME_NONE, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * GstMediaSource:duration: + * + * The Duration of the Media Source as a #GstClockTime + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + * + * Since: 1.24 + */ + properties[PROP_DURATION] = g_param_spec_uint64 ("duration", + "Duration", + "The Duration of the Media Source as a GstClockTime", + GST_CLOCK_TIME_NONE, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPS, properties); + + /** + * GstMediaSource::on-source-open: + * @self: The #GstMediaSource that has just opened + * + * Emitted when @self has been opened. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceopen) + * + * Since: 1.24 + */ + signals[ON_SOURCE_OPEN] = g_signal_new ("on-source-open", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstMediaSource::on-source-ended: + * @self: The #GstMediaSource that has just ended + * + * Emitted when @self has ended, normally through + * gst_media_source_end_of_stream(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceended) + * + * Since: 1.24 + */ + signals[ON_SOURCE_ENDED] = g_signal_new ("on-source-ended", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstMediaSource::on-source-closed: + * @self: The #GstMediaSource that has just closed + * + * Emitted when @self has closed, normally when detached from a #GstMseSrc. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-onsourceclose) + * + * Since: 1.24 + */ + signals[ON_SOURCE_CLOSE] = g_signal_new ("on-source-close", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + +} + +static inline void +reset_live_seekable_range (GstMediaSource * self) +{ + self->live_seekable_range.start = 0; + self->live_seekable_range.end = 0; +} + +static inline gboolean +is_updating (GstMediaSource * self) +{ + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) + break; + gboolean updating = gst_source_buffer_get_updating (buf); + gst_object_unref (buf); + if (updating) { + return TRUE; + } + } + return FALSE; +} + +static inline gboolean +is_attached (GstMediaSource * self) +{ + return GST_IS_MSE_SRC (self->element); +} + +static inline void +network_error (GstMediaSource * self) +{ + if (is_attached (self)) { + gst_mse_src_network_error (self->element); + } +} + +static inline void +decode_error (GstMediaSource * self) +{ + if (is_attached (self)) { + gst_mse_src_decode_error (self->element); + } +} + +static inline void +update_duration (GstMediaSource * self) +{ + if (is_attached (self)) { + gst_mse_src_set_duration (self->element, self->duration); + } +} + +static void +schedule_event (GstMediaSource * self, MediaSourceEvent event) +{ + MediaSourceEventItem item = { + .item = {.destroy = g_free,.visible = TRUE,.size = 1,.object = NULL}, + .event = event, + }; + + gst_mse_event_queue_push (self->event_queue, g_memdup2 (&item, + sizeof (MediaSourceEventItem))); +} + +static void +dispatch_event (MediaSourceEventItem * item, GstMediaSource * self) +{ + g_signal_emit (self, signals[item->event], 0); +} + +static void +gst_media_source_init (GstMediaSource * self) +{ + self->buffers = gst_source_buffer_list_new (); + self->active_buffers = gst_source_buffer_list_new (); + self->ready_state = DEFAULT_READY_STATE; + self->duration = DEFAULT_DURATION; + reset_live_seekable_range (self); + self->element = NULL; + self->event_queue = + gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self); +} + +/** + * gst_media_source_attach: + * @self: #GstMediaSource instance + * @element: (transfer none): #GstMseSrc source Element + * + * Associates @self with @element. + * Normally, the Element will be part of a #GstPipeline that plays back the data + * submitted to the Media Source's Source Buffers. + * + * #GstMseSrc is a special source element that is designed to consume media from + * a #GstMediaSource. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dfn-attaching-to-a-media-element) + * + * Since: 1.24 + */ +void +gst_media_source_attach (GstMediaSource * self, GstMseSrc * element) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self)); + g_return_if_fail (GST_IS_MSE_SRC (element)); + + if (is_attached (self)) + gst_media_source_detach (self); + + self->element = gst_object_ref_sink (element); + gst_mse_src_attach (element, self); + + self->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN; + schedule_event (self, ON_SOURCE_OPEN); +} + +/** + * gst_media_source_detach: + * @self: #GstMediaSource instance + * + * Detaches @self from any #GstMseSrc element that it may be associated with. + * + * Since: 1.24 + */ +void +gst_media_source_detach (GstMediaSource * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self)); + + self->ready_state = GST_MEDIA_SOURCE_READY_STATE_CLOSED; + gst_media_source_set_duration (self, GST_CLOCK_TIME_NONE, NULL); + + gst_source_buffer_list_remove_all (self->active_buffers); + empty_buffers (self); + + if (is_attached (self)) { + gst_mse_src_detach (self->element); + gst_clear_object (&self->element); + } + + schedule_event (self, ON_SOURCE_CLOSE); +} + +/** + * gst_media_source_get_source_element: + * @self: #GstMediaSource instance + * + * Gets the #GstMseSrc currently attached to @self or `NULL` if there is none. + * + * Returns: (transfer full) (nullable): a #GstMseSrc instance or `NULL` + */ +GstMseSrc * +gst_media_source_get_source_element (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL); + GST_OBJECT_LOCK (self); + GstMseSrc *element = self->element == NULL ? NULL + : gst_object_ref (self->element); + GST_OBJECT_UNLOCK (self); + return element; +} + +void +gst_media_source_open (GstMediaSource * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self)); + if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) { + self->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN; + schedule_event (self, ON_SOURCE_OPEN); + } +} + +/** + * gst_media_source_get_source_buffers: + * @self: #GstMediaSource instance + * + * Gets a #GstSourceBufferList containing all the Source Buffers currently + * associated with this Media Source. This object will reflect any future + * changes to the parent Media Source as well. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-sourcebuffers) + * + * Returns: (transfer full): a #GstSourceBufferList instance + * Since: 1.24 + */ +GstSourceBufferList * +gst_media_source_get_source_buffers (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL); + return g_object_ref (self->buffers); +} + +/** + * gst_media_source_get_active_source_buffers: + * @self: #GstMediaSource instance + * + * Gets a #GstSourceBufferList containing all the Source Buffers currently + * associated with this Media Source that are considered "active." + * For a Source Buffer to be considered active, either its video track is + * selected, its audio track is enabled, or its text track is visible or hidden. + * This object will reflect any future changes to the parent Media Source as + * well. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-activesourcebuffers) + * + * Returns: (transfer full): a new #GstSourceBufferList instance + * Since: 1.24 + */ +GstSourceBufferList * +gst_media_source_get_active_source_buffers (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL); + return g_object_ref (self->active_buffers); +} + +/** + * gst_media_source_get_ready_state: + * @self: #GstMediaSource instance + * + * Gets the current Ready State of the Media Source. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-readystate) + * + * Returns: the current #GstMediaSourceReadyState value + * Since: 1.24 + */ +GstMediaSourceReadyState +gst_media_source_get_ready_state (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_READY_STATE); + return self->ready_state; +} + +/** + * gst_media_source_get_position: + * @self: #GstMediaSource instance + * + * Gets the current playback position of the Media Source. + * + * Returns: the current playback position as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_media_source_get_position (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_POSITION); + if (is_attached (self)) + return gst_mse_src_get_position (self->element); + return DEFAULT_POSITION; +} + +/** + * gst_media_source_get_duration: + * @self: #GstMediaSource instance + * + * Gets the current duration of @self. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + * + * Returns: the current duration as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_media_source_get_duration (GstMediaSource * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), DEFAULT_DURATION); + if (self->ready_state == GST_MEDIA_SOURCE_READY_STATE_CLOSED) + return GST_CLOCK_TIME_NONE; + return self->duration; +} + +/** + * gst_media_source_set_duration: + * @self: #GstMediaSource instance + * @duration: The new duration to apply to @self. + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Sets the duration of @self. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-duration) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_media_source_set_duration (GstMediaSource * self, GstClockTime duration, + GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE); + self->duration = duration; + update_duration (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); + return TRUE; +} + +static void +on_received_init_segment (G_GNUC_UNUSED GstSourceBuffer * source_buffer, + gpointer user_data) +{ + GstMediaSource *self = GST_MEDIA_SOURCE (user_data); + if (!is_attached (self)) { + GST_DEBUG_OBJECT (self, "received init segment while detached, ignoring"); + return; + } + + GPtrArray *all_tracks = g_ptr_array_new (); + + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + break; + } + GPtrArray *tracks = gst_source_buffer_get_all_tracks (buf); + g_ptr_array_extend (all_tracks, tracks, NULL, NULL); + g_ptr_array_unref (tracks); + gst_object_unref (buf); + } + + gst_mse_src_emit_streams (self->element, + (GstMediaSourceTrack **) all_tracks->pdata, all_tracks->len); + + g_ptr_array_unref (all_tracks); +} + +static void +on_duration_changed (G_GNUC_UNUSED GstSourceBuffer * source_buffer, + gpointer user_data) +{ + GstMediaSource *self = GST_MEDIA_SOURCE (user_data); + GstClockTime current = self->duration; + GstClockTime max = 0; + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + break; + } + GstClockTime duration = gst_source_buffer_get_duration (buf); + if (GST_CLOCK_TIME_IS_VALID (duration)) { + max = MAX (max, duration); + } + gst_object_unref (buf); + } + if (current == max) { + return; + } + GST_DEBUG_OBJECT (self, "updating %" GST_TIMEP_FORMAT "=>%" GST_TIMEP_FORMAT, + ¤t, &max); + gst_media_source_set_duration (self, max, NULL); +} + +static GHashTable * +source_buffer_list_as_set (GstSourceBufferList * list) +{ + GHashTable *buffers = g_hash_table_new_full (g_direct_hash, g_direct_equal, + gst_object_unref, NULL); + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (list, i); + if (buf == NULL) { + break; + } + g_hash_table_add (buffers, buf); + } + return buffers; +} + +static void +rebuild_active_source_buffers (GstMediaSource * self) +{ + // TODO: Lock the source buffer lists + GST_DEBUG_OBJECT (self, "rebuilding active source buffers"); + GHashTable *previously_active = + source_buffer_list_as_set (self->active_buffers); + + gst_source_buffer_list_notify_freeze (self->active_buffers); + gst_source_buffer_list_remove_all (self->active_buffers); + + gboolean added = FALSE; + gboolean removed = FALSE; + + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + break; + } + if (gst_source_buffer_get_active (buf)) { + gst_source_buffer_list_append (self->active_buffers, buf); + added |= !g_hash_table_contains (previously_active, buf); + } else { + gst_source_buffer_list_append (self->active_buffers, buf); + removed |= g_hash_table_contains (previously_active, buf); + } + gst_object_unref (buf); + } + g_hash_table_unref (previously_active); + + gst_source_buffer_list_notify_cancel (self->active_buffers); + gst_source_buffer_list_notify_thaw (self->active_buffers); + + if (added) { + GST_DEBUG_OBJECT (self, "notifying active source buffer added"); + gst_source_buffer_list_notify_added (self->active_buffers); + } + if (removed) { + GST_DEBUG_OBJECT (self, "notifying active source buffer removed"); + gst_source_buffer_list_notify_removed (self->active_buffers); + } +} + +static void +on_active_state_changed (GstSourceBuffer * source_buffer, gpointer user_data) +{ + GstMediaSource *self = GST_MEDIA_SOURCE (user_data); + rebuild_active_source_buffers (self); +} + +/** + * gst_media_source_add_source_buffer: + * @self: #GstMediaSource instance + * @type: (transfer none): A MIME type describing the format of the incoming media + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Add a #GstSourceBuffer to this #GstMediaSource of the specified media type. + * The Media Source must be in the #GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-addsourcebuffer) + * + * Returns: (transfer full): a new #GstSourceBuffer instance on success, otherwise `NULL` + * Since: 1.24 + */ +GstSourceBuffer * +gst_media_source_add_source_buffer (GstMediaSource * self, const gchar * type, + GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), NULL); + g_return_val_if_fail (type != NULL, NULL); + + if (g_strcmp0 (type, "") == 0) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "supplied content type is empty"); + return NULL; + } + + if (!gst_media_source_is_type_supported (type)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED, + "unsupported content type"); + return NULL; + } + + if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "media source is not open"); + return NULL; + } + + GstSourceBufferCallbacks callbacks = { + .duration_changed = on_duration_changed, + .received_init_segment = on_received_init_segment, + .active_state_changed = on_active_state_changed, + }; + + GError *source_buffer_error = NULL; + GstSourceBuffer *buf = gst_source_buffer_new_with_callbacks (type, + GST_OBJECT (self), &callbacks, self, &source_buffer_error); + if (source_buffer_error) { + g_propagate_prefixed_error (error, source_buffer_error, + "failed to create source buffer"); + gst_clear_object (&buf); + return NULL; + } + + gst_source_buffer_list_append (self->buffers, buf); + + return buf; +} + +/** + * gst_media_source_remove_source_buffer: + * @self: #GstMediaSource instance + * @buffer: (transfer none): #GstSourceBuffer instance + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Remove @buffer from @self. + * + * @buffer must have been created as a child of @self and @self must be in the + * #GstMediaSourceReadyState %GST_MEDIA_SOURCE_READY_STATE_OPEN. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-removesourcebuffer) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_media_source_remove_source_buffer (GstMediaSource * self, + GstSourceBuffer * buffer, GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE); + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (buffer), FALSE); + + if (!gst_source_buffer_list_contains (self->buffers, buffer)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_FOUND, + "the supplied source buffer was not found in this media source"); + return FALSE; + } + + if (gst_source_buffer_get_updating (buffer)) + gst_source_buffer_teardown (buffer); + + gst_source_buffer_list_remove (self->active_buffers, buffer); + + gst_object_unparent (GST_OBJECT (buffer)); + gst_source_buffer_list_remove (self->buffers, buffer); + + return TRUE; +} + +static void +abort_all_source_buffers (GstMediaSource * self) +{ + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + return; + } + GST_LOG_OBJECT (self, "shutting down %" GST_PTR_FORMAT, buf); + gst_source_buffer_abort (buf, NULL); + gst_object_unref (buf); + } +} + +/** + * gst_media_source_end_of_stream: + * @self: #GstMediaSource instance + * @eos_error: The error type, if any + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Mark @self as reaching the end of stream, disallowing new data inputs. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-endofstream) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_media_source_end_of_stream (GstMediaSource * self, + GstMediaSourceEOSError eos_error, GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE); + + if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "media source is not open"); + return FALSE; + } + + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "some buffers are still updating"); + return FALSE; + } + + self->ready_state = GST_MEDIA_SOURCE_READY_STATE_ENDED; + schedule_event (self, ON_SOURCE_ENDED); + + switch (eos_error) { + case GST_MEDIA_SOURCE_EOS_ERROR_NETWORK: + network_error (self); + break; + case GST_MEDIA_SOURCE_EOS_ERROR_DECODE: + decode_error (self); + break; + default: + update_duration (self); + abort_all_source_buffers (self); + break; + } + + return TRUE; +} + +/** + * gst_media_source_set_live_seekable_range: + * @self: #GstMediaSource instance + * @start: The earliest point in the stream considered seekable + * @end: The latest point in the stream considered seekable + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Set the live seekable range for @self. This range informs the component + * playing this Media Source what it can allow the user to seek through. + * + * If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, or the supplied + * @start time is later than @end it will fail and set an error. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-setliveseekablerange) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_media_source_set_live_seekable_range (GstMediaSource * self, + GstClockTime start, GstClockTime end, GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE); + if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "media source is not open"); + return FALSE; + } + + if (start > end) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "bad time range: start must be earlier than end"); + return FALSE; + } + + self->live_seekable_range.start = start; + self->live_seekable_range.end = end; + + return TRUE; +} + +/** + * gst_media_source_clear_live_seekable_range: + * @self: #GstMediaSource instance + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Clear the live seekable range for @self. This will inform the component + * playing this Media Source that there is no seekable time range. + * + * If the ready state is not %GST_MEDIA_SOURCE_READY_STATE_OPEN, it will fail + * and set an error. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-mediasource-clearliveseekablerange) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_media_source_clear_live_seekable_range (GstMediaSource * self, + GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (self), FALSE); + + if (self->ready_state != GST_MEDIA_SOURCE_READY_STATE_OPEN) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "media source is not open"); + return FALSE; + } + + reset_live_seekable_range (self); + + return TRUE; +} + +/** + * gst_media_source_get_live_seekable_range: + * @self: #GstMediaSource instance + * @range: (out) (transfer none): time range + * + * Get the live seekable range of @self. Will fill in the supplied @range with + * the current live seekable range. + * + * Since: 1.24 + */ +void +gst_media_source_get_live_seekable_range (GstMediaSource * self, + GstMediaSourceRange * range) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self)); + g_return_if_fail (range != NULL); + + range->start = self->live_seekable_range.start; + range->end = self->live_seekable_range.end; +} + +void +gst_media_source_seek (GstMediaSource * self, GstClockTime time) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self)); + for (guint i = 0;; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (self->buffers, i); + if (buf == NULL) { + return; + } + gst_source_buffer_seek (buf, time); + gst_object_unref (buf); + } +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h new file mode 100644 index 0000000000..304da3e624 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasource.h @@ -0,0 +1,203 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2013 Google Inc. All rights reserved. + * Copyright (C) 2013 Orange + * Copyright (C) 2013-2017 Apple Inc. All rights reserved. + * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015, 2016 Igalia, S.L + * Copyright (C) 2015, 2016 Metrological Group B.V. + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +#include "gstsourcebufferlist.h" + +G_BEGIN_DECLS + +/** + * GstMediaSourceReadyState: + * @GST_MEDIA_SOURCE_READY_STATE_CLOSED: The #GstMediaSource is not connected to + * any playback element. + * @GST_MEDIA_SOURCE_READY_STATE_OPEN: The #GstMediaSource is connected to a + * playback element and ready to append data to its #GstSourceBuffer (s). + * @GST_MEDIA_SOURCE_READY_STATE_ENDED: gst_media_source_end_of_stream() has + * been called on the current #GstMediaSource + * + * Describes the possible states of the Media Source. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-readystate) + * + * Since: 1.24 + */ +typedef enum +{ + GST_MEDIA_SOURCE_READY_STATE_CLOSED, + GST_MEDIA_SOURCE_READY_STATE_OPEN, + GST_MEDIA_SOURCE_READY_STATE_ENDED, +} GstMediaSourceReadyState; + +/** + * GstMediaSourceError: + * @GST_MEDIA_SOURCE_ERROR_INVALID_STATE: + * @GST_MEDIA_SOURCE_ERROR_TYPE: + * @GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED: + * @GST_MEDIA_SOURCE_ERROR_NOT_FOUND: + * @GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED: + * + * Any error that can occur within #GstMediaSource or #GstSourceBuffer APIs. + * These values correspond directly to those in the Web IDL specification. + * + * [Specification](https://webidl.spec.whatwg.org/#idl-DOMException-error-names) + * + * Since: 1.24 + */ +typedef enum +{ + GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + GST_MEDIA_SOURCE_ERROR_TYPE, + GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED, + GST_MEDIA_SOURCE_ERROR_NOT_FOUND, + GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED, +} GstMediaSourceError; + +/** + * GstMediaSourceEOSError: + * @GST_MEDIA_SOURCE_EOS_ERROR_NONE: End the stream successfully + * @GST_MEDIA_SOURCE_EOS_ERROR_NETWORK: End the stream due to a networking error + * @GST_MEDIA_SOURCE_EOS_ERROR_DECODE: End the stream due to a decoding error + * + * Reasons for ending a #GstMediaSource using gst_media_source_end_of_stream(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-endofstreamerror) + * + * Since: 1.24 + */ +typedef enum +{ + GST_MEDIA_SOURCE_EOS_ERROR_NONE, + GST_MEDIA_SOURCE_EOS_ERROR_NETWORK, + GST_MEDIA_SOURCE_EOS_ERROR_DECODE, +} GstMediaSourceEOSError; + +/** + * GstMediaSourceRange: + * @start: The start of this range. + * @end: The end of this range. + * + * A structure describing a simplified version of the TimeRanges concept in the + * HTML specification, only representing a single @start and @end time. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#timeranges) + * + * Since: 1.24 + */ +typedef struct +{ + GstClockTime start; + GstClockTime end; +} GstMediaSourceRange; + +GST_MSE_API +gboolean gst_media_source_is_type_supported (const gchar * type); + +#define GST_TYPE_MEDIA_SOURCE (gst_media_source_get_type()) +#define GST_MEDIA_SOURCE_ERROR (gst_media_source_error_quark()) + +/** + * gst_media_source_error_quark: + * + * Any error type that can be reported by the Media Source API. + * + * Since: 1.24 + */ +GST_MSE_API +GQuark gst_media_source_error_quark (void); + +GST_MSE_API +G_DECLARE_FINAL_TYPE (GstMediaSource, gst_media_source, GST, MEDIA_SOURCE, + GstObject); + +GST_MSE_API +GstMediaSource *gst_media_source_new (void); + +GST_MSE_API +void gst_media_source_attach (GstMediaSource * self, GstMseSrc * element); + +GST_MSE_API +void gst_media_source_detach (GstMediaSource * self); + +GST_MSE_API +GstSourceBufferList * gst_media_source_get_source_buffers ( + GstMediaSource * self); + +GST_MSE_API +GstSourceBufferList * gst_media_source_get_active_source_buffers ( + GstMediaSource * self); + +GST_MSE_API +GstMediaSourceReadyState gst_media_source_get_ready_state ( + GstMediaSource * self); + +GST_MSE_API +GstClockTime gst_media_source_get_position (GstMediaSource * self); + +GST_MSE_API +GstClockTime gst_media_source_get_duration (GstMediaSource * self); + +GST_MSE_API +gboolean gst_media_source_set_duration (GstMediaSource * self, + GstClockTime duration, + GError ** error); + +GST_MSE_API +GstSourceBuffer * gst_media_source_add_source_buffer (GstMediaSource * self, + const gchar * type, + GError ** error); + +GST_MSE_API +gboolean gst_media_source_remove_source_buffer (GstMediaSource * self, + GstSourceBuffer * buffer, + GError ** error); + +GST_MSE_API +gboolean gst_media_source_end_of_stream (GstMediaSource * self, + GstMediaSourceEOSError eos_error, + GError ** error); + +GST_MSE_API +gboolean gst_media_source_set_live_seekable_range (GstMediaSource * self, + GstClockTime start, + GstClockTime end, + GError ** error); + +GST_MSE_API +gboolean gst_media_source_clear_live_seekable_range (GstMediaSource * self, + GError ** error); + +GST_MSE_API +void gst_media_source_get_live_seekable_range (GstMediaSource * self, + GstMediaSourceRange * range); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h new file mode 100644 index 0000000000..f22c79bf98 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap-private.h @@ -0,0 +1,94 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_MEDIA_SOURCE_SAMPLE_MAP \ + (gst_media_source_sample_map_get_type()) + +GST_MSE_PRIVATE +G_DECLARE_FINAL_TYPE (GstMediaSourceSampleMap, gst_media_source_sample_map, GST, + MEDIA_SOURCE_SAMPLE_MAP, GstObject); + +GST_MSE_PRIVATE +GstMediaSourceSampleMap *gst_media_source_sample_map_new (void); + +GST_MSE_PRIVATE +void gst_media_source_sample_map_add (GstMediaSourceSampleMap * self, + GstSample * sample); + +GST_MSE_PRIVATE +void gst_media_source_sample_map_remove (GstMediaSourceSampleMap * self, + GstSample * sample); + +GST_MSE_PRIVATE +gsize gst_media_source_sample_map_remove_range (GstMediaSourceSampleMap * self, + GstClockTime earliest, GstClockTime latest); + +GST_MSE_PRIVATE +gsize +gst_media_source_sample_map_remove_range_from_start (GstMediaSourceSampleMap + * self, GstClockTime latest_dts); + +GST_MSE_PRIVATE +gsize +gst_media_source_sample_map_remove_range_from_end (GstMediaSourceSampleMap + * self, GstClockTime earliest_dts); + +GST_MSE_PRIVATE +gboolean gst_media_source_sample_map_contains (GstMediaSourceSampleMap * self, + GstSample * sample); + +GST_MSE_PRIVATE +GSequenceIter * gst_media_source_sample_map_get_begin_iter_by_pts ( + GstMediaSourceSampleMap * self); + +GST_MSE_PRIVATE +GstClockTime gst_media_source_sample_map_get_highest_end_time ( + GstMediaSourceSampleMap * self); + +GST_MSE_PRIVATE +guint gst_media_source_sample_map_get_size (GstMediaSourceSampleMap * self); + +GST_MSE_PRIVATE +gsize gst_media_source_sample_map_get_storage_size ( + GstMediaSourceSampleMap * self); + +GST_MSE_PRIVATE +GstIterator * +gst_media_source_sample_map_iter_samples_by_dts (GstMediaSourceSampleMap * map, + GMutex * lock, guint32 * master_cookie, GstClockTime start_dts, + GstSample * start_sample); + +GST_MSE_PRIVATE +GstIterator * +gst_media_source_sample_map_iter_samples_by_pts (GstMediaSourceSampleMap * map, + GMutex * lock, guint32 * master_cookie, GstClockTime start_pts, + GstSample *start_sample); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c new file mode 100644 index 0000000000..6728c6c567 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcesamplemap.c @@ -0,0 +1,551 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstmediasourcesamplemap-private.h" +#include "gstmselogging-private.h" + +struct _GstMediaSourceSampleMap +{ + GstObject parent_instance; + + GHashTable *samples; + GSequence *samples_by_dts; + GSequence *samples_by_pts; + + gsize storage_size; +}; + +G_DEFINE_TYPE (GstMediaSourceSampleMap, gst_media_source_sample_map, + GST_TYPE_OBJECT); + +static gint compare_pts (GstSample * a, GstSample * b, gpointer user_data); +static GSequenceIter *find_sample_containing_dts (GstMediaSourceSampleMap * + self, GstClockTime dts); +static GSequenceIter *find_sample_containing_pts (GstMediaSourceSampleMap * + self, GstClockTime pts); +static GSequenceIter *find_sequentially (GSequence * sequence, gpointer item); + +static inline GstClockTime +sample_dts (GstSample * sample) +{ + return GST_BUFFER_DTS (gst_sample_get_buffer (sample)); +} + +static inline GstClockTime +sample_pts (GstSample * sample) +{ + return GST_BUFFER_PTS (gst_sample_get_buffer (sample)); +} + +static gsize +sample_buffer_size (GstSample * sample) +{ + return gst_buffer_get_size (gst_sample_get_buffer (sample)); +} + +static gboolean +iter_is_delta_unit (GSequenceIter * iter) +{ + if (g_sequence_iter_is_end (iter)) { + return TRUE; + } + GstSample *sample = g_sequence_get (iter); + GstBuffer *buffer = gst_sample_get_buffer (sample); + return GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); +} + +static gint +compare_dts (GstSample * a, GstSample * b, gpointer user_data) +{ + GstClockTime a_dts = sample_dts (a); + GstClockTime b_dts = sample_dts (b); + if (a_dts == b_dts) { + return compare_pts (a, b, user_data); + } + return a_dts > b_dts ? +1 : -1; +} + +static gint +compare_pts (GstSample * a, GstSample * b, gpointer user_data) +{ + GstClockTime a_pts = sample_pts (a); + GstClockTime b_pts = sample_pts (b); + if (a_pts == b_pts) { + return 0; + } + return a_pts > b_pts ? +1 : -1; +} + +static inline void +remove_sample (GSequence * sequence, GCompareDataFunc compare, + GstSample * sample, GstMediaSourceSampleMap * self) +{ + GSequenceIter *match = g_sequence_lookup (sequence, sample, compare, self); + if (match == NULL) { + return; + } + g_sequence_remove (match); +} + +GstMediaSourceSampleMap * +gst_media_source_sample_map_new (void) +{ + return gst_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE_SAMPLE_MAP, + NULL)); +} + +static void +gst_media_source_sample_map_finalize (GObject * object) +{ + GstMediaSourceSampleMap *self = GST_MEDIA_SOURCE_SAMPLE_MAP (object); + g_sequence_free (self->samples_by_dts); + g_sequence_free (self->samples_by_pts); + g_hash_table_unref (self->samples); + G_OBJECT_CLASS (gst_media_source_sample_map_parent_class)->finalize (object); +} + +static void +gst_media_source_sample_map_class_init (GstMediaSourceSampleMapClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_sample_map_finalize); +} + +static void +gst_media_source_sample_map_init (GstMediaSourceSampleMap * self) +{ + self->samples = g_hash_table_new_full (g_direct_hash, g_direct_equal, + (GDestroyNotify) gst_sample_unref, NULL); + self->samples_by_dts = g_sequence_new ((GDestroyNotify) gst_sample_unref); + self->samples_by_pts = g_sequence_new ((GDestroyNotify) gst_sample_unref); +} + +void +gst_media_source_sample_map_add (GstMediaSourceSampleMap * self, + GstSample * sample) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self)); + g_return_if_fail (GST_IS_SAMPLE (sample)); + GstBuffer *buffer = gst_sample_get_buffer (sample); + g_return_if_fail (GST_BUFFER_DTS_IS_VALID (buffer)); + g_return_if_fail (GST_BUFFER_PTS_IS_VALID (buffer)); + g_return_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer)); + + if (g_hash_table_contains (self->samples, sample)) { + return; + } + + g_hash_table_add (self->samples, gst_sample_ref (sample)); + g_sequence_insert_sorted (self->samples_by_dts, gst_sample_ref (sample), + (GCompareDataFunc) compare_dts, self); + g_sequence_insert_sorted (self->samples_by_pts, gst_sample_ref (sample), + (GCompareDataFunc) compare_pts, self); + self->storage_size += gst_buffer_get_size (buffer); + GST_TRACE_OBJECT (self, "new storage size=%" G_GSIZE_FORMAT, + self->storage_size); +} + +void +gst_media_source_sample_map_remove (GstMediaSourceSampleMap * self, + GstSample * sample) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self)); + g_return_if_fail (GST_IS_SAMPLE (sample)); + + if (!g_hash_table_contains (self->samples, sample)) { + return; + } + + gsize buffer_size = sample_buffer_size (sample); + remove_sample (self->samples_by_dts, (GCompareDataFunc) compare_dts, sample, + self); + remove_sample (self->samples_by_pts, (GCompareDataFunc) compare_pts, sample, + self); + g_hash_table_remove (self->samples, sample); + self->storage_size -= MIN (buffer_size, self->storage_size); +} + +gboolean +gst_media_source_sample_map_contains (GstMediaSourceSampleMap * self, + GstSample * sample) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), FALSE); + return g_hash_table_contains (self->samples, sample); +} + +gsize +gst_media_source_sample_map_remove_range (GstMediaSourceSampleMap * self, + GstClockTime earliest, GstClockTime latest) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0); + g_return_val_if_fail (earliest <= latest, 0); + + GSequenceIter *start_by_dts = find_sample_containing_dts (self, earliest); + GSequenceIter *end_by_dts = find_sample_containing_dts (self, latest); + + GstSample *start = g_sequence_get (start_by_dts); + GstSample *end = g_sequence_get (end_by_dts); + + GstClockTime start_time = + start == NULL ? GST_CLOCK_TIME_NONE : sample_dts (start); + GstClockTime end_time = end == NULL ? start_time : sample_dts (end); + + GST_TRACE_OBJECT (self, "remove range [%" GST_TIMEP_FORMAT ",%" + GST_TIMEP_FORMAT ")", &start_time, &end_time); + + GList *to_remove = NULL; + while (g_sequence_iter_compare (start_by_dts, end_by_dts) < 1) { + GstSample *sample = g_sequence_get (start_by_dts); + to_remove = g_list_prepend (to_remove, sample); + start_by_dts = g_sequence_iter_next (start_by_dts); + } + gsize bytes_removed = 0; + for (GList * iter = to_remove; iter != NULL; iter = g_list_next (iter)) { + GstSample *sample = iter->data; + bytes_removed += sample_buffer_size (sample); + gst_media_source_sample_map_remove (self, sample); + } + + g_list_free (to_remove); + + GST_TRACE_OBJECT (self, "removed=%" G_GSIZE_FORMAT "B, latest=%" + GST_TIMEP_FORMAT, bytes_removed, &latest); + return bytes_removed; +} + +gsize +gst_media_source_sample_map_remove_range_from_start (GstMediaSourceSampleMap + * self, GstClockTime latest_dts) +{ + return gst_media_source_sample_map_remove_range (self, 0, latest_dts); +} + +gsize +gst_media_source_sample_map_remove_range_from_end (GstMediaSourceSampleMap + * self, GstClockTime earliest_dts) +{ + return gst_media_source_sample_map_remove_range (self, earliest_dts, + GST_CLOCK_TIME_NONE); +} + +GstClockTime +gst_media_source_sample_map_get_highest_end_time (GstMediaSourceSampleMap * + self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), + GST_CLOCK_TIME_NONE); + GSequenceIter *iter = g_sequence_get_end_iter (self->samples_by_pts); + iter = g_sequence_iter_prev (iter); + if (g_sequence_iter_is_begin (iter)) { + return GST_CLOCK_TIME_NONE; + } + GstSample *sample = g_sequence_get (iter); + GstBuffer *buffer = gst_sample_get_buffer (sample); + g_return_val_if_fail (GST_BUFFER_PTS_IS_VALID (buffer), GST_CLOCK_TIME_NONE); + g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer), + GST_CLOCK_TIME_NONE); + return GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer); +} + +guint +gst_media_source_sample_map_get_size (GstMediaSourceSampleMap * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0); + return g_hash_table_size (self->samples); +} + +gsize +gst_media_source_sample_map_get_storage_size (GstMediaSourceSampleMap * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), 0); + return self->storage_size; +} + +static inline gboolean +sample_contains_dts (GstSample * sample, GstClockTime dts) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer), FALSE); + g_return_val_if_fail (GST_BUFFER_DTS_IS_VALID (buffer), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (dts), FALSE); + GstClockTime end = GST_BUFFER_DTS (buffer) + GST_BUFFER_DURATION (buffer); + return dts <= end; +} + +static inline gboolean +sample_contains_pts (GstSample * sample, GstClockTime pts) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + g_return_val_if_fail (GST_BUFFER_DURATION_IS_VALID (buffer), FALSE); + g_return_val_if_fail (GST_BUFFER_PTS_IS_VALID (buffer), FALSE); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (pts), FALSE); + GstClockTime end = GST_BUFFER_PTS (buffer) + GST_BUFFER_DURATION (buffer); + return pts <= end; +} + +static GSequenceIter * +find_sequentially (GSequence * sequence, gpointer item) +{ + GSequenceIter *iter = g_sequence_get_begin_iter (sequence); + for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { + gpointer current = g_sequence_get (iter); + if (current == item) { + return iter; + } + } + return iter; +} + +static GSequenceIter * +find_sample_containing_dts (GstMediaSourceSampleMap * self, GstClockTime dts) +{ + if (dts == 0) { + return g_sequence_get_begin_iter (self->samples_by_dts); + } + if (dts == GST_CLOCK_TIME_NONE) { + return g_sequence_get_end_iter (self->samples_by_dts); + } + GSequenceIter *iter = g_sequence_get_begin_iter (self->samples_by_dts); + while (!g_sequence_iter_is_end (iter)) { + GstSample *sample = g_sequence_get (iter); + if (sample_contains_dts (sample, dts)) { + return iter; + } + iter = g_sequence_iter_next (iter); + } + return iter; +} + +static GSequenceIter * +find_sample_containing_pts (GstMediaSourceSampleMap * self, GstClockTime pts) +{ + if (pts == 0) { + return g_sequence_get_begin_iter (self->samples_by_pts); + } + if (pts == GST_CLOCK_TIME_NONE) { + return g_sequence_get_end_iter (self->samples_by_pts); + } + GSequenceIter *iter = g_sequence_get_begin_iter (self->samples_by_pts); + while (!g_sequence_iter_is_end (iter)) { + GstSample *sample = g_sequence_get (iter); + if (sample_contains_pts (sample, pts)) { + return iter; + } + iter = g_sequence_iter_next (iter); + } + return iter; +} + +static GSequenceIter * +find_previous_non_delta_unit (GstMediaSourceSampleMap * self, + GSequenceIter * iter) +{ + while (!g_sequence_iter_is_begin (iter)) { + if (!iter_is_delta_unit (iter)) { + GST_TRACE_OBJECT (self, "found valid sample"); + return iter; + } + iter = g_sequence_iter_prev (iter); + } + GST_TRACE_OBJECT (self, "rolled back to the first sample"); + return iter; +} + +static GSequenceIter * +gst_media_source_sample_map_iter_starting_dts (GstMediaSourceSampleMap * self, + GstClockTime start_dts) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_dts), NULL); + GSequenceIter *iter = find_sample_containing_dts (self, start_dts); + return find_previous_non_delta_unit (self, iter); +} + +static GSequenceIter * +gst_media_source_sample_map_iter_starting_pts (GstMediaSourceSampleMap + * self, GstClockTime start_pts) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_SAMPLE_MAP (self), NULL); + g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_pts), NULL); + GSequenceIter *iter = find_sample_containing_pts (self, start_pts); + return find_previous_non_delta_unit (self, iter); +} + +typedef struct _SampleMapIterator SampleMapIterator; + +struct _SampleMapIterator +{ + GstIterator iterator; + GstMediaSourceSampleMap *map; +/* *INDENT-OFF* */ + GstClockTime (*timestamp_func) (GstSample *); + GSequenceIter *(*resync_locator_func) (SampleMapIterator *); +/* *INDENT-ON* */ + + GstClockTime start_time; + GstClockTime current_time; + GSequenceIter *current_iter; + GstSample *current_sample; +}; + +static void +iter_copy (const SampleMapIterator * it, SampleMapIterator * copy) +{ + copy->map = gst_object_ref (it->map); + copy->timestamp_func = it->timestamp_func; + copy->resync_locator_func = it->resync_locator_func; + + copy->current_time = it->current_time; + copy->start_time = it->start_time; + copy->current_iter = it->current_iter; + copy->current_sample = gst_sample_ref (it->current_sample); +} + +static GSequenceIter * +iter_find_resync_point_dts (SampleMapIterator * it) +{ + if (it->current_sample) { + GSequenceIter *iter = + find_sequentially (it->map->samples_by_dts, it->current_sample); + if (!g_sequence_iter_is_end (iter)) { + return g_sequence_iter_next (iter); + } + } + + return gst_media_source_sample_map_iter_starting_dts (it->map, + it->current_time); +} + +static GSequenceIter * +iter_find_resync_point_pts (SampleMapIterator * it) +{ + if (it->current_sample) { + GSequenceIter *iter = + find_sequentially (it->map->samples_by_pts, it->current_sample); + if (!g_sequence_iter_is_end (iter)) { + return g_sequence_iter_next (iter); + } + } + + return gst_media_source_sample_map_iter_starting_pts (it->map, + it->current_time); +} + +static GstIteratorResult +iter_next (SampleMapIterator * it, GValue * result) +{ + + if (g_sequence_iter_is_end (it->current_iter)) { + return GST_ITERATOR_DONE; + } + + GstSample *sample = g_sequence_get (it->current_iter); + gst_clear_sample (&it->current_sample); + it->current_sample = gst_sample_ref (sample); + + it->current_time = it->timestamp_func (sample); + it->current_iter = g_sequence_iter_next (it->current_iter); + + gst_value_set_sample (result, sample); + + return GST_ITERATOR_OK; +} + +static void +iter_resync (SampleMapIterator * it) +{ + GST_TRACE_OBJECT (it->map, "resync"); + it->current_time = it->start_time; + it->current_iter = it->resync_locator_func (it); +} + +static void +iter_free (SampleMapIterator * it) +{ + gst_clear_object (&it->map); + gst_clear_sample (&it->current_sample); +} + +GstIterator * +gst_media_source_sample_map_iter_samples_by_dts (GstMediaSourceSampleMap * map, + GMutex * lock, guint32 * master_cookie, GstClockTime start_dts, + GstSample * start_sample) +{ +/* *INDENT-OFF* */ + SampleMapIterator *it = (SampleMapIterator *) gst_iterator_new ( + sizeof (SampleMapIterator), + GST_TYPE_SAMPLE, + lock, + master_cookie, + (GstIteratorCopyFunction) iter_copy, + (GstIteratorNextFunction) iter_next, + (GstIteratorItemFunction) NULL, + (GstIteratorResyncFunction) iter_resync, + (GstIteratorFreeFunction) iter_free + ); +/* *INDENT-ON* */ + + it->map = gst_object_ref (map); + it->timestamp_func = sample_dts; + it->resync_locator_func = iter_find_resync_point_dts; + it->start_time = start_dts; + it->current_time = start_dts; + it->current_sample = start_sample ? gst_sample_ref (start_sample) : NULL; + it->current_iter = iter_find_resync_point_dts (it); + + return GST_ITERATOR (it); +} + +GstIterator * +gst_media_source_sample_map_iter_samples_by_pts (GstMediaSourceSampleMap * map, + GMutex * lock, guint32 * master_cookie, GstClockTime start_pts, + GstSample * start_sample) +{ +/* *INDENT-OFF* */ + SampleMapIterator *it = (SampleMapIterator *) gst_iterator_new ( + sizeof (SampleMapIterator), + GST_TYPE_SAMPLE, + lock, + master_cookie, + (GstIteratorCopyFunction) iter_copy, + (GstIteratorNextFunction) iter_next, + (GstIteratorItemFunction) NULL, + (GstIteratorResyncFunction) iter_resync, + (GstIteratorFreeFunction) iter_free + ); +/* *INDENT-ON* */ + + it->map = gst_object_ref (map); + it->timestamp_func = sample_pts; + it->resync_locator_func = iter_find_resync_point_pts; + it->start_time = start_pts; + it->current_time = start_pts; + it->current_sample = start_sample ? gst_sample_ref (start_sample) : NULL; + it->current_iter = iter_find_resync_point_pts (it); + + return GST_ITERATOR (it); +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h new file mode 100644 index 0000000000..68c834efbd --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack-private.h @@ -0,0 +1,110 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef enum +{ + GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO, + GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO, + GST_MEDIA_SOURCE_TRACK_TYPE_TEXT, + GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, +} GstMediaSourceTrackType; + +#define GST_TYPE_MEDIA_SOURCE_TRACK (gst_media_source_track_get_type()) + +GST_MSE_PRIVATE +G_DECLARE_FINAL_TYPE (GstMediaSourceTrack, gst_media_source_track, GST, + MEDIA_SOURCE_TRACK, GstObject); + +GST_MSE_PRIVATE +GstMediaSourceTrackType gst_media_source_track_get_track_type ( + GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +const gchar * +gst_media_source_track_get_id (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_get_active (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +GstMediaSourceTrack *gst_media_source_track_new (GstMediaSourceTrackType type, + const gchar * track_id); + +GST_MSE_PRIVATE +GstMediaSourceTrack * +gst_media_source_track_new_with_initial_caps (GstMediaSourceTrackType type, + const gchar * track_id, GstCaps * initial_caps); + +GST_MSE_PRIVATE +GstMediaSourceTrack * gst_media_source_track_new_with_size ( + GstMediaSourceTrackType type, const gchar * track_id, gsize size); + +GST_MSE_PRIVATE +GstCaps *gst_media_source_track_get_initial_caps (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +GstStreamType +gst_media_source_track_get_stream_type (GstMediaSourceTrack * self); + +GST_MSE_API +void gst_media_source_track_set_active (GstMediaSourceTrack * self, + gboolean active); + +GST_MSE_PRIVATE +GstMiniObject *gst_media_source_track_pop (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_push (GstMediaSourceTrack * self, + GstSample * sample); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_try_push (GstMediaSourceTrack * self, + GstSample * sample); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_push_eos (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +void gst_media_source_track_flush (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +void gst_media_source_track_resume (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_is_empty (GstMediaSourceTrack * self); + +GST_MSE_PRIVATE +GstStreamType gst_media_source_track_type_to_stream_type ( + GstMediaSourceTrackType type); + +GST_MSE_PRIVATE +GstMediaSourceTrackType gst_media_source_track_type_from_stream_type ( + GstStreamType type); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c new file mode 100644 index 0000000000..378c5c9cf4 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrack.c @@ -0,0 +1,458 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "gstmediasourcetrack-private.h" + +#include "gstmselogging-private.h" + +#include + +struct _GstMediaSourceTrack +{ + GstObject parent_instance; + + GstMediaSourceTrackType track_type; + gchar *track_id; + gboolean active; + GstCaps *initial_caps; + + gsize queue_size; + GstDataQueue *samples; +}; + +G_DEFINE_TYPE (GstMediaSourceTrack, gst_media_source_track, GST_TYPE_OBJECT); + +#define DEFAULT_QUEUE_SIZE (1 << 10) + +enum +{ + PROP_0, + + PROP_TRACK_TYPE, + PROP_TRACK_ID, + PROP_ACTIVE, + PROP_INITIAL_CAPS, + PROP_QUEUE_SIZE, + + N_PROPS, +}; + +enum +{ + ON_NOT_EMPTY, + + N_SIGNALS, +}; + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; + +static GstMediaSourceTrack * +_gst_media_source_track_new_full (GstMediaSourceTrackType type, + const gchar * track_id, gsize size, GstCaps * initial_caps) +{ + g_return_val_if_fail (type >= 0, NULL); + g_return_val_if_fail (type <= GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, NULL); + + gst_mse_init_logging (); + + GstMediaSourceTrack *self = g_object_new (GST_TYPE_MEDIA_SOURCE_TRACK, + "track-type", type, "track-id", track_id, "queue-size", size, + "initial-caps", initial_caps, NULL); + + return gst_object_ref_sink (self); +} + +GstMediaSourceTrack * +gst_media_source_track_new_with_size (GstMediaSourceTrackType type, + const gchar * track_id, gsize size) +{ + return _gst_media_source_track_new_full (type, track_id, size, NULL); +} + +GstMediaSourceTrack * +gst_media_source_track_new (GstMediaSourceTrackType type, + const gchar * track_id) +{ + return _gst_media_source_track_new_full (type, track_id, DEFAULT_QUEUE_SIZE, + NULL); +} + +GstMediaSourceTrack * +gst_media_source_track_new_with_initial_caps (GstMediaSourceTrackType type, + const gchar * track_id, GstCaps * initial_caps) +{ + g_return_val_if_fail (GST_IS_CAPS (initial_caps), NULL); + return _gst_media_source_track_new_full (type, track_id, DEFAULT_QUEUE_SIZE, + initial_caps); +} + +static void +gst_media_source_track_finalize (GObject * object) +{ + GstMediaSourceTrack *self = (GstMediaSourceTrack *) object; + + g_free (self->track_id); + gst_clear_caps (&self->initial_caps); + g_object_unref (self->samples); + + G_OBJECT_CLASS (gst_media_source_track_parent_class)->finalize (object); +} + +static void +gst_media_source_track_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (object); + + switch (prop_id) { + case PROP_TRACK_TYPE: + g_value_set_enum (value, gst_media_source_track_get_track_type (self)); + break; + case PROP_TRACK_ID: + g_value_set_static_string (value, gst_media_source_track_get_id (self)); + break; + case PROP_ACTIVE: + g_value_set_boolean (value, gst_media_source_track_get_active (self)); + break; + case PROP_INITIAL_CAPS: + g_value_take_boxed (value, + gst_media_source_track_get_initial_caps (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_media_source_track_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (object); + + switch (prop_id) { + case PROP_TRACK_TYPE: + self->track_type = g_value_get_enum (value); + break; + case PROP_TRACK_ID: + self->track_id = g_value_dup_string (value); + break; + case PROP_ACTIVE: + gst_media_source_track_set_active (self, g_value_get_boolean (value)); + break; + case PROP_INITIAL_CAPS:{ + const GstCaps *caps = gst_value_get_caps (value); + self->initial_caps = caps == NULL ? NULL : gst_caps_copy (caps); + break; + } + case PROP_QUEUE_SIZE:{ + self->queue_size = g_value_get_ulong (value); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_media_source_track_class_init (GstMediaSourceTrackClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_track_finalize); + oclass->get_property = + GST_DEBUG_FUNCPTR (gst_media_source_track_get_property); + oclass->set_property = + GST_DEBUG_FUNCPTR (gst_media_source_track_set_property); + + signals[ON_NOT_EMPTY] = g_signal_new ("on-not-empty", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + properties[PROP_TRACK_TYPE] = + g_param_spec_enum ("track-type", "Track Type", + "Type of media in this Track, either Audio, Video, Text, or Other.", + GST_TYPE_MEDIA_SOURCE_TRACK_TYPE, GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + properties[PROP_TRACK_ID] = + g_param_spec_string ("track-id", "Track ID", + "Identifier for this Track that must be unique within a Source Buffer.", + "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", "Active", + "Whether this Track requires its parent Source Buffer to be in its " + "parent Media Source's Active Source Buffers list", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_INITIAL_CAPS] = + g_param_spec_boxed ("initial-caps", "Initial Caps", + "GstCaps discovered in the first Initialization Segment", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_QUEUE_SIZE] = + g_param_spec_ulong ("queue-size", "Queue Size", + "Maximum Track Queue size", + 0, G_MAXULONG, DEFAULT_QUEUE_SIZE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPS, properties); +} + +static gboolean +check_queue_depth (GstDataQueue * queue, guint visible, guint bytes, + guint64 time, gpointer checkdata) +{ + GstMediaSourceTrack *self = GST_MEDIA_SOURCE_TRACK (checkdata); + return visible >= self->queue_size; +} + +static void +gst_media_source_track_init (GstMediaSourceTrack * self) +{ + self->samples = gst_data_queue_new (check_queue_depth, NULL, NULL, self); +} + +GstMediaSourceTrackType +gst_media_source_track_get_track_type (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), + GST_MEDIA_SOURCE_TRACK_TYPE_OTHER); + return self->track_type; +} + +GstStreamType +gst_media_source_track_get_stream_type (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), + GST_STREAM_TYPE_UNKNOWN); + return gst_media_source_track_type_to_stream_type (self->track_type); +} + +const gchar * +gst_media_source_track_get_id (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL); + return self->track_id; +} + +GstCaps * +gst_media_source_track_get_initial_caps (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL); + if (GST_IS_CAPS (self->initial_caps)) { + return self->initial_caps; + } else { + return NULL; + } +} + +gboolean +gst_media_source_track_get_active (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE); + return self->active; +} + +void +gst_media_source_track_set_active (GstMediaSourceTrack * self, gboolean active) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self)); + self->active = active; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]); +} + +GstMiniObject * +gst_media_source_track_pop (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), NULL); + GstDataQueueItem *item = NULL; + if (!gst_data_queue_pop (self->samples, &item)) { + return NULL; + } + GstMiniObject *object = item->object; + item->object = NULL; + item->destroy (item); + return object; +} + +static inline gsize +sample_size (GstSample * sample) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + return gst_buffer_get_size (buffer); +} + +static inline GstClockTime +sample_duration (GstSample * sample) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + return GST_BUFFER_DURATION (buffer); +} + +static void +destroy_item (GstDataQueueItem * item) +{ + gst_clear_mini_object (&item->object); + g_free (item); +} + +static inline GstDataQueueItem * +wrap_sample (GstSample * sample) +{ + GstDataQueueItem item = { + .object = GST_MINI_OBJECT (sample), + .size = sample_size (sample), + .duration = sample_duration (sample), + .destroy = (GDestroyNotify) destroy_item, + .visible = TRUE, + }; + return g_memdup2 (&item, sizeof (GstDataQueueItem)); +} + +gboolean +gst_media_source_track_push (GstMediaSourceTrack * self, GstSample * sample) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE); + g_return_val_if_fail (GST_IS_SAMPLE (sample), FALSE); + + gboolean was_empty = gst_media_source_track_is_empty (self); + + GstDataQueueItem *item = wrap_sample (sample); + + gboolean result = gst_data_queue_push (self->samples, item); + + if (result) { + if (was_empty) { + g_signal_emit (self, signals[ON_NOT_EMPTY], 0); + } + return TRUE; + } else { + item->destroy (item); + return FALSE; + } +} + +static inline GstDataQueueItem * +wrap_eos (void) +{ + GstEvent *event = gst_event_ref (gst_event_new_eos ()); + + GstDataQueueItem item = { + .object = GST_MINI_OBJECT (event), + .size = 0, + .duration = 0, + .destroy = (GDestroyNotify) destroy_item, + .visible = TRUE, + }; + return g_memdup2 (&item, sizeof (GstDataQueueItem)); +} + +gboolean +gst_media_source_track_push_eos (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE); + + GstDataQueueItem *item = wrap_eos (); + + gboolean result = gst_data_queue_push (self->samples, item); + + if (result) { + return TRUE; + } else { + item->destroy (item); + return FALSE; + } +} + +void +gst_media_source_track_flush (GstMediaSourceTrack * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self)); + gst_data_queue_set_flushing (self->samples, TRUE); + gst_data_queue_flush (self->samples); +} + +void +gst_media_source_track_resume (GstMediaSourceTrack * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self)); + gst_data_queue_set_flushing (self->samples, FALSE); +} + +gboolean +gst_media_source_track_try_push (GstMediaSourceTrack * self, GstSample * sample) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE); + g_return_val_if_fail (GST_IS_SAMPLE (sample), FALSE); + + if (gst_data_queue_is_full (self->samples)) { + return FALSE; + } + + return gst_media_source_track_push (self, sample); +} + +gboolean +gst_media_source_track_is_empty (GstMediaSourceTrack * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK (self), FALSE); + return gst_data_queue_is_empty (self->samples); +} + +GstStreamType +gst_media_source_track_type_to_stream_type (GstMediaSourceTrackType type) +{ + switch (type) { + case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO: + return GST_STREAM_TYPE_AUDIO; + case GST_MEDIA_SOURCE_TRACK_TYPE_TEXT: + return GST_STREAM_TYPE_TEXT; + case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO: + return GST_STREAM_TYPE_VIDEO; + default: + return GST_STREAM_TYPE_UNKNOWN; + } +} + +GstMediaSourceTrackType +gst_media_source_track_type_from_stream_type (GstStreamType type) +{ + switch (type) { + case GST_STREAM_TYPE_AUDIO: + return GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO; + case GST_STREAM_TYPE_TEXT: + return GST_MEDIA_SOURCE_TRACK_TYPE_TEXT; + case GST_STREAM_TYPE_VIDEO: + return GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO; + default: + return GST_MEDIA_SOURCE_TRACK_TYPE_OTHER; + } +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h new file mode 100644 index 0000000000..8abfa77b61 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer-private.h @@ -0,0 +1,100 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_MEDIA_SOURCE_TRACK_BUFFER \ + (gst_media_source_track_buffer_get_type()) + +GST_MSE_PRIVATE +G_DECLARE_FINAL_TYPE (GstMediaSourceTrackBuffer, gst_media_source_track_buffer, + GST, MEDIA_SOURCE_TRACK_BUFFER, GstObject); + +GST_MSE_PRIVATE +GstMediaSourceTrackBuffer *gst_media_source_track_buffer_new (void); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_buffer_is_empty ( + GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +gint gst_media_source_track_buffer_get_size (GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +GstClockTime gst_media_source_track_buffer_get_highest_end_time ( + GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +GArray * gst_media_source_track_buffer_get_ranges ( + GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_set_group_start (GstMediaSourceTrackBuffer + * self, GstClockTime group_start); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_process_init_segment ( + GstMediaSourceTrackBuffer * self, gboolean sequence_mode); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_add (GstMediaSourceTrackBuffer * self, + GstSample * sample); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_remove (GstMediaSourceTrackBuffer * self, + GstSample * sample); + +GST_MSE_PRIVATE +gsize +gst_media_source_track_buffer_remove_range (GstMediaSourceTrackBuffer * self, + GstClockTime start, GstClockTime end); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_clear (GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +void gst_media_source_track_buffer_eos (GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_buffer_is_eos ( + GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_track_buffer_await_eos_until ( + GstMediaSourceTrackBuffer * self, gint64 deadline); + +GST_MSE_PRIVATE +gsize gst_media_source_track_buffer_get_storage_size ( + GstMediaSourceTrackBuffer * self); + +GST_MSE_PRIVATE +GstIterator * gst_media_source_track_buffer_iter_samples ( + GstMediaSourceTrackBuffer * buffer, GstClockTime start_dts, + GstSample * start_sample); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c new file mode 100644 index 0000000000..a73bdc0901 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmediasourcetrackbuffer.c @@ -0,0 +1,377 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstmediasourcetrackbuffer-private.h" +#include "gstmediasourcesamplemap-private.h" +#include "gstmediasource.h" +#include "gstmselogging-private.h" + +typedef struct +{ + gboolean enabled; + GstClockTime group_start; + GstClockTime group_end; + GstClockTimeDiff offset; + + GstClockTime last_dts; + GstClockTime last_duration; +} Timestamps; + +struct _GstMediaSourceTrackBuffer +{ + GstObject parent_instance; + + GstMediaSourceSampleMap *samples; + Timestamps timestamps; + guint eos; + + guint32 master_cookie; + + GCond new_data_cond; + GMutex new_data_mutex; +}; + +#define g_array_new_ranges() \ + (g_array_new (TRUE, FALSE, sizeof (GstMediaSourceRange))) + +#define NEW_DATA_LOCK(a) (g_mutex_lock (&a->new_data_mutex)) +#define NEW_DATA_UNLOCK(a) (g_mutex_unlock (&a->new_data_mutex)) +#define NEW_DATA_SIGNAL(a) (g_cond_signal (&a->new_data_cond)) +#define NEW_DATA_WAIT(a) (g_cond_wait (&a->new_data_cond, &a->new_data_mutex)) +#define NEW_DATA_WAIT_UNTIL(a, d) \ + (g_cond_wait_until (&a->new_data_cond, &a->new_data_mutex, d)) + +static void timestamps_init (Timestamps * self, gboolean enabled); +static void timestamps_process (Timestamps * self, GstSample * sample); + +G_DEFINE_TYPE (GstMediaSourceTrackBuffer, gst_media_source_track_buffer, + GST_TYPE_OBJECT); + +static void +invalidate_cookie (GstMediaSourceTrackBuffer * self) +{ + self->master_cookie++; +} + +GstMediaSourceTrackBuffer * +gst_media_source_track_buffer_new (void) +{ + return gst_object_ref_sink (g_object_new (GST_TYPE_MEDIA_SOURCE_TRACK_BUFFER, + NULL)); +} + +static void +gst_media_source_track_buffer_finalize (GObject * object) +{ + GstMediaSourceTrackBuffer *self = GST_MEDIA_SOURCE_TRACK_BUFFER (object); + gst_object_unref (self->samples); + g_cond_clear (&self->new_data_cond); + g_mutex_clear (&self->new_data_mutex); + G_OBJECT_CLASS (gst_media_source_track_buffer_parent_class)->finalize + (object); +} + +static void +gst_media_source_track_buffer_class_init (GstMediaSourceTrackBufferClass * + klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = GST_DEBUG_FUNCPTR (gst_media_source_track_buffer_finalize); +} + +static void +gst_media_source_track_buffer_init (GstMediaSourceTrackBuffer * self) +{ + self->samples = gst_media_source_sample_map_new (); + self->eos = FALSE; + self->master_cookie = 0; + timestamps_init (&self->timestamps, FALSE); + g_cond_init (&self->new_data_cond); + g_mutex_init (&self->new_data_mutex); +} + +void +gst_media_source_track_buffer_process_init_segment (GstMediaSourceTrackBuffer + * self, gboolean sequence_mode) +{ + NEW_DATA_LOCK (self); + + timestamps_init (&self->timestamps, sequence_mode); + + NEW_DATA_UNLOCK (self); +} + +void +gst_media_source_track_buffer_set_group_start (GstMediaSourceTrackBuffer + * self, GstClockTime group_start) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self)); + if (self->timestamps.enabled) { + self->timestamps.group_start = group_start; + } +} + +void +gst_media_source_track_buffer_add (GstMediaSourceTrackBuffer * self, + GstSample * sample) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self)); + g_return_if_fail (GST_IS_SAMPLE (sample)); + + NEW_DATA_LOCK (self); + + timestamps_process (&self->timestamps, sample); + gst_media_source_sample_map_add (self->samples, sample); + invalidate_cookie (self); + + NEW_DATA_SIGNAL (self); + NEW_DATA_UNLOCK (self); +} + +void +gst_media_source_track_buffer_remove (GstMediaSourceTrackBuffer * self, + GstSample * sample) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self)); + g_return_if_fail (GST_IS_SAMPLE (sample)); + + NEW_DATA_LOCK (self); + + gst_media_source_sample_map_remove (self->samples, sample); + invalidate_cookie (self); + + NEW_DATA_SIGNAL (self); + NEW_DATA_UNLOCK (self); +} + +gsize +gst_media_source_track_buffer_remove_range (GstMediaSourceTrackBuffer * self, + GstClockTime earliest, GstClockTime latest) +{ + NEW_DATA_LOCK (self); + gsize size = gst_media_source_sample_map_remove_range (self->samples, + earliest, latest); + invalidate_cookie (self); + NEW_DATA_SIGNAL (self); + NEW_DATA_UNLOCK (self); + return size; +} + +void +gst_media_source_track_buffer_clear (GstMediaSourceTrackBuffer * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self)); + + NEW_DATA_LOCK (self); + + g_set_object (&self->samples, gst_media_source_sample_map_new ()); + + NEW_DATA_SIGNAL (self); + NEW_DATA_UNLOCK (self); +} + +static gboolean +is_eos (GstMediaSourceTrackBuffer * self) +{ + return g_atomic_int_get (&self->eos); +} + +void +gst_media_source_track_buffer_eos (GstMediaSourceTrackBuffer * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self)); + NEW_DATA_LOCK (self); + g_atomic_int_set (&self->eos, TRUE); + NEW_DATA_SIGNAL (self); + NEW_DATA_UNLOCK (self); +} + +gboolean +gst_media_source_track_buffer_is_eos (GstMediaSourceTrackBuffer * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), FALSE); + return is_eos (self); +} + +gboolean +gst_media_source_track_buffer_await_eos_until (GstMediaSourceTrackBuffer * self, + gint64 deadline) +{ + NEW_DATA_LOCK (self); + while (!is_eos (self) && NEW_DATA_WAIT_UNTIL (self, deadline)) { + /* wait */ + } + NEW_DATA_UNLOCK (self); + return is_eos (self); +} + +gint +gst_media_source_track_buffer_get_size (GstMediaSourceTrackBuffer * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), 0); + return gst_media_source_sample_map_get_size (self->samples); +} + +GstClockTime +gst_media_source_track_buffer_get_highest_end_time (GstMediaSourceTrackBuffer + * self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), + GST_CLOCK_TIME_NONE); + return gst_media_source_sample_map_get_highest_end_time (self->samples); +} + +typedef struct +{ + GArray *ranges; + GstMediaSourceRange current_range; +} GetRangesAccumulator; + +static gboolean +get_ranges_fold (const GValue * item, GetRangesAccumulator * acc, + gpointer user_data) +{ + GstSample *sample = gst_value_get_sample (item); + GstBuffer *buffer = gst_sample_get_buffer (sample); + GstClockTime start = GST_BUFFER_PTS (buffer); + GstClockTime end = start + GST_BUFFER_DURATION (buffer); + + GstMediaSourceRange *range = &acc->current_range; + + if (range->end == 0 || start <= (range->end + (GST_SECOND / 100))) { + range->end = end; + return TRUE; + } + g_array_append_val (acc->ranges, *range); + + range->start = start; + range->end = end; + + return TRUE; +} + +GArray * +gst_media_source_track_buffer_get_ranges (GstMediaSourceTrackBuffer * self) +{ + GetRangesAccumulator acc = { + .ranges = g_array_new_ranges (), + .current_range = {.start = 0,.end = 0}, + }; + + /* *INDENT-OFF* */ + GstIterator *iter = gst_media_source_sample_map_iter_samples_by_pts ( + self->samples, + &self->new_data_mutex, + &self->master_cookie, + 0, + NULL + ); + /* *INDENT-ON* */ + while (gst_iterator_fold (iter, (GstIteratorFoldFunction) get_ranges_fold, + (GValue *) & acc, NULL) == GST_ITERATOR_RESYNC) { + gst_iterator_resync (iter); + } + gst_iterator_free (iter); + + if (acc.current_range.end > 0) { + g_array_append_val (acc.ranges, acc.current_range); + } + + return acc.ranges; +} + +static void +timestamps_init (Timestamps * self, gboolean enabled) +{ + self->enabled = enabled; + self->group_start = GST_CLOCK_TIME_NONE; + self->group_end = GST_CLOCK_TIME_NONE; + self->offset = 0; + self->last_dts = 0; + self->last_duration = 0; +} + +static void +timestamps_process (Timestamps * self, GstSample * sample) +{ + if (!self->enabled) { + return; + } + + GstBuffer *buffer = gst_sample_get_buffer (sample); + GstClockTime duration = GST_BUFFER_DURATION (buffer); + + GstClockTime pts = 0; + GstClockTime dts = 0; + + if (GST_CLOCK_TIME_IS_VALID (self->group_start)) { + self->offset = self->group_start - pts; + self->group_end = self->group_start; + self->group_start = GST_CLOCK_TIME_NONE; + } + + if (self->offset != 0) { + pts += self->offset; + dts += self->offset; + } + + GstClockTime end_pts = pts + duration; + + self->last_dts = dts; + self->last_duration = duration; + + if (GST_CLOCK_TIME_IS_VALID (self->group_end)) { + self->group_end = MAX (self->group_end, end_pts); + } + self->offset = end_pts; + + GST_BUFFER_PTS (buffer) = pts; + GST_BUFFER_DTS (buffer) = dts; +} + +gsize +gst_media_source_track_buffer_get_storage_size (GstMediaSourceTrackBuffer * + self) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE_TRACK_BUFFER (self), 0); + return gst_media_source_sample_map_get_storage_size (self->samples); +} + +GstIterator * +gst_media_source_track_buffer_iter_samples (GstMediaSourceTrackBuffer * self, + GstClockTime start_dts, GstSample * start_sample) +{ + /* *INDENT-OFF* */ + return gst_media_source_sample_map_iter_samples_by_dts ( + self->samples, + &self->new_data_mutex, + &self->master_cookie, + start_dts, + start_sample + ); + /* *INDENT-ON* */ +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h new file mode 100644 index 0000000000..9b0c9bbb23 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue-private.h @@ -0,0 +1,46 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +typedef void (*GstMseEventQueueCallback) (GstDataQueueItem *, gpointer); + +#define GST_TYPE_MSE_EVENT_QUEUE (gst_mse_event_queue_get_type()) + +G_DECLARE_FINAL_TYPE (GstMseEventQueue, gst_mse_event_queue, GST, + MSE_EVENT_QUEUE, GstObject); + +GST_MSE_PRIVATE +GstMseEventQueue * gst_mse_event_queue_new (GstMseEventQueueCallback callback, + gpointer user_data); + +GST_MSE_PRIVATE +gboolean gst_mse_event_queue_push (GstMseEventQueue * self, + GstDataQueueItem * item); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c new file mode 100644 index 0000000000..b49e09c793 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmseeventqueue.c @@ -0,0 +1,133 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstmseeventqueue-private.h" + +struct _GstMseEventQueue +{ + GstObject parent_instance; + + GstMseEventQueueCallback callback; + gpointer user_data; + + GstTask *task; + GRecMutex lock; + GstDataQueue *queue; +}; + +G_DEFINE_TYPE (GstMseEventQueue, gst_mse_event_queue, GST_TYPE_OBJECT); + +GstMseEventQueue * +gst_mse_event_queue_new (GstMseEventQueueCallback callback, gpointer user_data) +{ + g_return_val_if_fail (callback != NULL, NULL); + + GstMseEventQueue *self = g_object_new (GST_TYPE_MSE_EVENT_QUEUE, NULL); + + self->callback = callback; + self->user_data = user_data; + + gst_task_start (self->task); + + return g_object_ref_sink (self); +} + +static void +gst_mse_background_event_queue_dispose (GObject * obj) +{ + GstMseEventQueue *self = GST_MSE_EVENT_QUEUE (obj); + + gst_data_queue_set_flushing (self->queue, TRUE); + gst_data_queue_flush (self->queue); + + G_OBJECT_CLASS (gst_mse_event_queue_parent_class)->dispose (obj); +} + +static void +gst_mse_background_event_queue_finalize (GObject * obj) +{ + GstMseEventQueue *self = GST_MSE_EVENT_QUEUE (obj); + + gst_task_join (self->task); + g_rec_mutex_clear (&self->lock); + gst_clear_object (&self->task); + gst_clear_object (&self->queue); + + G_OBJECT_CLASS (gst_mse_event_queue_parent_class)->finalize (obj); +} + +void +gst_mse_event_queue_class_init (GstMseEventQueueClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = GST_DEBUG_FUNCPTR (gst_mse_background_event_queue_dispose); + oclass->finalize = + GST_DEBUG_FUNCPTR (gst_mse_background_event_queue_finalize); +} + +static void +task_func (GstMseEventQueue * self) +{ + GstTask *task = self->task; + GstDataQueue *queue = self->queue; + GstDataQueueItem *item = NULL; + if (!gst_data_queue_pop (queue, &item)) { + gst_task_stop (task); + return; + } + self->callback (item, self->user_data); + if (item->destroy) { + item->destroy (item); + } +} + +static gboolean +never_full (GstDataQueue * queue, guint visible, guint bytes, guint64 time, + gpointer user_data) +{ + return FALSE; +} + +void +gst_mse_event_queue_init (GstMseEventQueue * self) +{ + self->queue = gst_data_queue_new (never_full, NULL, NULL, NULL); + self->task = gst_task_new ((GstTaskFunction) task_func, self, NULL); + g_rec_mutex_init (&self->lock); + gst_task_set_lock (self->task, &self->lock); +} + +gboolean +gst_mse_event_queue_push (GstMseEventQueue * self, GstDataQueueItem * item) +{ + g_return_val_if_fail (GST_IS_MSE_EVENT_QUEUE (self), FALSE); + g_return_val_if_fail (item != NULL, FALSE); + return gst_data_queue_push (self->queue, item); +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h new file mode 100644 index 0000000000..160d1a3606 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging-private.h @@ -0,0 +1,33 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#define GST_CAT_DEFAULT gst_mse_debug + +G_GNUC_INTERNAL +GST_DEBUG_CATEGORY_EXTERN (gst_mse_debug); + +G_GNUC_INTERNAL +void gst_mse_init_logging (void); diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c new file mode 100644 index 0000000000..0664b8a553 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmselogging.c @@ -0,0 +1,35 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstmselogging-private.h" + +GST_DEBUG_CATEGORY (gst_mse_debug); + +void +gst_mse_init_logging (void) +{ + GST_DEBUG_CATEGORY_INIT (gst_mse_debug, "gst-mse", 0, "GstMse"); +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h new file mode 100644 index 0000000000..dfdb537f35 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype-private.h @@ -0,0 +1,60 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct +{ + gchar *mime_type; + GStrv codecs; +} GstMediaSourceMediaType; + +#define GST_MEDIA_SOURCE_MEDIA_TYPE_INIT { 0 } + +GST_MSE_PRIVATE +gboolean gst_media_source_media_type_parse (GstMediaSourceMediaType * self, + const gchar * type); + +GST_MSE_PRIVATE +gboolean gst_media_source_media_type_is_caps_supported (const GstCaps * caps); + +GST_MSE_PRIVATE +gboolean gst_media_source_media_type_is_supported ( + GstMediaSourceMediaType * self); + +GST_MSE_PRIVATE +gboolean gst_media_source_media_type_generates_timestamp ( + GstMediaSourceMediaType * self); + +GST_MSE_PRIVATE +void gst_media_source_media_type_reset (GstMediaSourceMediaType * self); + +GST_MSE_PRIVATE +void gst_media_source_media_type_free (GstMediaSourceMediaType * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c new file mode 100644 index 0000000000..85c19fa7ab --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsemediatype.c @@ -0,0 +1,544 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2019 Igalia S.L. + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstmsemediatype-private.h" + +static const gchar tspecials[] = { + '(', ')', '<', '>', '@', ',', ';', ':', + '\\', '"', '/', '[', ']', '?', '=', + 0, +}; + +static gboolean +is_tspecial (const gchar c) +{ + for (const gchar * tspecial = tspecials; *tspecial != 0; tspecial++) { + if (*tspecial == c) { + return TRUE; + } + } + return FALSE; +} + +static gboolean +is_token_character (const gchar c) +{ + if (g_ascii_iscntrl (c) || is_tspecial (c)) { + return FALSE; + } + return g_ascii_isgraph (c); +} + +static gboolean +is_ascii (const gchar c) +{ + return c > 0 && c <= G_MAXINT8; +} + +static gboolean +is_eos (const gchar c) +{ + return c == '\0'; +} + +static void +ignore_whitespace (const gchar ** input) +{ + g_return_if_fail (input != NULL); + + const gchar *output; + for (output = *input; g_ascii_isspace (*output); output++) { + } + *input = output; +} + +static gboolean +any_of (const gchar ** input, const gchar ** choices, gchar ** value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (choices != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + for (const gchar ** choice = choices; *choice != NULL; choice++) { + gulong prefix_length = strlen (*choice); + if (g_str_has_prefix (*input, *choice)) { + *value = g_strndup (*input, prefix_length); + *input += prefix_length; + return TRUE; + } + } + return FALSE; +} + +static gboolean +literal (const gchar ** input, const gchar * value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (!g_str_has_prefix (*input, value)) { + return FALSE; + } + *input += strlen (value); + return TRUE; +} + +static gboolean +token (const gchar ** input, gchar ** value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + const gchar *unparsed = *input; + for (; is_token_character (unparsed[0]); unparsed++) { + } + gsize length = unparsed - *input; + if (length < 1) { + return FALSE; + } + *value = g_strndup (*input, length); + *input = unparsed; + return TRUE; +} + +static gboolean +quoted_string_char (const gchar ** input, gchar * value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + const gchar *unparsed = *input; + char c = unparsed[0]; + if (!is_ascii (c)) { + return FALSE; + } + + if (c == '"' || c == '\\' || c == '\r') { + return FALSE; + } + + *value = c; + (*input)++; + return TRUE; +} + +static gboolean +escaped_ascii_char (const gchar ** input, gchar * value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (!literal (input, "\\")) { + return FALSE; + } + + gchar c = (*input)[0]; + if (!is_ascii (c)) { + return FALSE; + } + + *value = c; + (*input)++; + return TRUE; +} + +static gboolean +quoted_string (const gchar ** input, gchar ** value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + const gchar *unparsed = *input; + if (!literal (&unparsed, "\"")) { + return FALSE; + } + + GString *string_value = g_string_new (NULL); + for (;;) { + if (literal (&unparsed, "\"")) { + *input = unparsed; + *value = g_string_free (string_value, FALSE); + return TRUE; + } + + if (is_eos (unparsed[0])) { + goto error; + } + + gchar c; + + if (quoted_string_char (&unparsed, &c)) { + g_string_append_c (string_value, c); + continue; + } + + if (escaped_ascii_char (&unparsed, &c)) { + g_string_append_c (string_value, c); + continue; + } + + goto error; + } +error: + g_string_free (string_value, TRUE); + return FALSE; +} + +static const gchar *discrete_media_types[] = { + "text", + "image", + "audio", + "video", + "application", + NULL, +}; + +static gboolean +discrete_media_type (const gchar ** input, gchar ** value) +{ + return any_of (input, discrete_media_types, value); +} + +static gboolean +composite_media_type (const gchar ** input, gchar ** value) +{ + return token (input, value); +} + +static gboolean +media_type_parameter (const gchar ** input, gchar ** param_name, + gchar ** param_value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (param_name != NULL, FALSE); + g_return_val_if_fail (param_value != NULL, FALSE); + + ignore_whitespace (input); + if (!token (input, param_name)) { + return FALSE; + } + if (!literal (input, "=")) { + return FALSE; + } + if (!token (input, param_value)) { + if (!quoted_string (input, param_value)) { + return FALSE; + } + } + ignore_whitespace (input); + return TRUE; +} + +static gboolean +media_type (const gchar ** input, gchar ** value) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + gchar *discrete = NULL; + gchar *composite = NULL; + + if (!discrete_media_type (input, &discrete)) { + goto error; + } + if (!literal (input, "/")) { + goto error; + } + if (!composite_media_type (input, &composite)) { + goto error; + } + + *value = g_strdup_printf ("%s/%s", discrete, composite); + g_clear_pointer (&discrete, g_free); + g_clear_pointer (&composite, g_free); + return TRUE; + +error: + g_clear_pointer (&discrete, g_free); + g_clear_pointer (&composite, g_free); + return FALSE; +} + +static gboolean +media_type_codecs (const gchar ** input, gchar *** codecs) +{ + g_return_val_if_fail (input != NULL, FALSE); + g_return_val_if_fail (codecs != NULL, FALSE); + + gchar *param_name = NULL; + gchar *codecs_value = NULL; + while (!is_eos (*input[0]) + && media_type_parameter (input, ¶m_name, &codecs_value)) { + gboolean is_codecs = g_strcmp0 (param_name, "codecs") == 0; + g_clear_pointer (¶m_name, g_free); + if (is_codecs) { + break; + } + g_clear_pointer (&codecs_value, g_free); + } + if (codecs_value == NULL) { + return TRUE; + } + + gchar **codec_names = g_strsplit (codecs_value, ",", 0); + guint n_codecs = g_strv_length (codec_names); + GPtrArray *codecs_array = g_ptr_array_new_full (n_codecs, NULL); + for (guint i = 0; i < n_codecs; i++) { + gchar *codec = g_strstrip (g_strdup (codec_names[i])); + if (g_strcmp0 (codec, "") != 0) { + g_ptr_array_add (codecs_array, g_strdup (codec)); + } + g_clear_pointer (&codec, g_free); + } + g_ptr_array_add (codecs_array, NULL); + *codecs = (gchar **) g_ptr_array_free (codecs_array, FALSE); + + g_clear_pointer (&codecs_value, g_free); + g_strfreev (codec_names); + + return TRUE; +} + +gboolean +gst_media_source_media_type_parse (GstMediaSourceMediaType * self, + const gchar * type) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (type != NULL, FALSE); + + gchar *lowercase = g_ascii_strdown (type, -1); + const gchar *input = lowercase; + + ignore_whitespace (&input); + if (!media_type (&input, &self->mime_type)) { + g_free (lowercase); + return FALSE; + } + self->codecs = NULL; + ignore_whitespace (&input); + if (is_eos (input[0])) { + g_free (lowercase); + return TRUE; + } + if (!literal (&input, ";")) { + g_free (lowercase); + return TRUE; + } + ignore_whitespace (&input); + media_type_codecs (&input, &self->codecs); + g_free (lowercase); + return TRUE; +} + +static inline gboolean +has_any_prefix (const gchar * str, const gchar * prefix, ...) +{ + if (g_str_has_prefix (str, prefix)) { + return TRUE; + } + + va_list varargs; + + va_start (varargs, prefix); + for (const gchar * arg = va_arg (varargs, const gchar *); arg != NULL; + arg = va_arg (varargs, const gchar *)) + { + if (g_str_has_prefix (str, arg)) { + va_end (varargs); + return TRUE; + } + } + va_end (varargs); + return FALSE; +} + +static const gchar * +patch_media_type (const gchar * media_type) +{ + if (g_strcmp0 (media_type, "video/mp4") == 0) { + return "video/quicktime"; + } + if (g_strcmp0 (media_type, "audio/mp4") == 0 || + g_strcmp0 (media_type, "audio/aac") == 0) { + return "audio/x-m4a"; + } + if (!has_any_prefix (media_type, "audio/", "video/", NULL)) { + return NULL; + } + return media_type; +} + +static GstStaticCaps h264_caps = GST_STATIC_CAPS ("video/x-h264"); +static GstStaticCaps h265_caps = GST_STATIC_CAPS ("video/x-h265"); +static GstStaticCaps av1_caps = GST_STATIC_CAPS ("video/x-av1"); +static GstStaticCaps vp8_caps = GST_STATIC_CAPS ("video/x-vp8"); +static GstStaticCaps vp9_caps = GST_STATIC_CAPS ("video/x-vp9"); + +static GstStaticCaps vorbis_caps = GST_STATIC_CAPS ("audio/x-vorbis"); +static GstStaticCaps opus_caps = GST_STATIC_CAPS ("audio/x-opus"); +static GstStaticCaps flac_caps = GST_STATIC_CAPS ("audio/x-flac"); +static GstStaticCaps mp3_caps = +GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)1, layer=(int)3"); +static GstStaticCaps aac_caps = +GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)4"); + +static inline GstStaticCaps * +mse_codec_id_to_static_caps (const gchar * codec_id) +{ + if (has_any_prefix (codec_id, "avc", "x-h264", "mp4v", NULL)) { + return &h264_caps; + } + if (has_any_prefix (codec_id, "hvc1", "hev1", "x-h265", NULL)) { + return &h265_caps; + } + if (has_any_prefix (codec_id, "av01", "av1", "x-av1", NULL)) { + return &av1_caps; + } + if (has_any_prefix (codec_id, "vp8", "x-vp8", NULL)) { + return &vp8_caps; + } + if (has_any_prefix (codec_id, "vp9", "vp09", "x-vp9", NULL)) { + return &vp9_caps; + } + + if (has_any_prefix (codec_id, "mpeg", "mp4a", NULL)) { + return &aac_caps; + } + if (has_any_prefix (codec_id, "vorbis", "x-vorbis", NULL)) { + return &vorbis_caps; + } + if (has_any_prefix (codec_id, "opus", "x-opus", NULL)) { + return &opus_caps; + } + if (has_any_prefix (codec_id, "flac", "x-flac", NULL)) { + return &flac_caps; + } + if (has_any_prefix (codec_id, "mp3", "audio/mp3", NULL)) { + return &mp3_caps; + } + + return NULL; +} + +static gboolean +is_supported (const GstCaps * caps, GList * elements) +{ + GList *supporting_elements = gst_element_factory_list_filter (elements, caps, + GST_PAD_SINK, FALSE); + gboolean supported = supporting_elements != NULL; + gst_plugin_feature_list_free (supporting_elements); + return supported; +} + +static gboolean +is_codec_supported (const gchar * codec_id, GList * elements) +{ + GstStaticCaps *static_caps = mse_codec_id_to_static_caps (codec_id); + if (static_caps == NULL) { + return FALSE; + } + GstCaps *codec_caps = gst_static_caps_get (static_caps); + gboolean supported = is_supported (codec_caps, elements); + gst_clear_caps (&codec_caps); + return supported; +} + +static inline GList * +get_srcpad_elements (void) +{ + return gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DEMUXER + | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_DECRYPTOR | + GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_SINK | + GST_ELEMENT_FACTORY_TYPE_PARSER, GST_RANK_MARGINAL); +} + +gboolean +gst_media_source_media_type_is_caps_supported (const GstCaps * caps) +{ + GList *elements = get_srcpad_elements (); + gboolean supported = is_supported (caps, elements); + gst_plugin_feature_list_free (elements); + return supported; +} + +gboolean +gst_media_source_media_type_is_supported (GstMediaSourceMediaType * self) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (self->mime_type != NULL, FALSE); + + const gchar *mime_type = patch_media_type (self->mime_type); + if (mime_type == NULL) { + return FALSE; + } + + GList *elements = get_srcpad_elements (); + GstCaps *caps = gst_caps_from_string (mime_type); + + gboolean supported = is_supported (caps, elements); + for (gchar ** codec_id = self->codecs; + supported && codec_id && *codec_id; codec_id++) { + supported &= is_codec_supported (*codec_id, elements); + } + + gst_plugin_feature_list_free (elements); + gst_clear_caps (&caps); + + return supported; +} + +static const gchar *generate_timestamps_formats[] = { + "audio/mpeg", + "audio/aac", + NULL, +}; + +gboolean +gst_media_source_media_type_generates_timestamp (GstMediaSourceMediaType * self) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (self->mime_type != NULL, FALSE); + return g_strv_contains (generate_timestamps_formats, self->mime_type); +} + +void +gst_media_source_media_type_reset (GstMediaSourceMediaType * self) +{ + g_return_if_fail (self != NULL); + g_clear_pointer (&self->mime_type, g_free); + g_clear_pointer (&self->codecs, g_strfreev); +} + +void +gst_media_source_media_type_free (GstMediaSourceMediaType * self) +{ + gst_media_source_media_type_reset (self); + g_free (self); +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h new file mode 100644 index 0000000000..b40b0a6553 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc-private.h @@ -0,0 +1,55 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include "gstmsesrc.h" +#include "gstmediasource.h" +#include "gstmediasourcetrack-private.h" + +G_BEGIN_DECLS + +GST_MSE_PRIVATE +void gst_mse_src_set_duration (GstMseSrc * self, GstClockTime duration); + +GST_MSE_PRIVATE +void gst_mse_src_network_error (GstMseSrc * self); + +GST_MSE_PRIVATE +void gst_mse_src_decode_error (GstMseSrc * self); + +GST_MSE_PRIVATE +void gst_mse_src_emit_streams (GstMseSrc * self, GstMediaSourceTrack ** tracks, + gsize n_tracks); + +GST_MSE_PRIVATE +void gst_mse_src_update_ready_state (GstMseSrc * self); + +GST_MSE_PRIVATE +void gst_mse_src_attach (GstMseSrc * self, GstMediaSource * media_source); + +GST_MSE_PRIVATE +void gst_mse_src_detach (GstMseSrc * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c new file mode 100644 index 0000000000..96e07062ca --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.c @@ -0,0 +1,1385 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2009, 2010 Sebastian Dröge + * Copyright (C) 2013, 2022, 2023 Collabora Ltd. + * Copyright (C) 2013 Orange + * Copyright (C) 2014, 2015 Sebastian Dröge + * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Igalia, S.L + * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Metrological Group B.V. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstmsesrc + * @title: GstMseSrc + * @short_description: Source Element for Media Source playback + * + * #GstMseSrc is a source Element that interacts with a #GstMediaSource to + * consume #GstSamples processed by the Media Source and supplies them + * to the containing #GstPipeline. In the perspective of the Media Source API, + * this element fulfills the basis of the Media Element's role relating to + * working with a Media Source. The remaining responsibilities are meant to be + * fulfilled by the application and #GstPlay can be used to satisfy many of + * them. + * + * Once added to a Pipeline, this element should be attached to a Media Source + * using gst_media_source_attach(). + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include "gstmsesrc.h" +#include "gstmsesrc-private.h" + +#include "gstmselogging-private.h" + +#include "gstmediasource.h" +#include "gstmediasource-private.h" +#include "gstmediasourcetrack-private.h" +#include "gstsourcebuffer.h" +#include "gstsourcebuffer-private.h" + +#define DEFAULT_POSITION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE +#define DEFAULT_READY_STATE GST_MSE_SRC_READY_STATE_HAVE_NOTHING +#define DECODE_ERROR "decode error" +#define NETWORK_ERROR "network error" + +enum +{ + PROP_0, + + PROP_POSITION, + PROP_DURATION, + PROP_READY_STATE, + + PROP_N_AUDIO, + PROP_N_TEXT, + PROP_N_VIDEO, + + N_PROPS, +}; + +enum +{ + THRESHOLD_FUTURE_DATA = GST_SECOND * 5, + THRESHOLD_ENOUGH_DATA = GST_SECOND * 50, +}; + +static GParamSpec *properties[N_PROPS]; + +static GstStaticPadTemplate gst_mse_src_template = +GST_STATIC_PAD_TEMPLATE ("src_%s", GST_PAD_SRC, GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +/** + * GstMseSrcPad: + * + * Since: 1.24 + */ +struct _GstMseSrcPad +{ + GstPad base; + + GstStream *stream; + GstMediaSourceTrack *track; + GstCaps *most_recent_caps; + GstSegment segment; + + GstClockTime position; + + gboolean sent_stream_collection; + gboolean sent_stream_start; + gboolean sent_initial_caps; + gboolean does_need_segment; + + GCond linked_or_flushing_cond; + GMutex linked_or_flushing_lock; + gboolean flushing; + gboolean eos; +}; + +#define STREAMS_LOCK(a) (g_mutex_lock (&a->streams_lock)) +#define STREAMS_UNLOCK(a) (g_mutex_unlock (&a->streams_lock)) + +#define LINKED_OR_FLUSHING_LOCK(a) (g_mutex_lock (&a->linked_or_flushing_lock)) +#define LINKED_OR_FLUSHING_UNLOCK(a) (g_mutex_unlock (&a->linked_or_flushing_lock)) +#define LINKED_OR_FLUSHING_SIGNAL(a) (g_cond_signal (&a->linked_or_flushing_cond)) +#define LINKED_OR_FLUSHING_WAIT(a) \ + (g_cond_wait (&a->linked_or_flushing_cond, &a->linked_or_flushing_lock)) + +#define FLOW_COMBINER_LOCK(a) (g_mutex_lock (&a->flow_combiner_lock)) +#define FLOW_COMBINER_UNLOCK(a) (g_mutex_unlock (&a->flow_combiner_lock)) + +G_DEFINE_TYPE (GstMseSrcPad, gst_mse_src_pad, GST_TYPE_PAD); + +static gboolean pad_activate_mode (GstMseSrcPad * pad, GstObject * parent, + GstPadMode mode, gboolean active); + +static GstPadLinkReturn pad_linked (GstMseSrcPad * pad, GstMseSrc * parent, + GstPad * sink); +static gboolean pad_event (GstMseSrcPad * pad, GstMseSrc * parent, + GstEvent * event); +static gboolean pad_query (GstMseSrcPad * pad, GstObject * parent, + GstQuery * query); +static void pad_task (GstMseSrcPad * pad); + +static GstPad * +gst_mse_src_pad_new (GstMediaSourceTrack * track, GstStream * stream, + guint id, GstClockTime start, gdouble rate) +{ + gchar *name = g_strdup_printf ("src_%u", id); + GstMseSrcPad *self = g_object_new (GST_TYPE_MSE_SRC_PAD, "name", name, + "direction", GST_PAD_SRC, NULL); + g_free (name); + self->stream = stream; + self->track = track; + self->segment.start = start; + self->segment.rate = rate; + + return GST_PAD (self); +} + +static void +gst_mse_src_pad_init (GstMseSrcPad * self) +{ + gst_segment_init (&self->segment, GST_FORMAT_TIME); + self->sent_stream_collection = FALSE; + self->sent_stream_start = FALSE; + self->sent_initial_caps = FALSE; + self->does_need_segment = TRUE; + self->position = DEFAULT_POSITION; + self->flushing = FALSE; + self->eos = FALSE; + g_mutex_init (&self->linked_or_flushing_lock); + g_cond_init (&self->linked_or_flushing_cond); + + gst_pad_set_activatemode_function (GST_PAD (self), + (GstPadActivateModeFunction) pad_activate_mode); + gst_pad_set_link_function (GST_PAD (self), (GstPadLinkFunction) pad_linked); + gst_pad_set_event_function (GST_PAD (self), (GstPadEventFunction) pad_event); + gst_pad_set_query_function (GST_PAD (self), (GstPadQueryFunction) pad_query); +} + +static void +gst_mse_src_pad_finalize (GObject * object) +{ + GstMseSrcPad *self = GST_MSE_SRC_PAD (object); + + gst_clear_caps (&self->most_recent_caps); + g_mutex_clear (&self->linked_or_flushing_lock); + g_cond_clear (&self->linked_or_flushing_cond); + + G_OBJECT_CLASS (gst_mse_src_pad_parent_class)->finalize (object); +} + +static void +gst_mse_src_pad_class_init (GstMseSrcPadClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + oclass->finalize = GST_DEBUG_FUNCPTR (gst_mse_src_pad_finalize); +} + +// TODO: Check if this struct is even necessary. +// The custom pad should be able to keep track of information for each track. +typedef struct +{ + GstMediaSourceTrack *track; + GstPad *pad; + GstStream *info; +} Stream; + +/** + * GstMseSrc: + * + * Since: 1.24 + */ +struct _GstMseSrc +{ + GstElement base; + + GstMediaSource *media_source; + + guint group_id; + GstStreamCollection *collection; + GHashTable *streams; + GMutex streams_lock; + + GstClockTime duration; + GstClockTime start_time; + gdouble rate; + GstMseSrcReadyState ready_state; + + GstFlowCombiner *flow_combiner; + GMutex flow_combiner_lock; + + GCond eos_cond; + GMutex eos_lock; + + gchar *uri; +}; + +static void gst_mse_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); +static GstStateChangeReturn gst_mse_src_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_mse_src_send_event (GstElement * element, GstEvent * event); +static void update_ready_state_for_init_segment (GstMseSrc * self); +static void update_ready_state_for_sample (GstMseSrc * self); + +G_DEFINE_TYPE_WITH_CODE (GstMseSrc, gst_mse_src, GST_TYPE_ELEMENT, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_mse_src_uri_handler_init)); + +static void +gst_mse_src_constructed (GObject * object) +{ + GstMseSrc *self = GST_MSE_SRC (object); + GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_SOURCE); +} + +static void +gst_mse_src_dispose (GObject * object) +{ + GstMseSrc *self = GST_MSE_SRC (object); + gst_clear_object (&self->collection); + g_clear_pointer (&self->streams, g_hash_table_unref); + g_mutex_clear (&self->streams_lock); + g_clear_pointer (&self->flow_combiner, gst_flow_combiner_free); + g_mutex_clear (&self->flow_combiner_lock); + g_cond_clear (&self->eos_cond); + g_mutex_clear (&self->eos_lock); + G_OBJECT_CLASS (gst_mse_src_parent_class)->dispose (object); +} + +static void +gst_mse_src_finalize (GObject * object) +{ + GstMseSrc *self = GST_MSE_SRC (object); + + g_clear_pointer (&self->uri, g_free); + + G_OBJECT_CLASS (gst_mse_src_parent_class)->finalize (object); +} + +static void +gst_mse_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstMseSrc *self = GST_MSE_SRC (object); + + switch (prop_id) { + case PROP_DURATION: + g_value_set_uint64 (value, gst_mse_src_get_duration (self)); + break; + case PROP_POSITION: + g_value_set_uint64 (value, gst_mse_src_get_position (self)); + break; + case PROP_READY_STATE: + g_value_set_enum (value, gst_mse_src_get_ready_state (self)); + break; + case PROP_N_AUDIO: + g_value_set_uint (value, gst_mse_src_get_n_audio (self)); + break; + case PROP_N_TEXT: + g_value_set_uint (value, gst_mse_src_get_n_text (self)); + break; + case PROP_N_VIDEO: + g_value_set_uint (value, gst_mse_src_get_n_video (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_mse_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMseSrc *self = GST_MSE_SRC (object); + + switch (prop_id) { + case PROP_DURATION: + gst_mse_src_set_duration (self, g_value_get_uint64 (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_mse_src_class_init (GstMseSrcClass * klass) +{ + GObjectClass *oclass = (GObjectClass *) klass; + GstElementClass *eclass = (GstElementClass *) klass; + + oclass->constructed = GST_DEBUG_FUNCPTR (gst_mse_src_constructed); + oclass->finalize = GST_DEBUG_FUNCPTR (gst_mse_src_finalize); + oclass->dispose = GST_DEBUG_FUNCPTR (gst_mse_src_dispose); + oclass->get_property = GST_DEBUG_FUNCPTR (gst_mse_src_get_property); + oclass->set_property = GST_DEBUG_FUNCPTR (gst_mse_src_set_property); + + eclass->change_state = GST_DEBUG_FUNCPTR (gst_mse_src_change_state); + eclass->send_event = GST_DEBUG_FUNCPTR (gst_mse_src_send_event); + + /** + * GstMseSrc:position: + * + * The playback position as a #GstClockTime + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position) + * + * Since: 1.24 + */ + properties[PROP_POSITION] = g_param_spec_uint64 ("position", + "Position", + "The playback position as a GstClockTime", + 0, G_MAXUINT64, DEFAULT_POSITION, G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + /** + * GstMseSrc:duration: + * + * The duration of the stream as a #GstClockTime + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration) + * + * Since: 1.24 + */ + properties[PROP_DURATION] = g_param_spec_uint64 ("duration", + "Duration", + "The duration of the stream as a GstClockTime", + 0, G_MAXUINT64, DEFAULT_DURATION, G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * GstMseSrc:ready-state: + * + * The Ready State of this element, describing to what level it can supply + * content for the current #GstMseSrc:position. This is a separate concept + * from #GstMediaSource:ready-state: and corresponds to the HTML Media + * Element's Ready State. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + * + * Since: 1.24 + */ + properties[PROP_READY_STATE] = g_param_spec_enum ("ready-state", + "Ready State", + "The Ready State of this Element", + GST_TYPE_MSE_SRC_READY_STATE, + DEFAULT_READY_STATE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstMseSrc:n-audio: + * + * The number of audio tracks in the Media Source + * + * Since: 1.24 + */ + properties[PROP_N_AUDIO] = g_param_spec_uint ("n-audio", + "Number of Audio Tracks", + "The number of audio tracks in the Media Source", + 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstMseSrc:n-text: + * + * The number of text tracks in the Media Source + * + * Since: 1.24 + */ + properties[PROP_N_TEXT] = g_param_spec_uint ("n-text", + "Number of Text Tracks", + "The number of text tracks in the Media Source", + 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstMseSrc:n-video: + * + * The number of video tracks in the Media Source + * + * Since: 1.24 + */ + properties[PROP_N_VIDEO] = g_param_spec_uint ("n-video", + "Number of Video Tracks", + "The number of video tracks in the Media Source", + 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPS, properties); + + gst_element_class_set_static_metadata (eclass, "MseSrc", + "Generic/Source", + "Implements a GStreamer Source for the gstreamer-mse API", "Collabora"); + gst_element_class_add_static_pad_template (eclass, &gst_mse_src_template); + + gst_mse_init_logging (); +} + +static void +clear_stream (Stream * stream) +{ + gst_clear_object (&stream->track); + gst_clear_object (&stream->info); + g_free (stream); +} + +static GHashTable * +streams_init (const GstMseSrc * self) +{ + return g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) clear_stream); +} + +static GstStreamCollection * +collection_init (const GstMseSrc * self) +{ + return gst_stream_collection_new (G_OBJECT_TYPE_NAME (self)); +} + +static void +gst_mse_src_init (GstMseSrc * self) +{ + self->group_id = gst_util_group_id_next (); + self->streams = streams_init (self); + self->collection = collection_init (self); + self->uri = NULL; + self->start_time = 0; + self->rate = 1; + g_mutex_init (&self->streams_lock); + self->flow_combiner = gst_flow_combiner_new (); + g_mutex_init (&self->flow_combiner_lock); + g_cond_init (&self->eos_cond); + g_mutex_init (&self->eos_lock); +} + +/** + * gst_mse_src_get_position: + * @self: #GstMseSrc instance + * + * Gets the current playback position of @self. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#current-playback-position) + * + * Returns: The playback position of this Element as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_mse_src_get_position (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), GST_CLOCK_TIME_NONE); + gint64 position; + gboolean success = gst_element_query_position (GST_ELEMENT (self), + GST_FORMAT_TIME, &position); + if (success) + return (GstClockTime) position; + else + return DEFAULT_POSITION; +} + +static void +update_pad_duration (GstMseSrc * self, GstMseSrcPad * pad) +{ + pad->segment.duration = self->duration; + pad->does_need_segment = TRUE; +} + +void +gst_mse_src_set_duration (GstMseSrc * self, GstClockTime duration) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + + self->duration = duration; + + gst_element_foreach_src_pad (GST_ELEMENT (self), + (GstElementForeachPadFunc) update_pad_duration, NULL); + + gst_element_post_message (GST_ELEMENT (self), + gst_message_new_duration_changed (GST_OBJECT (self))); +} + +/** + * gst_mse_src_get_duration: + * @self: #GstMseSrc instance + * + * Gets the duration of @self. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#dom-media-duration) + * + * Returns: The duration of this stream as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_mse_src_get_duration (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), DEFAULT_DURATION); + return self->duration; +} + +/** + * gst_mse_src_get_ready_state: + * @self: #GstMseSrc instance + * + * The Ready State of @self, describing to what level it can supply content for + * the current #GstMseSrc:position. This is a separate concept from + * #GstMediaSource:ready-state: and corresponds to the HTML Media Element's + * Ready State. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + * + * Returns: the current #GstMseSrcReadyState + * Since: 1.24 + */ +GstMseSrcReadyState +gst_mse_src_get_ready_state (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), DEFAULT_READY_STATE); + return self->ready_state; +} + +static guint +n_streams_by_type (GstMseSrc * self, GstMediaSourceTrackType type) +{ + guint count = 0; + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->streams); + for (gpointer key; g_hash_table_iter_next (&iter, &key, NULL);) { + GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key); + GstMediaSourceTrackType stream_type = + gst_media_source_track_get_track_type (track); + if (type == stream_type) { + count++; + } + } + return count; +} + +/** + * gst_mse_src_get_n_audio: + * @self: #GstMseSrc instance + * + * Returns: the number of audio tracks available from this source + * Since: 1.24 + */ +guint +gst_mse_src_get_n_audio (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), 0); + return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO); +} + +/** + * gst_mse_src_get_n_text: + * @self: #GstMseSrc instance + * + * Returns: the number of text tracks available from this source + * Since: 1.24 + */ +guint +gst_mse_src_get_n_text (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), 0); + return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_TEXT); +} + +/** + * gst_mse_src_get_n_video: + * @self: #GstMseSrc instance + * + * Returns: the number of video tracks available from this source + * Since: 1.24 + */ +guint +gst_mse_src_get_n_video (GstMseSrc * self) +{ + g_return_val_if_fail (GST_IS_MSE_SRC (self), 0); + return n_streams_by_type (self, GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO); +} + +void +gst_mse_src_decode_error (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + GstMseSrcReadyState ready_state = g_atomic_int_get (&self->ready_state); + if (ready_state == GST_MSE_SRC_READY_STATE_HAVE_NOTHING) { + GST_ELEMENT_ERROR (self, STREAM, DECODE, (DECODE_ERROR), + ("the necessary decoder may be missing from this installation")); + } else { + GST_ELEMENT_ERROR (self, STREAM, DECODE, (DECODE_ERROR), + ("the stream may be corrupt")); + } +} + +void +gst_mse_src_network_error (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + GstMseSrcReadyState ready_state = g_atomic_int_get (&self->ready_state); + if (ready_state == GST_MSE_SRC_READY_STATE_HAVE_NOTHING) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NETWORK_ERROR), + ("an error occurred before any media was read")); + } else { + GST_ELEMENT_ERROR (self, RESOURCE, READ, (NETWORK_ERROR), + ("an error occurred while reading media")); + } +} + +static inline gboolean +is_streamable (GstMediaSourceTrack * track) +{ + switch (gst_media_source_track_get_track_type (track)) { + case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO: + case GST_MEDIA_SOURCE_TRACK_TYPE_TEXT: + case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO: + return TRUE; + default: + return FALSE; + } +} + +static GstStream * +create_gst_stream (GstMediaSourceTrack * track) +{ + gchar *stream_id = g_strdup_printf ("%s-%s", + GST_OBJECT_NAME (track), gst_media_source_track_get_id (track)); + GstStream *stream = gst_stream_new (stream_id, + gst_media_source_track_get_initial_caps (track), + gst_media_source_track_get_stream_type (track), GST_STREAM_FLAG_SELECT); + g_free (stream_id); + return stream; +} + +static void +set_flushing_and_signal (GstMseSrcPad * pad) +{ + GST_TRACE_OBJECT (pad, "locking"); + LINKED_OR_FLUSHING_LOCK (pad); + g_atomic_int_set (&pad->flushing, TRUE); + LINKED_OR_FLUSHING_SIGNAL (pad); + LINKED_OR_FLUSHING_UNLOCK (pad); + GST_TRACE_OBJECT (pad, "done"); +} + +static void +clear_flushing (GstMseSrcPad * pad) +{ + GST_TRACE_OBJECT (pad, "locking"); + LINKED_OR_FLUSHING_LOCK (pad); + g_atomic_int_set (&pad->flushing, FALSE); + LINKED_OR_FLUSHING_UNLOCK (pad); + GST_TRACE_OBJECT (pad, "done"); +} + +static void +flush_stream (GstMseSrc * self, Stream * stream, gboolean is_seek) +{ + GstMseSrcPad *pad = GST_MSE_SRC_PAD (stream->pad); + gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_start ()); + + if (is_seek) { + GST_DEBUG_OBJECT (pad, "flushing for seek to %" GST_TIMEP_FORMAT, + &self->start_time); + set_flushing_and_signal (pad); + gst_media_source_track_flush (stream->track); + gst_pad_stop_task (GST_PAD (pad)); + GST_DEBUG_OBJECT (pad, "stopped task"); + GstSegment *segment = &(pad->segment); + segment->base = 0; + segment->start = self->start_time; + segment->time = self->start_time; + segment->rate = self->rate; + } + + gst_media_source_track_flush (stream->track); + g_atomic_int_set (&pad->does_need_segment, TRUE); + + gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_stop (is_seek)); +} + +static void +flush_all_streams (GstMseSrc * self, gboolean is_seek) +{ + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->streams); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + flush_stream (self, (Stream *) value, is_seek); + } +} + +static void +resume_all_streams (GstMseSrc * self) +{ + GstState state; + gst_element_get_state (GST_ELEMENT (self), &state, NULL, 0); + gboolean active = state > GST_STATE_READY; + + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->streams); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + Stream *stream = value; + GstPad *pad = GST_PAD (stream->pad); + if (active) { + clear_flushing (GST_MSE_SRC_PAD (pad)); + gst_pad_start_task (pad, (GstTaskFunction) pad_task, pad, NULL); + } + } +} + +static void +tear_down_stream (GstMseSrc * self, Stream * stream) +{ + GST_DEBUG_OBJECT (self, "tearing down stream %s", + gst_media_source_track_get_id (stream->track)); + + flush_stream (self, stream, FALSE); + gst_pad_set_active (stream->pad, FALSE); + + if (gst_stream_collection_get_size (self->collection) > 0) { + gst_element_remove_pad (GST_ELEMENT (self), stream->pad); + FLOW_COMBINER_LOCK (self); + gst_flow_combiner_remove_pad (self->flow_combiner, stream->pad); + FLOW_COMBINER_UNLOCK (self); + } +} + + +static void +tear_down_all_streams (GstMseSrc * self) +{ + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->streams); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + tear_down_stream (self, (Stream *) value); + g_hash_table_iter_remove (&iter); + } +} + +static GstStateChangeReturn +gst_mse_src_change_state (GstElement * element, GstStateChange transition) +{ + GstMseSrc *self = GST_MSE_SRC (element); + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + tear_down_all_streams (self); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_mse_src_detach (self); + break; + default: + break; + } + return GST_ELEMENT_CLASS (gst_mse_src_parent_class)->change_state (element, + transition); +} + +static void +gst_mse_src_seek (GstMseSrc * self, GstClockTime start_time, gdouble rate) +{ + self->start_time = start_time; + self->rate = rate; + + flush_all_streams (self, TRUE); + if (self->media_source) { + GST_DEBUG_OBJECT (self, "seeking on media source %" GST_PTR_FORMAT, + self->media_source); + gst_media_source_seek (self->media_source, start_time); + } else { + GST_DEBUG_OBJECT (self, "detached, not seeking on media source"); + } + resume_all_streams (self); +} + +static gboolean +gst_mse_src_send_event (GstElement * element, GstEvent * event) +{ + if (GST_EVENT_TYPE (event) != GST_EVENT_SEEK) { + return GST_ELEMENT_CLASS (gst_mse_src_parent_class)->send_event (element, + event); + } + + GstMseSrc *self = GST_MSE_SRC (element); + + gdouble rate; + GstFormat format; + GstSeekType seek_type; + gint64 start; + gst_event_parse_seek (event, &rate, &format, NULL, &seek_type, &start, NULL, + NULL); + + gst_event_unref (event); + + if (format != GST_FORMAT_TIME || seek_type != GST_SEEK_TYPE_SET) { + GST_ERROR_OBJECT (self, + "Rejecting unsupported seek event: %" GST_PTR_FORMAT, event); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "handling %" GST_PTR_FORMAT, event); + gst_mse_src_seek (self, start, rate); + return TRUE; +} + +static inline gboolean +is_flushing (GstMseSrcPad * pad) +{ + return g_atomic_int_get (&pad->flushing) || GST_PAD_IS_FLUSHING (pad); +} + +static void +await_pad_linked_or_flushing (GstMseSrcPad * pad) +{ + GST_TRACE_OBJECT (pad, "waiting for link"); + LINKED_OR_FLUSHING_LOCK (pad); + while (!gst_pad_is_linked (GST_PAD_CAST (pad)) && !is_flushing (pad)) { + LINKED_OR_FLUSHING_WAIT (pad); + } + LINKED_OR_FLUSHING_UNLOCK (pad); + GST_TRACE_OBJECT (pad, "linked"); +} + +static gboolean +all_pads_eos_fold (const GValue * item, gboolean * all_eos, gpointer user_data) +{ + GstMseSrcPad *pad = g_value_get_object (item); + if (pad->eos) { + return TRUE; + } else { + *all_eos = FALSE; + return FALSE; + } +} + +static gboolean +all_pads_eos (GstMseSrc * self) +{ + GstIterator *iter = gst_element_iterate_src_pads (GST_ELEMENT_CAST (self)); + gboolean all_eos = TRUE; + while (gst_iterator_fold (iter, + (GstIteratorFoldFunction) all_pads_eos_fold, (GValue *) & all_eos, + NULL) == GST_ITERATOR_RESYNC) { + gst_iterator_resync (iter); + } + gst_iterator_free (iter); + return all_eos; +} + +static void +pad_task (GstMseSrcPad * pad) +{ + await_pad_linked_or_flushing (pad); + + if (is_flushing (pad)) { + GST_TRACE_OBJECT (pad, "pad is flushing"); + goto pause; + } + + GstMseSrc *self = GST_MSE_SRC (gst_pad_get_parent_element (GST_PAD (pad))); + + GstMediaSourceTrack *track = pad->track; + + GstMiniObject *object = gst_media_source_track_pop (track); + + if (object == NULL) { + GST_DEBUG_OBJECT (pad, "nothing was popped from track, must be flushing"); + gst_media_source_track_flush (track); + goto pause; + } + + if (!g_atomic_int_get (&pad->sent_stream_start)) { + const gchar *track_id = gst_media_source_track_get_id (track); + GstEvent *event = gst_event_new_stream_start (track_id); + gst_event_set_group_id (event, self->group_id); + gst_event_set_stream (event, pad->stream); + if (!gst_pad_push_event (GST_PAD (pad), event)) { + GST_ERROR_OBJECT (pad, "failed to push stream start"); + goto pause; + } + GST_TRACE_OBJECT (pad, "stream start"); + g_atomic_int_set (&pad->sent_stream_start, TRUE); + } + + GstCaps *caps = gst_media_source_track_get_initial_caps (track); + if (!g_atomic_int_get (&pad->sent_initial_caps) && GST_IS_CAPS (caps)) { + GST_DEBUG_OBJECT (pad, "sending initial caps"); + gst_caps_replace (&pad->most_recent_caps, caps); + GstEvent *event = gst_event_new_caps (caps); + if (!gst_pad_push_event (GST_PAD (pad), event)) { + GST_ERROR_OBJECT (pad, "failed to push caps update"); + goto pause; + } + GST_TRACE_OBJECT (pad, "initial caps %" GST_PTR_FORMAT, caps); + g_atomic_int_set (&pad->sent_initial_caps, TRUE); + } + + if (g_atomic_int_get (&pad->does_need_segment)) { + GST_DEBUG_OBJECT (pad, "sending new segment starting@%" GST_TIMEP_FORMAT, + &pad->segment.time); + GstEvent *event = gst_event_new_segment (&pad->segment); + if (!gst_pad_push_event (GST_PAD (pad), event)) { + GST_ERROR_OBJECT (pad, "failed to push new segment"); + goto pause; + } + GST_TRACE_OBJECT (pad, "segment"); + g_atomic_int_set (&pad->does_need_segment, FALSE); + } + + if (!g_atomic_int_get (&pad->sent_stream_collection)) { + GstEvent *event = gst_event_new_stream_collection (self->collection); + if (!gst_pad_push_event (GST_PAD (pad), event)) { + GST_ERROR_OBJECT (pad, "failed to push stream collection"); + goto pause; + } + GST_TRACE_OBJECT (pad, "stream collection"); + g_atomic_int_set (&pad->sent_stream_collection, TRUE); + } + + if (GST_IS_SAMPLE (object)) { + GstSample *sample = GST_SAMPLE (object); + GstCaps *sample_caps = gst_sample_get_caps (sample); + + if (!gst_caps_is_equal (pad->most_recent_caps, sample_caps)) { + gst_caps_replace (&pad->most_recent_caps, sample_caps); + GstEvent *event = gst_event_new_caps (gst_caps_ref (sample_caps)); + if (!gst_pad_push_event (GST_PAD (pad), event)) { + GST_ERROR_OBJECT (pad, "failed to push new caps"); + goto pause; + } + GST_TRACE_OBJECT (pad, "new caps %" GST_PTR_FORMAT, sample_caps); + } + + GstBuffer *buffer = gst_buffer_copy (gst_sample_get_buffer (sample)); + if (GST_BUFFER_DTS_IS_VALID (buffer)) { + pad->position = GST_BUFFER_DTS (buffer); + } + + GstFlowReturn push_result = gst_pad_push (GST_PAD (pad), buffer); + + FLOW_COMBINER_LOCK (self); + GstFlowReturn combined_result = + gst_flow_combiner_update_pad_flow (self->flow_combiner, + GST_PAD_CAST (pad), push_result); + FLOW_COMBINER_UNLOCK (self); + + if (combined_result != GST_FLOW_OK) { + GST_DEBUG_OBJECT (pad, "push result: %s, combined result: %s", + gst_flow_get_name (push_result), gst_flow_get_name (combined_result)); + goto pause; + } + } else if (GST_IS_EVENT (object)) { + if (GST_EVENT_TYPE (object) == GST_EVENT_EOS) { + g_mutex_lock (&self->eos_lock); + pad->eos = TRUE; + g_cond_broadcast (&self->eos_cond); + g_mutex_unlock (&self->eos_lock); + g_mutex_lock (&self->eos_lock); + while (!all_pads_eos (self)) { + GST_DEBUG_OBJECT (pad, "waiting for eos on all tracks"); + g_cond_wait (&self->eos_cond, &self->eos_lock); + } + g_mutex_unlock (&self->eos_lock); + GST_DEBUG_OBJECT (pad, "have eos on all tracks"); + } + if (!gst_pad_push_event (GST_PAD (pad), GST_EVENT (object))) { + GST_ERROR_OBJECT (self, "failed to push enqueued event"); + goto pause; + } + } else { + GST_ERROR_OBJECT (self, "unexpected object on track queue" + ", only samples and events are supported"); + g_assert_not_reached (); + } + + return; + +pause: + if (!g_atomic_int_get (&pad->flushing)) { + gst_pad_pause_task (GST_PAD (pad)); + } +} + +static gboolean +pad_activate_mode (GstMseSrcPad * pad, GstObject * parent, GstPadMode mode, + gboolean active) +{ + if (mode != GST_PAD_MODE_PUSH) { + GST_ERROR_OBJECT (parent, "msesrc only supports push mode"); + return FALSE; + } + + if (active) { + gst_pad_start_task (GST_PAD (pad), (GstTaskFunction) pad_task, pad, NULL); + } else { + set_flushing_and_signal (pad); + gst_media_source_track_flush (pad->track); + gst_pad_stop_task (GST_PAD (pad)); + clear_flushing (pad); + } + + return TRUE; +} + +static GstPadLinkReturn +pad_linked (GstMseSrcPad * pad, GstMseSrc * parent, GstPad * sink) +{ + GST_DEBUG_OBJECT (pad, "pad is linked to %" GST_PTR_FORMAT ", resuming task", + sink); + LINKED_OR_FLUSHING_LOCK (pad); + LINKED_OR_FLUSHING_SIGNAL (pad); + LINKED_OR_FLUSHING_UNLOCK (pad); + return GST_PAD_LINK_OK; +} + +static gboolean +pad_event (GstMseSrcPad * pad, GstMseSrc * parent, GstEvent * event) +{ + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + return gst_element_send_event (GST_ELEMENT (parent), event); + default: + return gst_pad_event_default (GST_PAD (pad), GST_OBJECT (parent), event); + } +} + +static gboolean +pad_query (GstMseSrcPad * pad, GstObject * parent, GstQuery * query) +{ + GstMseSrc *self = GST_MSE_SRC (parent); + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION:{ + GstClockTime position = pad->position; + GstFormat fmt; + gst_query_parse_position (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME && GST_CLOCK_TIME_IS_VALID (position)) { + GST_TRACE_OBJECT (pad, "position query returning %" GST_TIMEP_FORMAT, + &position); + gst_query_set_position (query, GST_FORMAT_TIME, position); + return TRUE; + } + break; + } + case GST_QUERY_DURATION:{ + GstFormat fmt; + gst_query_parse_duration (query, &fmt, NULL); + if (fmt == GST_FORMAT_TIME) { + gst_query_set_duration (query, GST_FORMAT_TIME, self->duration); + return TRUE; + } else { + return FALSE; + } + } + case GST_QUERY_SEEKING:{ + GstFormat fmt; + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + if (fmt != GST_FORMAT_TIME) { + return FALSE; + } + gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, self->duration); + return TRUE; + } + default: + break; + } + return gst_pad_query_default (GST_PAD (pad), parent, query); +} + +static void +append_stream (GstMseSrc * self, GstMediaSourceTrack * track) +{ + if (g_hash_table_contains (self->streams, track)) { + GST_DEBUG_OBJECT (self, "skipping processed %" GST_PTR_FORMAT, track); + return; + } + GST_DEBUG_OBJECT (self, "creating stream for %" GST_PTR_FORMAT, track); + guint pad_index = g_hash_table_size (self->streams); + GstStream *info = create_gst_stream (track); + Stream stream = { + .info = gst_object_ref (info), + .track = gst_object_ref (track), + .pad = gst_mse_src_pad_new (track, info, pad_index, + self->start_time, self->rate), + }; + g_hash_table_insert (self->streams, track, + g_memdup2 (&stream, sizeof (Stream))); + gst_stream_collection_add_stream (self->collection, stream.info); +} + +void +gst_mse_src_emit_streams (GstMseSrc * self, GstMediaSourceTrack ** tracks, + gsize n_tracks) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + + GstElement *element = GST_ELEMENT (self); + GstObject *object = GST_OBJECT (self); + + update_ready_state_for_init_segment (self); + + STREAMS_LOCK (self); + + for (gsize i = 0; i < n_tracks; i++) { + GstMediaSourceTrack *track = tracks[i]; + if (!is_streamable (track)) { + continue; + } + append_stream (self, track); + } + + GstState state; + gst_element_get_state (element, &state, NULL, 0); + gboolean active = state > GST_STATE_READY; + + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->streams); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + Stream *stream = value; + GstPad *pad = stream->pad; + if (active) { + gst_pad_set_active (pad, TRUE); + } + GstElement *parent = gst_pad_get_parent_element (pad); + if (parent) { + GST_DEBUG_OBJECT (self, "skipping parented pad %" GST_PTR_FORMAT, pad); + gst_object_unref (parent); + continue; + } + gst_element_add_pad (element, pad); + FLOW_COMBINER_LOCK (self); + gst_flow_combiner_add_pad (self->flow_combiner, pad); + FLOW_COMBINER_UNLOCK (self); + } + STREAMS_UNLOCK (self); + + gst_element_no_more_pads (element); + gst_element_post_message (element, gst_message_new_stream_collection (object, + self->collection)); +} + +void +gst_mse_src_update_ready_state (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + update_ready_state_for_sample (self); +} + +static GstURIType +gst_mse_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_mse_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "mse", NULL }; + return protocols; +} + +static gchar * +gst_mse_src_uri_get_uri (GstURIHandler * handler) +{ + GstMseSrc *self = GST_MSE_SRC (handler); + return g_strdup (self->uri); +} + +static gboolean +gst_mse_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstMseSrc *self = GST_MSE_SRC (handler); + g_free (self->uri); + self->uri = g_strdup (uri); + return TRUE; +} + +static void +gst_mse_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + iface->get_type = gst_mse_src_uri_get_type; + iface->get_protocols = gst_mse_src_uri_get_protocols; + iface->get_uri = gst_mse_src_uri_get_uri; + iface->set_uri = gst_mse_src_uri_set_uri; +} + +void +gst_mse_src_attach (GstMseSrc * self, GstMediaSource * media_source) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + g_return_if_fail (GST_IS_MEDIA_SOURCE (media_source)); + g_set_object (&self->media_source, media_source); +} + +void +gst_mse_src_detach (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MSE_SRC (self)); + gst_clear_object (&self->media_source); +} + +static void +set_ready_state (GstMseSrc * self, GstMseSrcReadyState ready_state) +{ + if (ready_state == self->ready_state) { + return; + } + GST_DEBUG_OBJECT (self, "ready state %d=>%d", self->ready_state, ready_state); + self->ready_state = ready_state; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_READY_STATE]); +} + +static void +update_ready_state_for_init_segment (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self->media_source)); + if (self->ready_state != GST_MSE_SRC_READY_STATE_HAVE_NOTHING) { + return; + } + GstSourceBufferList *buffers = gst_media_source_get_source_buffers + (self->media_source); + gboolean all_received_init_segment = TRUE; + for (guint i = 0; all_received_init_segment; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (buffers, i); + if (buf == NULL) { + break; + } + all_received_init_segment &= gst_source_buffer_has_init_segment (buf); + gst_object_unref (buf); + } + if (!all_received_init_segment) { + return; + } + set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_METADATA); +} + +static gboolean +has_current_data (GstMseSrc * self) +{ + GstClockTime position = gst_mse_src_get_position (self); + if (!GST_CLOCK_TIME_IS_VALID (position)) { + return FALSE; + } + GstSourceBufferList *active = + gst_media_source_get_active_source_buffers (self->media_source); + gboolean has_data = TRUE; + for (guint i = 0; has_data; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (active, i); + if (buf == NULL) { + if (i == 0) { + has_data = FALSE; + GST_DEBUG_OBJECT (self, + "no active source buffers, nothing is buffered"); + } + break; + } + has_data = gst_source_buffer_is_buffered (buf, position); + gst_object_unref (buf); + } + g_object_unref (active); + return has_data; +} + +static gboolean +has_future_data (GstMseSrc * self) +{ + GstClockTime position = gst_mse_src_get_position (self); + GstClockTime duration = self->duration; + if (!GST_CLOCK_TIME_IS_VALID (position) + || !GST_CLOCK_TIME_IS_VALID (duration)) { + return FALSE; + } + GstClockTime target_position = MIN (position + THRESHOLD_FUTURE_DATA, + duration); + GstSourceBufferList *active = + gst_media_source_get_active_source_buffers (self->media_source); + gboolean has_data = TRUE; + for (guint i = 0; has_data; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (active, i); + if (buf == NULL) { + if (i == 0) { + has_data = FALSE; + GST_DEBUG_OBJECT (self, + "no active source buffers, nothing is buffered"); + } + break; + } + has_data = gst_source_buffer_is_range_buffered (buf, position, + target_position); + gst_object_unref (buf); + } + g_object_unref (active); + return has_data; +} + +static gboolean +has_enough_data (GstMseSrc * self) +{ + GstClockTime position = gst_mse_src_get_position (self); + GstClockTime duration = self->duration; + if (!GST_CLOCK_TIME_IS_VALID (position) + || !GST_CLOCK_TIME_IS_VALID (duration)) { + return FALSE; + } + GstClockTime target_position = MIN (position + THRESHOLD_ENOUGH_DATA, + duration); + GstSourceBufferList *active = + gst_media_source_get_active_source_buffers (self->media_source); + gboolean has_data = TRUE; + for (guint i = 0; has_data; i++) { + GstSourceBuffer *buf = gst_source_buffer_list_index (active, i); + if (buf == NULL) { + if (i == 0) { + has_data = FALSE; + GST_DEBUG_OBJECT (self, + "no active source buffers, nothing is buffered"); + } + break; + } + has_data = gst_source_buffer_is_range_buffered (buf, position, + target_position); + gst_object_unref (buf); + } + g_object_unref (active); + return has_data; +} + +static void +update_ready_state_for_sample (GstMseSrc * self) +{ + g_return_if_fail (GST_IS_MEDIA_SOURCE (self->media_source)); + g_return_if_fail (self->ready_state >= GST_MSE_SRC_READY_STATE_HAVE_METADATA); + + if (has_enough_data (self)) { + set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA); + } else if (has_future_data (self)) { + set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA); + } else if (has_current_data (self)) { + set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA); + } else { + set_ready_state (self, GST_MSE_SRC_READY_STATE_HAVE_METADATA); + } +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h new file mode 100644 index 0000000000..1a7ff1411f --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstmsesrc.h @@ -0,0 +1,94 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2009, 2010 Sebastian Dröge + * Copyright (C) 2013, 2022, 2023 Collabora Ltd. + * Copyright (C) 2013 Orange + * Copyright (C) 2014, 2015 Sebastian Dröge + * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Igalia, S.L + * Copyright (C) 2015, 2016, 2018, 2019, 2020, 2021 Metrological Group B.V. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +/** + * GstMseSrcReadyState: + * @GST_MSE_SRC_READY_STATE_HAVE_NOTHING: No information is available about the + * stream + * @GST_MSE_SRC_READY_STATE_HAVE_METADATA: The duration is available and video + * dimensions are available if the stream contains video + * @GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA: The current playback position can + * be presented but future information is not available + * @GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA: There is data for the current + * position and some amount in the future and any text tracks are ready. + * @GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA: Either there is enough data to + * play the stream through at the current playback and input rate or the input + * buffer is full. + * + * Describes how much information a #GstMseSrc has about the media it is playing + * back at the current playback #GstMseSrc:position. This type corresponds + * directly to the ready state of a HTML Media Element and is a separate concept + * from #GstMediaSourceReadyState. + * + * [Specification](https://html.spec.whatwg.org/multipage/media.html#ready-states) + * + * Since: 1.24 + */ +typedef enum +{ + GST_MSE_SRC_READY_STATE_HAVE_NOTHING, + GST_MSE_SRC_READY_STATE_HAVE_METADATA, + GST_MSE_SRC_READY_STATE_HAVE_CURRENT_DATA, + GST_MSE_SRC_READY_STATE_HAVE_FUTURE_DATA, + GST_MSE_SRC_READY_STATE_HAVE_ENOUGH_DATA, +} GstMseSrcReadyState; + +#define GST_TYPE_MSE_SRC (gst_mse_src_get_type()) +#define GST_TYPE_MSE_SRC_PAD (gst_mse_src_pad_get_type()) + +GST_MSE_API +G_DECLARE_FINAL_TYPE (GstMseSrc, gst_mse_src, GST, MSE_SRC, GstElement); + +GST_MSE_API +G_DECLARE_FINAL_TYPE (GstMseSrcPad, gst_mse_src_pad, GST, MSE_SRC_PAD, GstPad); + +GST_MSE_API +GstClockTime gst_mse_src_get_position (GstMseSrc * self); + +GST_MSE_API +GstClockTime gst_mse_src_get_duration (GstMseSrc * self); + +GST_MSE_API +GstMseSrcReadyState gst_mse_src_get_ready_state (GstMseSrc * self); + +GST_MSE_API +guint gst_mse_src_get_n_audio (GstMseSrc * self); + +GST_MSE_API +guint gst_mse_src_get_n_text (GstMseSrc * self); + +GST_MSE_API +guint gst_mse_src_get_n_video (GstMseSrc * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h new file mode 100644 index 0000000000..17ae1af5c3 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer-private.h @@ -0,0 +1,72 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include "gstsourcebuffer.h" + +G_BEGIN_DECLS + +typedef struct +{ + void (*duration_changed) (GstSourceBuffer * self, gpointer user_data); + void (*received_init_segment) (GstSourceBuffer * self, gpointer user_data); + void (*active_state_changed) (GstSourceBuffer * self, gpointer user_data); +} GstSourceBufferCallbacks; + +GST_MSE_PRIVATE +GstSourceBuffer * gst_source_buffer_new (const gchar * content_type, + GstObject * parent, GError ** error); + +GST_MSE_PRIVATE +GstSourceBuffer * gst_source_buffer_new_with_callbacks ( + const gchar * content_type, GstObject * parent, + GstSourceBufferCallbacks *callbacks, gpointer user_data, GError ** error); + +GST_MSE_PRIVATE +void gst_source_buffer_teardown (GstSourceBuffer * self); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_has_init_segment (GstSourceBuffer * self); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_is_buffered (GstSourceBuffer * self, GstClockTime + time); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_is_range_buffered (GstSourceBuffer * self, + GstClockTime start, GstClockTime end); + +GST_MSE_PRIVATE +GstClockTime gst_source_buffer_get_duration (GstSourceBuffer * self); + +GST_MSE_PRIVATE +GPtrArray * gst_source_buffer_get_all_tracks (GstSourceBuffer * self); + +GST_MSE_PRIVATE +void gst_source_buffer_seek (GstSourceBuffer * self, GstClockTime time); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_get_active (GstSourceBuffer * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c new file mode 100644 index 0000000000..7695bad373 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.c @@ -0,0 +1,2019 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2013 Google Inc. All rights reserved. + * Copyright (C) 2013 Orange + * Copyright (C) 2013-2020 Apple Inc. All rights reserved. + * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015, 2016, 2017 Igalia, S.L + * Copyright (C) 2015, 2016, 2017 Metrological Group B.V. + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstsourcebuffer + * @title: GstSourceBuffer + * @short_description: Source Buffer + * @include: mse/mse.h + * @symbols: + * - GstSourceBuffer + * + * The Source Buffer is the primary means of data flow between an application + * and the Media Source API. It represents a single timeline of media, + * containing some combination of audio, video, and text tracks. + * An application is responsible for feeding raw data into the Source Buffer + * using gst_source_buffer_append_buffer() and the Source Buffer will + * asynchronously process the data into tracks of time-coded multimedia samples. + * + * The application as well as the associated playback component can then select + * to play media from any subset of tracks across all Source Buffers of a Media + * Source. + * + * A few control points are also provided to customize the behavior. + * + * - #GstSourceBuffer:append-mode controls how timestamps of processed samples are + * interpreted. They are either inserted in the timeline directly where the + * decoded media states they should, or inserted directly after the previously + * encountered sample. + * + * - #GstSourceBuffer:append-window-start / #GstSourceBuffer:append-window-end + * control the planned time window where media from appended data can be added + * to the current timeline. Any samples outside that range may be ignored. + * + * - #GstSourceBuffer:timestamp-offset is added to the start time of any sample + * processed. + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstsourcebuffer.h" +#include "gstsourcebuffer-private.h" + +#include "gstmselogging-private.h" +#include "gstmsemediatype-private.h" +#include "gstmseeventqueue-private.h" + +#include "gstappendpipeline-private.h" +#include "gstmediasource.h" +#include "gstmediasource-private.h" +#include "gstmediasourcetrack-private.h" +#include "gstmediasourcetrackbuffer-private.h" +#include "gstsourcebufferlist-private.h" +#include "gstmsesrc.h" +#include "gstmsesrc-private.h" + +#define g_array_new_ranges() \ + (g_array_new (TRUE, FALSE, sizeof (GstMediaSourceRange))) + +typedef struct +{ + GstSourceBufferCallbacks callbacks; + gpointer user_data; +} Callbacks; + +/** + * GstSourceBuffer: + * Since: 1.24 + */ +typedef struct +{ + GstSourceBuffer *parent; + + GstMediaSourceTrack *track; + GstMediaSourceTrackBuffer *buffer; + + GstTask *task; + GRecMutex lock; + + gboolean cancelled; +} TrackFeedTask; + +typedef struct +{ + gsize n_samples; + GstSample *current_sample; + GstClockTime current_dts; +} TrackFeedAccumulator; + +typedef struct +{ + const GstClockTime time; + gboolean buffered; +} IsBufferedAccumulator; + +typedef struct +{ + const GstClockTime start; + const GstClockTime end; + gboolean start_buffered; + gboolean end_buffered; +} IsRangeBufferedAccumulator; + +struct _GstSourceBuffer +{ + GstObject parent_instance; + + GstSourceBufferAppendMode append_mode; + GstClockTime append_window_start; + GstClockTime append_window_end; + gchar *content_type; + gboolean generate_timestamps; + GstClockTime timestamp_offset; + gboolean updating; + gboolean errored; + gsize size_limit; + gsize size; + GstBuffer *pending_data; + GstTask *append_to_buffer_task; + GRecMutex append_to_buffer_lock; + GstClockTime seek_time; + GstAppendPipeline *append_pipeline; + GstMseEventQueue *event_queue; + + gboolean processed_init_segment; + + GHashTable *track_buffers; + GHashTable *track_feeds; + + Callbacks callbacks; +}; + +G_DEFINE_TYPE (GstSourceBuffer, gst_source_buffer, GST_TYPE_OBJECT); + +enum +{ + PROP_0, + + PROP_APPEND_MODE, + PROP_APPEND_WINDOW_START, + PROP_APPEND_WINDOW_END, + PROP_BUFFERED, + PROP_CONTENT_TYPE, + PROP_TIMESTAMP_OFFSET, + PROP_UDPATING, + + N_PROPS, +}; + +typedef enum +{ + ON_UPDATE_START, + ON_UPDATE, + ON_UPDATE_END, + ON_ERROR, + ON_ABORT, + + N_SIGNALS, +} SourceBufferEvent; + +typedef struct +{ + GstDataQueueItem item; + SourceBufferEvent event; +} SourceBufferEventItem; + +#define DEFAULT_BUFFER_SIZE 1 << 24 +#define DEFAULT_APPEND_MODE GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; + +static void call_received_init_segment (GstSourceBuffer * self); +static void call_duration_changed (GstSourceBuffer * self); +static void call_active_state_changed (GstSourceBuffer * self); + +static inline gboolean is_removed (GstSourceBuffer * self); +static void reset_parser_state (GstSourceBuffer * self); +static void append_error (GstSourceBuffer * self); + +static void seek_track_buffer (GstMediaSourceTrack * track, + GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self); +static void dispatch_event (SourceBufferEventItem * item, GstSourceBuffer * + self); +static void schedule_event (GstSourceBuffer * self, SourceBufferEvent event); +static void append_to_buffer_task (GstSourceBuffer * self); +static void track_feed_task (TrackFeedTask * feed); +static void clear_track_feed (TrackFeedTask * feed); +static void stop_track_feed (TrackFeedTask * feed); +static void start_track_feed (TrackFeedTask * feed); +static void reset_track_feed (TrackFeedTask * feed); +static TrackFeedTask *get_track_feed (GstSourceBuffer * self, + GstMediaSourceTrack * track); +static GstMediaSourceTrackBuffer *get_track_buffer (GstSourceBuffer * self, + GstMediaSourceTrack * track); +static void add_track_feed (GstMediaSourceTrack * track, + GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self); +static void add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer * + self); +static void update_msesrc_ready_state (GstSourceBuffer * self); + +static void on_duration_changed (GstAppendPipeline * pipeline, + gpointer user_data); +static void on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track, + gpointer user_data); +static void on_error (GstAppendPipeline * pipeline, gpointer user_data); +static void on_new_sample (GstAppendPipeline * pipeline, + GstMediaSourceTrack * track, GstSample * sample, gpointer user_data); +static void on_received_init_segment (GstAppendPipeline * pipeline, + gpointer user_data); + +static inline GstMediaSource * +get_media_source (GstSourceBuffer * self) +{ + return GST_MEDIA_SOURCE (gst_object_get_parent (GST_OBJECT (self))); +} + +static GstMseSrc * +get_msesrc (GstSourceBuffer * self) +{ + GstMediaSource *media_source = get_media_source (self); + if (media_source == NULL) { + return NULL; + } + return gst_media_source_get_source_element (media_source); +} + +static void +clear_pending_data (GstSourceBuffer * self) +{ + gst_clear_buffer (&self->pending_data); +} + +static GstBuffer * +take_pending_data (GstSourceBuffer * self) +{ + return g_steal_pointer (&self->pending_data); +} + +static void +set_pending_data (GstSourceBuffer * self, GstBuffer * buffer) +{ + clear_pending_data (self); + self->pending_data = buffer; +} + +GstSourceBuffer * +gst_source_buffer_new (const gchar * content_type, GstObject * parent, + GError ** error) +{ + g_return_val_if_fail (GST_IS_MEDIA_SOURCE (parent), NULL); + g_return_val_if_fail (content_type != NULL, NULL); + + gst_mse_init_logging (); + + GstMediaSourceMediaType media_type = GST_MEDIA_SOURCE_MEDIA_TYPE_INIT; + gst_media_source_media_type_parse (&media_type, content_type); + + gboolean generate_timestamps = gst_media_source_media_type_generates_timestamp + (&media_type); + gst_media_source_media_type_reset (&media_type); + + GstSourceBufferAppendMode append_mode = generate_timestamps + ? GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE + : GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS; + + GstSourceBuffer *self = g_object_new (GST_TYPE_SOURCE_BUFFER, + "parent", parent, NULL); + + self->generate_timestamps = generate_timestamps; + self->append_mode = append_mode; + self->content_type = g_strdup (content_type); + + GstAppendPipelineCallbacks callbacks = { + .duration_changed = on_duration_changed, + .eos = on_eos, + .error = on_error, + .new_sample = on_new_sample, + .received_init_segment = on_received_init_segment, + }; + GError *append_pipeline_error = NULL; + self->append_pipeline = + gst_append_pipeline_new (&callbacks, self, &append_pipeline_error); + if (append_pipeline_error) { + g_propagate_prefixed_error (error, append_pipeline_error, + "failed to create source buffer"); + goto error; + } + + return gst_object_ref_sink (self); +error: + gst_clear_object (&self); + return NULL; +} + +GstSourceBuffer * +gst_source_buffer_new_with_callbacks (const gchar * content_type, + GstObject * parent, GstSourceBufferCallbacks * callbacks, + gpointer user_data, GError ** error) +{ + g_return_val_if_fail (callbacks, NULL); + + GError *source_buffer_error = NULL; + GstSourceBuffer *self = + gst_source_buffer_new (content_type, parent, &source_buffer_error); + if (source_buffer_error) { + g_propagate_error (error, source_buffer_error); + gst_clear_object (&self); + return NULL; + } + self->callbacks.callbacks = *callbacks; + self->callbacks.user_data = user_data; + + return self; +} + +static void +gst_source_buffer_dispose (GObject * object) +{ + GstSourceBuffer *self = (GstSourceBuffer *) object; + + if (self->append_to_buffer_task) { + gst_task_join (self->append_to_buffer_task); + } + gst_clear_object (&self->append_to_buffer_task); + + gst_clear_object (&self->append_pipeline); + + g_hash_table_remove_all (self->track_feeds); + + if (!is_removed (self)) { + GstMediaSource *parent = get_media_source (self); + gst_media_source_remove_source_buffer (parent, self, NULL); + gst_object_unref (parent); + } + + gst_clear_object (&self->event_queue); + + G_OBJECT_CLASS (gst_source_buffer_parent_class)->dispose (object); +} + +static void +gst_source_buffer_finalize (GObject * object) +{ + GstSourceBuffer *self = (GstSourceBuffer *) object; + + g_clear_pointer (&self->content_type, g_free); + g_rec_mutex_clear (&self->append_to_buffer_lock); + + g_hash_table_unref (self->track_buffers); + g_hash_table_unref (self->track_feeds); + + G_OBJECT_CLASS (gst_source_buffer_parent_class)->finalize (object); +} + +static void +gst_source_buffer_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (object); + + switch (prop_id) { + case PROP_APPEND_MODE: + g_value_set_enum (value, gst_source_buffer_get_append_mode (self)); + break; + case PROP_APPEND_WINDOW_START: + g_value_set_uint64 (value, + gst_source_buffer_get_append_window_start (self)); + break; + case PROP_APPEND_WINDOW_END: + g_value_set_uint64 (value, + gst_source_buffer_get_append_window_end (self)); + break; + case PROP_BUFFERED: + g_value_take_boxed (value, gst_source_buffer_get_buffered (self, NULL)); + break; + case PROP_CONTENT_TYPE: + g_value_take_string (value, gst_source_buffer_get_content_type (self)); + break; + case PROP_TIMESTAMP_OFFSET: + g_value_set_int64 (value, gst_source_buffer_get_timestamp_offset (self)); + break; + case PROP_UDPATING: + g_value_set_boolean (value, gst_source_buffer_get_updating (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_source_buffer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (object); + + switch (prop_id) { + case PROP_APPEND_MODE: + gst_source_buffer_set_append_mode (self, g_value_get_enum (value), NULL); + break; + case PROP_CONTENT_TYPE: + gst_source_buffer_change_content_type (self, + g_value_get_string (value), NULL); + break; + case PROP_TIMESTAMP_OFFSET: + gst_source_buffer_set_timestamp_offset (self, + g_value_get_int64 (value), NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_source_buffer_class_init (GstSourceBufferClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = GST_DEBUG_FUNCPTR (gst_source_buffer_dispose); + oclass->finalize = GST_DEBUG_FUNCPTR (gst_source_buffer_finalize); + oclass->get_property = GST_DEBUG_FUNCPTR (gst_source_buffer_get_property); + oclass->set_property = GST_DEBUG_FUNCPTR (gst_source_buffer_set_property); + + /** + * GstSourceBuffer:append-mode: + * + * Affects how timestamps of processed media segments are interpreted. + * In %GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, the start timestamp of a + * processed media segment is used directly along with + * #GstSourceBuffer:timestamp-offset . + * In %GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, the timestamp of a + * processed media segment is ignored and replaced with the end time of the + * most recently appended segment. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + * + * Since: 1.24 + */ + properties[PROP_APPEND_MODE] = g_param_spec_enum ("append-mode", + "Append Mode", + "Either Segments or Sequence", + GST_TYPE_SOURCE_BUFFER_APPEND_MODE, DEFAULT_APPEND_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:append-window-start: + * + * Any segments processed which end before this value will be ignored by this + * Source Buffer. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + * + * Since: 1.24 + */ + properties[PROP_APPEND_WINDOW_START] = + g_param_spec_uint64 ("append-window-start", "Append Window Start", + "The timestamp representing the start of the append window", 0, + GST_CLOCK_TIME_NONE, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:append-window-end: + * + * Any segments processed which have a start time greater than this value will + * be ignored by this Source Buffer. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + * + * Since: 1.24 + */ + properties[PROP_APPEND_WINDOW_END] = g_param_spec_uint64 ("append-window-end", + "Append Window End", + "The timestamp representing the end of the append window", + 0, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:buffered: + * + * The set of Time Intervals that have been loaded into the current Source + * Buffer + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered) + * + * Since: 1.24 + */ + properties[PROP_BUFFERED] = g_param_spec_boxed ("buffered", + "Buffered Time Intervals", + "The set of Time Intervals that have been loaded into" + " the current Source Buffer", G_TYPE_ARRAY, G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:content-type: + * + * The MIME content-type of the data stream + * + * Since: 1.24 + */ + properties[PROP_CONTENT_TYPE] = g_param_spec_string ("content-type", + "Content Type", + "The MIME content-type of the data stream", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:timestamp-offset: + * + * The next media segment appended to the current Source Buffer will have its + * start timestamp increased by this amount. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + * + * Since: 1.24 + */ + properties[PROP_TIMESTAMP_OFFSET] = g_param_spec_int64 ("timestamp-offset", + "Timestamp Offset", + "The next media segment appended to the current Source Buffer" + " will have its start timestamp increased by this amount", + 0, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GstSourceBuffer:updating: + * + * Whether the current source buffer is still asynchronously processing + * previously issued commands. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating) + * + * Since: 1.24 + */ + properties[PROP_UDPATING] = g_param_spec_boolean ("updating", + "Updating", + "Whether the current Source Buffer is still" + " asynchronously processing previously issued commands", + FALSE, G_PARAM_READABLE); + + g_object_class_install_properties (oclass, N_PROPS, properties); + + /** + * GstSourceBuffer::on-update-start: + * @self: The #GstSourceBuffer that has just started updating + * + * Emitted when @self has begun to process data after a call to + * gst_source_buffer_append_buffer(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdatestart) + * + * Since: 1.24 + */ + signals[ON_UPDATE_START] = g_signal_new ("on-update-start", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstSourceBuffer::on-update: + * @self: The #GstSourceBuffer that has just updated + * + * Emitted when @self has successfully processed data after a call to + * gst_source_buffer_append_buffer(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdate) + * + * Since: 1.24 + */ + signals[ON_UPDATE] = g_signal_new ("on-update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstSourceBuffer::on-update-end: + * @self: The #GstSourceBuffer that is no longer updating + * + * Emitted when @self is no longer in the updating state after a call to + * gst_source_buffer_append_buffer(). This can happen after a successful or + * unsuccessful append. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdateend) + * + * Since: 1.24 + */ + signals[ON_UPDATE_END] = g_signal_new ("on-update-end", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstSourceBuffer::on-error: + * @self: The #GstSourceBuffer that has encountered an error + * + * Emitted when @self has encountered an error after a call to + * gst_source_buffer_append_buffer(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onerror) + * + * Since: 1.24 + */ + signals[ON_ERROR] = g_signal_new ("on-error", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstSourceBuffer::on-abort: + * @self: The #GstSourceBuffer that has been aborted. + * + * Emitted when @self was aborted after a call to gst_source_buffer_abort(). + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onabort) + * + * Since: 1.24 + */ + signals[ON_ABORT] = g_signal_new ("on-abort", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void +on_duration_changed (GstAppendPipeline * pipeline, gpointer user_data) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data); + if (is_removed (self)) { + return; + } + call_duration_changed (self); +} + +static void +on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track, + gpointer user_data) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data); + if (GST_IS_MEDIA_SOURCE_TRACK (track)) { + GST_DEBUG_OBJECT (self, "got EOS event on %" GST_PTR_FORMAT, track); + GstMediaSourceTrackBuffer *buffer = get_track_buffer (self, track); + gst_media_source_track_buffer_eos (buffer); + } + update_msesrc_ready_state (self); +} + +static void +on_error (GstAppendPipeline * pipeline, gpointer user_data) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data); + append_error (self); +} + +static GstMediaSourceTrackBuffer * +get_track_buffer (GstSourceBuffer * self, GstMediaSourceTrack * track) +{ + g_return_val_if_fail (g_hash_table_contains (self->track_buffers, track), + NULL); + return g_hash_table_lookup (self->track_buffers, track); +} + +static inline TrackFeedTask * +get_track_feed (GstSourceBuffer * self, GstMediaSourceTrack * track) +{ + g_return_val_if_fail (g_hash_table_contains (self->track_feeds, track), NULL); + return g_hash_table_lookup (self->track_feeds, track); +} + +static void +add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer * self) +{ + const gchar *id = gst_media_source_track_get_id (track); + if (g_hash_table_contains (self->track_buffers, track)) { + GST_DEBUG_OBJECT (self, "already have a track buffer for track %s", id); + return; + } + GstMediaSourceTrackBuffer *buf = gst_media_source_track_buffer_new (); + g_hash_table_insert (self->track_buffers, track, buf); + GST_DEBUG_OBJECT (self, "added track buffer for track %s", id); + + add_track_feed (track, buf, self); +} + +static void +add_track_feed (GstMediaSourceTrack * track, + GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self) +{ + TrackFeedTask *feed = g_new0 (TrackFeedTask, 1); + GstTask *task = gst_task_new ((GstTaskFunction) track_feed_task, feed, NULL); + g_rec_mutex_init (&feed->lock); + gst_task_set_lock (task, &feed->lock); + feed->task = task; + feed->buffer = track_buffer; + feed->track = gst_object_ref (track); + feed->parent = self; + feed->cancelled = FALSE; + g_hash_table_insert (self->track_feeds, track, feed); +} + +static void +clear_track_feed (TrackFeedTask * feed) +{ + gst_object_unref (feed->task); + g_rec_mutex_clear (&feed->lock); + gst_object_unref (feed->track); + g_free (feed); +} + +static void +stop_track_feed (TrackFeedTask * feed) +{ + g_return_if_fail (feed != NULL); + gst_media_source_track_flush (feed->track); + g_atomic_int_set (&feed->cancelled, TRUE); + gst_task_join (feed->task); +} + +static void +start_track_feed (TrackFeedTask * feed) +{ + g_return_if_fail (feed != NULL); + g_atomic_int_set (&feed->cancelled, FALSE); + gst_media_source_track_resume (feed->track); + gst_task_start (feed->task); +} + +static void +reset_track_feed (TrackFeedTask * feed) +{ + stop_track_feed (feed); + start_track_feed (feed); +} + +static gboolean +is_within_append_window (GstSourceBuffer * self, GstSample * sample) +{ + GstBuffer *buffer = gst_sample_get_buffer (sample); + GstClockTime start = GST_BUFFER_PTS (buffer); + GstClockTime end = start + GST_BUFFER_DURATION (buffer); + + if (start < self->append_window_start) { + return FALSE; + } + + if (!GST_CLOCK_TIME_IS_VALID (self->append_window_end)) { + return TRUE; + } + + return end <= self->append_window_end; +} + +static void +on_new_sample (GstAppendPipeline * pipeline, GstMediaSourceTrack * track, + GstSample * sample, gpointer user_data) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data); + + g_return_if_fail (self->processed_init_segment); + + GST_OBJECT_LOCK (self); + + if (is_within_append_window (self, sample)) { + GstMediaSourceTrackBuffer *track_buffer = get_track_buffer (self, track); + GST_TRACE_OBJECT (self, "new sample on %s with %" GST_PTR_FORMAT, + gst_media_source_track_get_id (track), gst_sample_get_buffer (sample)); + gst_media_source_track_buffer_add (track_buffer, sample); + TrackFeedTask *feed = get_track_feed (self, track); + start_track_feed (feed); + } + + GST_OBJECT_UNLOCK (self); + + update_msesrc_ready_state (self); +} + +static void +call_received_init_segment (GstSourceBuffer * self) +{ + GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks; + if (callbacks->received_init_segment) { + callbacks->received_init_segment (self, self->callbacks.user_data); + } +} + +static void +call_duration_changed (GstSourceBuffer * self) +{ + GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks; + if (callbacks->duration_changed) { + callbacks->duration_changed (self, self->callbacks.user_data); + } +} + +static void +call_active_state_changed (GstSourceBuffer * self) +{ + GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks; + if (callbacks->active_state_changed) { + callbacks->active_state_changed (self, self->callbacks.user_data); + } +} + +static void +update_track_buffer_modes (GstSourceBuffer * self) +{ + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->track_buffers); + gboolean enabled = + self->append_mode == GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE; + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value); + gst_media_source_track_buffer_process_init_segment (buffer, enabled); + gst_media_source_track_buffer_set_group_start (buffer, + self->timestamp_offset); + } +} + +static void +on_received_init_segment (GstAppendPipeline * pipeline, gpointer user_data) +{ + GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data); + GST_DEBUG_OBJECT (self, "got init segment, have duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (gst_append_pipeline_get_duration (pipeline))); + + GST_OBJECT_LOCK (self); + + if (!self->processed_init_segment) { + GST_DEBUG_OBJECT (self, "processing first init segment"); + + GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks (pipeline); + GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks (pipeline); + GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks (pipeline); + + g_ptr_array_foreach (audio_tracks, (GFunc) add_track_buffer, self); + g_ptr_array_foreach (text_tracks, (GFunc) add_track_buffer, self); + g_ptr_array_foreach (video_tracks, (GFunc) add_track_buffer, self); + } + + self->processed_init_segment = TRUE; + + update_track_buffer_modes (self); + + GST_OBJECT_UNLOCK (self); + + call_received_init_segment (self); + call_active_state_changed (self); +} + +static guint +track_buffer_hash (GstMediaSourceTrack * track) +{ + return g_str_hash (gst_media_source_track_get_id (track)); +} + +static gboolean +track_buffer_equal (GstMediaSourceTrack * a, GstMediaSourceTrack * b) +{ + return g_str_equal (gst_media_source_track_get_id (a), + gst_media_source_track_get_id (b)); +} + +static void +gst_source_buffer_init (GstSourceBuffer * self) +{ + + self->append_mode = DEFAULT_APPEND_MODE; + self->append_window_start = 0; + self->append_window_end = GST_CLOCK_TIME_NONE; + self->content_type = NULL; + self->timestamp_offset = 0; + self->updating = FALSE; + self->errored = FALSE; + self->size_limit = DEFAULT_BUFFER_SIZE; + self->size = 0; + self->pending_data = NULL; + self->processed_init_segment = FALSE; + self->event_queue = + gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self); + + g_rec_mutex_init (&self->append_to_buffer_lock); + self->append_to_buffer_task = gst_task_new ( + (GstTaskFunction) append_to_buffer_task, self, NULL); + gst_task_set_lock (self->append_to_buffer_task, &self->append_to_buffer_lock); + self->track_buffers = g_hash_table_new_full ((GHashFunc) track_buffer_hash, + (GEqualFunc) track_buffer_equal, NULL, gst_object_unref); + + self->track_feeds = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) clear_track_feed); + self->seek_time = 0; + self->callbacks.callbacks.duration_changed = NULL; + self->callbacks.user_data = NULL; +} + +static inline gboolean +is_removed (GstSourceBuffer * self) +{ + GstObject *parent = gst_object_get_parent (GST_OBJECT (self)); + if (parent == NULL) { + return TRUE; + } + gst_object_unref (parent); + + GstMediaSource *source = get_media_source (self); + GstSourceBufferList *buffers = gst_media_source_get_source_buffers (source); + gboolean removed = !gst_source_buffer_list_contains (buffers, self); + + gst_object_unref (source); + gst_object_unref (buffers); + + return removed; +} + +static inline gboolean +is_updating (GstSourceBuffer * self) +{ + return g_atomic_int_get (&self->updating); +} + +static inline void +set_updating (GstSourceBuffer * self) +{ + g_atomic_int_set (&self->updating, TRUE); +} + +static inline void +clear_updating (GstSourceBuffer * self) +{ + g_atomic_int_set (&self->updating, FALSE); +} + +static inline gboolean +is_errored (GstSourceBuffer * self) +{ + return g_atomic_int_get (&self->errored); +} + +static inline void +set_errored (GstSourceBuffer * self) +{ + g_atomic_int_set (&self->errored, TRUE); +} + +static inline void +clear_errored (GstSourceBuffer * self) +{ + g_atomic_int_set (&self->errored, FALSE); +} + +static inline gboolean +is_ended (GstSourceBuffer * self) +{ + if (is_removed (self)) { + return TRUE; + } + + GstMediaSource *source = get_media_source (self); + gboolean ended = gst_media_source_get_ready_state (source) == + GST_MEDIA_SOURCE_READY_STATE_ENDED; + + gst_object_unref (source); + + return ended; +} + +static void +open_parent (GstSourceBuffer * self) +{ + g_return_if_fail (!is_removed (self)); + GstMediaSource *source = get_media_source (self); + gst_media_source_open (source); + gst_object_unref (source); +} + +/** + * gst_source_buffer_get_append_mode: + * @self: #GstSourceBuffer instance + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + * + * Returns: The current #GstSourceBufferAppendMode + * Since: 1.24 + */ +GstSourceBufferAppendMode +gst_source_buffer_get_append_mode (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), DEFAULT_APPEND_MODE); + return self->append_mode; +} + +/** + * gst_source_buffer_set_append_mode: + * @self: #GstSourceBuffer instance + * @mode: #GstSourceBufferAppendMode the desired Append Mode + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Changes the Append Mode of @self. This influences what timestamps will be + * assigned to media processed by this Source Buffer. In Segment mode, the + * timestamps in each segment determine the position of each sample after it + * is processed. In Sequence mode, the timestamp of each processed sample is + * generated based on the end of the most recently processed segment. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_set_append_mode (GstSourceBuffer * self, + GstSourceBufferAppendMode mode, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + + if (is_removed (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "buffer is removed"); + return FALSE; + } + + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "buffer is still updating"); + return FALSE; + } + + if (self->generate_timestamps && mode == + GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "cannot change to segments mode while generate timestamps is active"); + return FALSE; + } + + if (is_ended (self)) { + open_parent (self); + } + + self->append_mode = mode; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APPEND_MODE]); + return TRUE; +} + +/** + * gst_source_buffer_get_append_window_start: + * @self: #GstSourceBuffer instance + * + * Returns the current append window start time. Any segment processed that ends + * earlier than this value will be ignored. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + * + * Returns: The current Append Window start time as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_source_buffer_get_append_window_start (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE); + return self->append_window_start; +} + +/** + * gst_source_buffer_set_append_window_start: + * @self: #GstSourceBuffer instance + * @start: the append window end + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Modifies the current append window start of @self. If successful, samples + * processed after setting this value that end before this point will be + * ignored. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_set_append_window_start (GstSourceBuffer * self, + GstClockTime start, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + + if (is_removed (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "append window start cannot be set on source buffer " + "with no media source"); + return FALSE; + } + + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "append window start cannot be set on source buffer while updating"); + return FALSE; + } + + if (start < 0 || start <= self->append_window_end) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "append window start must be between zero and append window end"); + return FALSE; + } + + self->append_window_start = start; + g_object_notify_by_pspec (G_OBJECT (self), + properties[PROP_APPEND_WINDOW_START]); + return TRUE; +} + +/** + * gst_source_buffer_get_append_window_end: + * @self: #GstSourceBuffer instance + * + * Returns the current append window end time. Any segment processed that starts + * after this value will be ignored. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + * + * Returns: The current Append Window end time as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_source_buffer_get_append_window_end (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE); + return self->append_window_end; +} + +/** + * gst_source_buffer_set_append_window_end: + * @self: #GstSourceBuffer instance + * @end: the append window end + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Modifies the current append window end of @self. If successful, samples + * processed after setting this value that start after this point will be + * ignored. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_set_append_window_end (GstSourceBuffer * self, + GstClockTime end, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + + if (is_removed (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "append window end cannot be set on source buffer " + "with no media source"); + return FALSE; + } + + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "append window end cannot be set on source buffer while updating"); + return FALSE; + } + + if (end <= self->append_window_start) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "append window end must be after append window start"); + return FALSE; + } + + self->append_window_end = end; + g_object_notify_by_pspec (G_OBJECT (self), + properties[PROP_APPEND_WINDOW_END]); + return TRUE; +} + +static gboolean +get_intersection (GstMediaSourceRange * a, GstMediaSourceRange * b, + GstMediaSourceRange * intersection) +{ + g_return_val_if_fail (a != NULL, FALSE); + g_return_val_if_fail (b != NULL, FALSE); + g_return_val_if_fail (intersection != NULL, FALSE); + GstMediaSourceRange range = { + .start = MAX (a->start, b->start), + .end = MIN (a->end, b->end), + }; + if (range.start >= range.end) { + return FALSE; + } + *intersection = range; + return TRUE; +} + +static GArray * +intersect_ranges (GstMediaSourceRange * a, GstMediaSourceRange * a_end, + GstMediaSourceRange * b, GstMediaSourceRange * b_end) +{ + GArray *intersection = g_array_new_ranges (); + while (a < a_end && b < b_end) { + GstMediaSourceRange range; + if (!get_intersection (a, b, &range)) { + if (a->end < b->end) { + a++; + } else { + b++; + } + continue; + } + + if (a->end < b->end) { + a++; + } else { + b++; + } + + g_array_append_val (intersection, range); + } + return intersection; +} + +static inline gboolean +contributes_to_buffered (GstMediaSourceTrack * track) +{ + switch (gst_media_source_track_get_track_type (track)) { + case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO: + case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO: + return TRUE; + default: + return FALSE; + } +} + +/** + * gst_source_buffer_get_buffered: + * @self: #GstSourceBuffer instance + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Returns a sequence of #GstMediaSourceRange values representing which segments + * of @self are buffered in memory. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered) + * + * Returns: (transfer full) (element-type GstMediaSourceRange): a #GArray of #GstMediaSourceRange values. + * Since: 1.24 + */ +GArray * +gst_source_buffer_get_buffered (GstSourceBuffer * self, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL); + GHashTableIter iter; + GArray *buffered = NULL; + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer key, value; g_hash_table_iter_next (&iter, &key, &value);) { + GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key); + GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value); + if (!contributes_to_buffered (track)) { + continue; + } + GArray *current_ranges = gst_media_source_track_buffer_get_ranges (buffer); + if (buffered == NULL) { + buffered = current_ranges; + continue; + } + GArray *intersection = intersect_ranges ( + (GstMediaSourceRange *) buffered->data, + ((GstMediaSourceRange *) buffered->data) + buffered->len, + (GstMediaSourceRange *) current_ranges->data, + ((GstMediaSourceRange *) current_ranges->data) + current_ranges->len); + g_array_unref (buffered); + buffered = intersection; + } + if (buffered == NULL) { + return g_array_new_ranges (); + } else { + return buffered; + } +} + +/** + * gst_source_buffer_get_content_type: + * @self: #GstSourceBuffer instance + * + * Returns the current content type of @self. + * + * Returns: (transfer full): a string representing the content type + * Since: 1.24 + */ +gchar * +gst_source_buffer_get_content_type (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL); + + GST_OBJECT_LOCK (self); + gchar *content_type = g_strdup (self->content_type); + GST_OBJECT_UNLOCK (self); + + return content_type; +} + +/** + * gst_source_buffer_change_content_type: + * @self: #GstSourceBuffer instance + * @type: (transfer none): the desired content type + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Attempts to change the content type of @self to @type. Any new data appended + * to the Source Buffer must be of the supplied @type afterward. + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_change_content_type (GstSourceBuffer * self, + const gchar * type, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + + if (type == NULL || g_strcmp0 (type, "") == 0) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE, + "content type must not be empty"); + return FALSE; + } + + if (is_removed (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "content type cannot be set on source buffer with no media source"); + return FALSE; + } + + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "content type cannot be set on source buffer that is updating"); + return FALSE; + } + + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED, + "content type cannot be changed"); + return FALSE; +} + +/** + * gst_source_buffer_remove: + * @self: #GstSourceBuffer instance + * @start: The beginning timestamp of data to remove + * @end: The end timestamp of data to remove + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Attempts to remove any parsed data between @start and @end from @self. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-remove) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_remove (GstSourceBuffer * self, GstClockTime start, + GstClockTime end, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + return TRUE; +} + +/** + * gst_source_buffer_get_timestamp_offset: + * @self: #GstSourceBuffer instance + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + * + * Returns: The current timestamp offset as a #GstClockTime + * Since: 1.24 + */ +GstClockTime +gst_source_buffer_get_timestamp_offset (GstSourceBuffer * self) +{ + return self->timestamp_offset; +} + +/** + * gst_source_buffer_set_timestamp_offset: + * @self: #GstSourceBuffer instance + * @offset: The new timestamp offset + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Attempt to set the timestamp offset of @self. Any media processed after this + * value is set will have this value added to its start time. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_set_timestamp_offset (GstSourceBuffer * self, GstClockTime + offset, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + if (is_removed (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "source buffer is removed"); + return FALSE; + } + if (is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "source buffer is still updating"); + return FALSE; + } + if (is_ended (self)) { + GstMediaSource *parent = get_media_source (self); + gst_media_source_open (parent); + gst_clear_object (&parent); + } + GST_OBJECT_LOCK (self); + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + GstMediaSourceTrackBuffer *buffer = value; + gst_media_source_track_buffer_set_group_start (buffer, offset); + } + self->timestamp_offset = offset; + GST_OBJECT_UNLOCK (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP_OFFSET]); + return TRUE; +} + +/** + * gst_source_buffer_get_updating: + * @self: #GstSourceBuffer instance + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating) + * + * Returns: Whether @self is currently adding or removing media content. + * Since: 1.24 + */ +gboolean +gst_source_buffer_get_updating (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + return is_updating (self); +} + +static gsize +compute_total_size_unlocked (GstSourceBuffer * self) +{ + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->track_buffers); + gsize total_size = 0; + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + GstMediaSourceTrackBuffer *buffer = value; + total_size += gst_media_source_track_buffer_get_storage_size (buffer); + } + return total_size; +} + +static gboolean +will_overflow (GstSourceBuffer * self, gsize bytes) +{ + GST_OBJECT_LOCK (self); + gsize total = compute_total_size_unlocked (self); + GST_OBJECT_UNLOCK (self); + return total + bytes > self->size_limit; +} + +static void +evict_coded_frames (GstSourceBuffer * self, gsize space_required, + gsize size_limit, GstClockTime position, GstClockTime duration) +{ + if (!will_overflow (self, space_required)) { + return; + } + + if (!GST_CLOCK_TIME_IS_VALID (position)) { + GST_ERROR ("invalid position, cannot delete anything"); + return; + } + + GstClockTime min_distance_from_position = GST_SECOND * 5; + GstClockTime max_dts = position > min_distance_from_position ? + position - min_distance_from_position : 0; + + GST_DEBUG_OBJECT (self, "position=%" GST_TIMEP_FORMAT + ", attempting removal from 0 to %" GST_TIMEP_FORMAT, &position, &max_dts); + + GST_OBJECT_LOCK (self); + GHashTableIter iter; + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) { + GstMediaSourceTrackBuffer *buffer = value; + gst_media_source_track_buffer_remove_range (buffer, 0, max_dts); + } + self->size = compute_total_size_unlocked (self); + GST_OBJECT_UNLOCK (self); + + GST_DEBUG_OBJECT (self, "capacity=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT + "(%" G_GSIZE_FORMAT "%%)", self->size, self->size_limit, + self->size * 100 / size_limit); +} + +static void +reset_parser_state (GstSourceBuffer * self) +{ + clear_pending_data (self); + if (gst_append_pipeline_reset (self->append_pipeline)) { + clear_errored (self); + } else { + set_errored (self); + } +} + +static void +append_error (GstSourceBuffer * self) +{ + gst_task_stop (self->append_to_buffer_task); + reset_parser_state (self); + clear_updating (self); + + if (is_removed (self)) { + return; + } + + schedule_event (self, ON_ERROR); + schedule_event (self, ON_UPDATE_END); + + GstMediaSource *source = get_media_source (self); + gst_media_source_end_of_stream (source, GST_MEDIA_SOURCE_EOS_ERROR_DECODE, + NULL); + gst_object_unref (source); +} + +static void +append_successful (GstSourceBuffer * self, gboolean ended) +{ + gst_task_stop (self->append_to_buffer_task); + clear_updating (self); + schedule_event (self, ON_UPDATE); + schedule_event (self, ON_UPDATE_END); +} + +static gboolean +encountered_bad_bytes (GstSourceBuffer * self) +{ + return gst_append_pipeline_get_failed (self->append_pipeline); +} + +static void +append_to_buffer_task (GstSourceBuffer * self) +{ + if (is_removed (self)) { + append_successful (self, TRUE); + return; + } + + if (encountered_bad_bytes (self)) { + append_error (self); + return; + } + + GstBuffer *pending_data = take_pending_data (self); + + if (!GST_IS_BUFFER (pending_data)) { + GST_LOG_OBJECT (self, "no pending data"); + append_successful (self, is_ended (self)); + return; + } + + GstFlowReturn result = gst_append_pipeline_append (self->append_pipeline, + pending_data); + + if (result != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "failed to append: %s", gst_flow_get_name (result)); + append_error (self); + return; + } + + append_successful (self, is_ended (self)); +} + +static gboolean +track_feed_fold (const GValue * item, TrackFeedAccumulator * acc, + TrackFeedTask * feed) +{ + GstSample *sample = gst_sample_ref (gst_value_get_sample (item)); + GstClockTime dts = GST_BUFFER_DTS (gst_sample_get_buffer (sample)); + acc->n_samples++; + acc->current_dts = dts; + gst_clear_sample (&acc->current_sample); + acc->current_sample = gst_sample_ref (sample); + if (gst_media_source_track_push (feed->track, sample)) { + return TRUE; + } else { + gst_sample_unref (sample); + return FALSE; + } +} + +static void +track_feed_task (TrackFeedTask * feed) +{ + GstSourceBuffer *self = feed->parent; + GstMediaSourceTrack *track = feed->track; + GstMediaSourceTrackBuffer *buffer = feed->buffer; + GstClockTime time = feed->parent->seek_time; + const gchar *track_id = gst_media_source_track_get_id (track); + + GST_DEBUG_OBJECT (self, "%s: feed starting@%" GST_TIMEP_FORMAT, track_id, + &time); + + TrackFeedAccumulator acc = { + .n_samples = 0, + .current_dts = time, + .current_sample = NULL, + }; + while (TRUE) { + gboolean eos = gst_media_source_track_buffer_is_eos (buffer); + GstIterator *it = gst_media_source_track_buffer_iter_samples (buffer, + acc.current_dts, acc.current_sample); + while (TRUE) { + GstIteratorResult fold_result = gst_iterator_fold (it, + (GstIteratorFoldFunction) track_feed_fold, (GValue *) & acc, feed); + if (fold_result != GST_ITERATOR_RESYNC) { + break; + } + if (g_atomic_int_get (&feed->cancelled)) { + break; + } + gst_iterator_resync (it); + } + gst_iterator_free (it); + + if (eos) { + GST_DEBUG_OBJECT (self, "%s: enqueued all %" G_GSIZE_FORMAT " samples", + track_id, acc.n_samples); + gst_media_source_track_push_eos (track); + GST_DEBUG_OBJECT (self, "%s: marked EOS", track_id); + gst_task_stop (feed->task); + break; + } + + if (g_atomic_int_get (&feed->cancelled)) { + GST_DEBUG_OBJECT (self, "feed is cancelled, stopping task"); + gst_task_stop (feed->task); + break; + } + + GST_DEBUG_OBJECT (self, "%s: resume after %" G_GSIZE_FORMAT " samples", + track_id, acc.n_samples); + gint64 deadline = g_get_monotonic_time () + G_TIME_SPAN_SECOND; + gst_media_source_track_buffer_await_eos_until (buffer, deadline); + } + + gst_clear_sample (&acc.current_sample); +} + +static void +dispatch_event (SourceBufferEventItem * item, GstSourceBuffer * self) +{ + g_signal_emit (self, signals[item->event], 0); +} + +static void +schedule_event (GstSourceBuffer * self, SourceBufferEvent event) +{ + g_return_if_fail (event < N_SIGNALS); + if (is_removed (self)) { + return; + } + SourceBufferEventItem item = { + .item = {.destroy = g_free,.visible = TRUE,.size = 1,.object = NULL}, + .event = event, + }; + gst_mse_event_queue_push (self->event_queue, g_memdup2 (&item, + sizeof (SourceBufferEventItem))); +} + +static void +schedule_append_to_buffer_task (GstSourceBuffer * self) +{ + GstTask *task = self->append_to_buffer_task; + g_return_if_fail (GST_IS_TASK (task)); + g_return_if_fail (gst_task_get_state (task) != GST_TASK_STARTED); + gst_task_start (task); +} + +static void +update_msesrc_ready_state (GstSourceBuffer * self) +{ + GstMseSrc *element = get_msesrc (self); + if (element == NULL) { + return; + } + gst_mse_src_update_ready_state (element); + gst_object_unref (element); +} + +/** + * gst_source_buffer_append_buffer: + * @self: #GstSourceBuffer instance + * @buf: (transfer full):The media data to append + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Schedules the bytes inside @buf to be processed by @self. When it is possible + * to accept the supplied data, it will be processed asynchronously and fill in + * the track buffers for playback purposes. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendbuffer) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_append_buffer (GstSourceBuffer * self, GstBuffer * buf, + GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + g_return_val_if_fail (GST_IS_BUFFER (buf), FALSE); + + if (is_removed (self) || is_updating (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "source buffer is removed or still updating"); + return FALSE; + } + + if (is_errored (self)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "source buffer has encountered error"); + return FALSE; + } + + if (is_ended (self)) { + open_parent (self); + } + + GstMediaSource *source = get_media_source (self); + gsize buffer_size = gst_buffer_get_size (buf); + GstClockTime position = gst_media_source_get_position (source); + GstClockTime duration = gst_media_source_get_duration (source); + + gst_object_unref (source); + + evict_coded_frames (self, buffer_size, self->size_limit, position, duration); + + if (will_overflow (self, buffer_size)) { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED, + "buffer is full"); + return FALSE; + } + + g_return_val_if_fail (self->pending_data == NULL, FALSE); + + set_pending_data (self, buf); + set_updating (self); + + schedule_event (self, ON_UPDATE_START); + schedule_append_to_buffer_task (self); + + return TRUE; +} + +/** + * gst_source_buffer_abort: + * @self: #GstSourceBuffer instance + * @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL` + * + * Attempts to end any processing of the currently pending data and reset the + * media parser. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-abort) + * + * Returns: `TRUE` on success, `FALSE` otherwise + * Since: 1.24 + */ +gboolean +gst_source_buffer_abort (GstSourceBuffer * self, GError ** error) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + if (gst_append_pipeline_eos (self->append_pipeline) == GST_FLOW_OK) { + return TRUE; + } else { + g_set_error (error, + GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE, + "failed to abort source buffer"); + return FALSE; + } +} + +gboolean +gst_source_buffer_has_init_segment (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE); + return gst_append_pipeline_has_init_segment (self->append_pipeline); +} + +static gboolean +is_buffered_fold (const GValue * item, IsBufferedAccumulator * acc, + GstSourceBuffer * self) +{ + GstSample *sample = gst_value_get_sample (item); + GstBuffer *buffer = gst_sample_get_buffer (sample); + GstClockTime buffer_start = GST_BUFFER_DTS (buffer); + GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer); + if (acc->time < buffer_start) { + GST_TRACE_OBJECT (self, "position precedes buffer start, done"); + acc->buffered = FALSE; + return FALSE; + } + if (acc->time >= buffer_start && acc->time < buffer_end) { + GST_TRACE_OBJECT (self, "position is within buffer, done"); + acc->buffered = TRUE; + return FALSE; + } + return TRUE; +} + +gboolean +gst_source_buffer_is_buffered (GstSourceBuffer * self, GstClockTime time) +{ + GHashTableIter iter; + gboolean buffered = TRUE; + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer key, value; + buffered && g_hash_table_iter_next (&iter, &key, &value);) { + GstMediaSourceTrack *track = key; + if (!gst_media_source_track_get_active (track)) { + continue; + } + GstMediaSourceTrackBuffer *track_buffer = value; + IsBufferedAccumulator acc = { + .time = time, + .buffered = FALSE, + }; + GstIterator *iter = + gst_media_source_track_buffer_iter_samples (track_buffer, time, NULL); + while (gst_iterator_fold (iter, (GstIteratorFoldFunction) is_buffered_fold, + (GValue *) & acc, self) == GST_ITERATOR_RESYNC) { + gst_iterator_resync (iter); + } + gst_iterator_free (iter); + buffered = acc.buffered; + } + return buffered; +} + +static gboolean +is_range_buffered_fold (const GValue * item, IsRangeBufferedAccumulator * acc, + GstSourceBuffer * self) +{ + GstSample *sample = gst_value_get_sample (item); + GstBuffer *buffer = gst_sample_get_buffer (sample); + GstClockTime buffer_start = GST_BUFFER_DTS (buffer); + GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer); + + GstClockTime start = acc->start; + GstClockTime end = acc->end; + + if (!acc->start_buffered) { + if (start < buffer_start) { + GST_TRACE_OBJECT (self, "start position precedes buffer start, done"); + return FALSE; + } + if (start >= buffer_start && start < buffer_end) { + GST_TRACE_OBJECT (self, "start position is within buffer, checking end"); + acc->start_buffered = TRUE; + return TRUE; + } + } else { + if (end < buffer_start) { + GST_TRACE_OBJECT (self, "end position precedes buffer start, done"); + return FALSE; + } + if (end <= buffer_end) { + GST_TRACE_OBJECT (self, "end position is within buffer, done"); + acc->end_buffered = TRUE; + return FALSE; + } + } + + return TRUE; +} + +gboolean +gst_source_buffer_is_range_buffered (GstSourceBuffer * self, GstClockTime start, + GstClockTime end) +{ + GHashTableIter iter; + gboolean buffered = TRUE; + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer key, value; + buffered && g_hash_table_iter_next (&iter, &key, &value);) { + GstMediaSourceTrack *track = key; + if (!gst_media_source_track_get_active (track)) { + continue; + } + GstMediaSourceTrackBuffer *track_buffer = value; + IsRangeBufferedAccumulator acc = { + .start = start, + .end = end, + .start_buffered = FALSE, + .end_buffered = FALSE, + }; + GstIterator *iter = + gst_media_source_track_buffer_iter_samples (track_buffer, start, NULL); + while (gst_iterator_fold (iter, + (GstIteratorFoldFunction) is_range_buffered_fold, (GValue *) & acc, + self) == GST_ITERATOR_RESYNC) { + gst_iterator_resync (iter); + } + buffered = acc.end_buffered; + gst_iterator_free (iter); + } + return buffered; +} + +GstClockTime +gst_source_buffer_get_duration (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE); + return gst_append_pipeline_get_duration (self->append_pipeline); +} + +void +gst_source_buffer_teardown (GstSourceBuffer * self) +{ + reset_parser_state (self); + clear_updating (self); +} + +GPtrArray * +gst_source_buffer_get_all_tracks (GstSourceBuffer * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL); + + GPtrArray *tracks = g_ptr_array_new (); + + GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks + (self->append_pipeline); + GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks + (self->append_pipeline); + GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks + (self->append_pipeline); + + if (audio_tracks) { + g_ptr_array_extend (tracks, audio_tracks, NULL, NULL); + } + if (text_tracks) { + g_ptr_array_extend (tracks, text_tracks, NULL, NULL); + } + if (video_tracks) { + g_ptr_array_extend (tracks, video_tracks, NULL, NULL); + } + + return tracks; +} + +static void +seek_track_buffer (GstMediaSourceTrack * track, + GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self) +{ + TrackFeedTask *feed = get_track_feed (self, track); + + const gchar *track_id = gst_media_source_track_get_id (track); + GST_DEBUG_OBJECT (self, "%s: seeking", track_id); + reset_track_feed (feed); + GST_DEBUG_OBJECT (self, "%s: restarted track feed", track_id); +} + +void +gst_source_buffer_seek (GstSourceBuffer * self, GstClockTime time) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER (self)); + g_return_if_fail (GST_CLOCK_TIME_IS_VALID (time)); + self->seek_time = time; + g_hash_table_foreach (self->track_buffers, (GHFunc) seek_track_buffer, self); +} + +gboolean +gst_source_buffer_get_active (GstSourceBuffer * self) +{ + gboolean active = FALSE; + GHashTableIter iter; + GST_OBJECT_LOCK (self); + g_hash_table_iter_init (&iter, self->track_buffers); + for (gpointer key; !active && g_hash_table_iter_next (&iter, &key, NULL);) { + GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key); + active |= gst_media_source_track_get_active (track); + } + GST_OBJECT_UNLOCK (self); + return active; +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h new file mode 100644 index 0000000000..965094ae4c --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebuffer.h @@ -0,0 +1,127 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2013 Google Inc. All rights reserved. + * Copyright (C) 2013 Orange + * Copyright (C) 2013-2020 Apple Inc. All rights reserved. + * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2015, 2016, 2017 Igalia, S.L + * Copyright (C) 2015, 2016, 2017 Metrological Group B.V. + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +/** + * GstSourceBufferAppendMode: + * @GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS: + * @GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE: + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-appendmode) + * + * Since: 1.24 + */ +typedef enum +{ + GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, + GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, +} GstSourceBufferAppendMode; + +/** + * GstSourceBufferInterval: + * + * Since: 1.24 + */ +typedef struct +{ + GstClockTime start; + GstClockTime end; +} GstSourceBufferInterval; + +#define GST_TYPE_SOURCE_BUFFER (gst_source_buffer_get_type()) + +GST_MSE_API +G_DECLARE_FINAL_TYPE (GstSourceBuffer, gst_source_buffer, GST, SOURCE_BUFFER, + GstObject); + +GST_MSE_API +GstSourceBufferAppendMode gst_source_buffer_get_append_mode ( + GstSourceBuffer * self); + +GST_MSE_API +gboolean gst_source_buffer_set_append_mode (GstSourceBuffer * self, + GstSourceBufferAppendMode mode, GError ** error); + +GST_MSE_API +gchar *gst_source_buffer_get_content_type (GstSourceBuffer * self); + +GST_MSE_API +gboolean gst_source_buffer_get_updating (GstSourceBuffer * self); + +GST_MSE_API +GArray * gst_source_buffer_get_buffered (GstSourceBuffer * self, + GError ** error); + +GST_MSE_API +gboolean gst_source_buffer_set_timestamp_offset (GstSourceBuffer * self, + GstClockTime offset, + GError ** error); + +GST_MSE_API +GstClockTime gst_source_buffer_get_timestamp_offset (GstSourceBuffer * self); + +GST_MSE_API +gboolean gst_source_buffer_set_append_window_start (GstSourceBuffer * self, + GstClockTime start, + GError ** error); + +GST_MSE_API +GstClockTime gst_source_buffer_get_append_window_start (GstSourceBuffer * self); + +GST_MSE_API +gboolean gst_source_buffer_set_append_window_end (GstSourceBuffer * self, + GstClockTime end, + GError ** error); + +GST_MSE_API +GstClockTime gst_source_buffer_get_append_window_end (GstSourceBuffer * self); + +GST_MSE_API +gboolean gst_source_buffer_append_buffer (GstSourceBuffer * self, + GstBuffer * buf, GError ** error); + +GST_MSE_API +gboolean gst_source_buffer_abort (GstSourceBuffer * self, GError ** error); + +GST_MSE_API +gboolean gst_source_buffer_change_content_type (GstSourceBuffer * self, + const gchar * type, + GError ** error); + +GST_MSE_API +gboolean gst_source_buffer_remove (GstSourceBuffer * self, GstClockTime start, + GstClockTime end, GError ** error); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h new file mode 100644 index 0000000000..264fb35e6a --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist-private.h @@ -0,0 +1,64 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include "gstsourcebufferlist.h" +#include "gstsourcebuffer.h" + +G_BEGIN_DECLS + +GST_MSE_PRIVATE +GstSourceBufferList *gst_source_buffer_list_new (void); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_list_contains (GstSourceBufferList * self, + GstSourceBuffer * buf); + +GST_MSE_PRIVATE +void gst_source_buffer_list_append (GstSourceBufferList * self, + GstSourceBuffer * buf); + +GST_MSE_PRIVATE +gboolean gst_source_buffer_list_remove (GstSourceBufferList * self, + GstSourceBuffer * buf); + +GST_MSE_PRIVATE +void gst_source_buffer_list_remove_all (GstSourceBufferList * self); + +GST_MSE_PRIVATE +void gst_source_buffer_list_notify_freeze (GstSourceBufferList * self); + +GST_MSE_PRIVATE +void gst_source_buffer_list_notify_cancel (GstSourceBufferList * self); + +GST_MSE_PRIVATE +void gst_source_buffer_list_notify_added (GstSourceBufferList * self); + +GST_MSE_PRIVATE +void gst_source_buffer_list_notify_removed (GstSourceBufferList * self); + +GST_MSE_PRIVATE +void gst_source_buffer_list_notify_thaw (GstSourceBufferList * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c new file mode 100644 index 0000000000..db1f9d038d --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.c @@ -0,0 +1,461 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstsourcebufferlist + * @title: GstSourceBufferList + * @short_description: Source Buffer List + * @include: mse/mse.h + * @symbols: + * - GstSourceBufferList + * + * The Source Buffer List is a list of #GstSourceBuffers that can be + * indexed numerically and monitored for changes. The list itself cannot be + * modified through this interface, though the Source Buffers it holds can be + * modified after retrieval. + * + * It is used by #GstMediaSource to provide direct access to its child + * #GstSourceBuffers through #GstMediaSource:source-buffers as well as + * informing clients which of the Source Buffers are active through + * #GstMediaSource:active-source-buffers. + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstsourcebufferlist.h" +#include "gstsourcebufferlist-private.h" + +#include "gstmseeventqueue-private.h" + +typedef struct +{ + gboolean frozen; + gboolean added; + gboolean removed; +} PendingNotifications; + +/** + * GstSourceBufferList: + * + * Since: 1.24 + */ +struct _GstSourceBufferList +{ + GstObject parent_instance; + + GPtrArray *buffers; + + GstMseEventQueue *event_queue; + + PendingNotifications pending_notifications; +}; + +G_DEFINE_TYPE (GstSourceBufferList, gst_source_buffer_list, GST_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_LENGTH, + N_PROPS, +}; + +typedef enum +{ + ON_SOURCEBUFFER_ADDED, + ON_SOURCEBUFFER_REMOVED, + N_SIGNALS, +} SourceBufferListEvent; + +typedef struct +{ + GstDataQueueItem item; + SourceBufferListEvent event; +} SourceBufferListEventItem; + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS] = { 0 }; + +static gboolean +is_frozen (GstSourceBufferList * self) +{ + return g_atomic_int_get (&self->pending_notifications.frozen); +} + +static void +set_frozen (GstSourceBufferList * self) +{ + g_atomic_int_set (&self->pending_notifications.frozen, TRUE); +} + +static void +clear_frozen (GstSourceBufferList * self) +{ + g_atomic_int_set (&self->pending_notifications.frozen, FALSE); +} + +static void +set_pending_added (GstSourceBufferList * self) +{ + g_atomic_int_set (&self->pending_notifications.added, TRUE); +} + +static void +set_pending_removed (GstSourceBufferList * self) +{ + g_atomic_int_set (&self->pending_notifications.removed, TRUE); +} + +static gboolean +clear_pending_added (GstSourceBufferList * self) +{ + return g_atomic_int_and (&self->pending_notifications.added, FALSE); +} + +static gboolean +clear_pending_removed (GstSourceBufferList * self) +{ + return g_atomic_int_and (&self->pending_notifications.removed, FALSE); +} + +static void +schedule_event (GstSourceBufferList * self, SourceBufferListEvent event) +{ + SourceBufferListEventItem item = { + .item = {.destroy = g_free,.visible = TRUE,.size = 1,.object = NULL}, + .event = event, + }; + + gst_mse_event_queue_push (self->event_queue, g_memdup2 (&item, + sizeof (SourceBufferListEventItem))); +} + +static void +dispatch_event (SourceBufferListEventItem * item, GstSourceBufferList * self) +{ + g_signal_emit (self, signals[item->event], 0); +} + +static void +call_source_buffer_added (GstSourceBufferList * self) +{ + if (is_frozen (self)) { + set_pending_added (self); + } else { + clear_pending_added (self); + schedule_event (self, ON_SOURCEBUFFER_ADDED); + } +} + +static void +call_source_buffer_removed (GstSourceBufferList * self) +{ + if (is_frozen (self)) { + set_pending_removed (self); + } else { + clear_pending_removed (self); + schedule_event (self, ON_SOURCEBUFFER_REMOVED); + } +} + +GstSourceBufferList * +gst_source_buffer_list_new (void) +{ + return gst_object_ref_sink (g_object_new (GST_TYPE_SOURCE_BUFFER_LIST, NULL)); +} + +static void +gst_source_buffer_list_dispose (GObject * object) +{ + GstSourceBufferList *self = (GstSourceBufferList *) object; + gst_clear_object (&self->event_queue); + + G_OBJECT_CLASS (gst_source_buffer_list_parent_class)->dispose (object); +} + +static void +gst_source_buffer_list_finalize (GObject * object) +{ + GstSourceBufferList *self = (GstSourceBufferList *) object; + + g_clear_pointer (&self->buffers, g_ptr_array_unref); + + G_OBJECT_CLASS (gst_source_buffer_list_parent_class)->finalize (object); +} + +static void +gst_source_buffer_list_get_property (GObject * object, guint prop_id, GValue + * value, GParamSpec * pspec) +{ + GstSourceBufferList *self = GST_SOURCE_BUFFER_LIST (object); + + switch (prop_id) { + case PROP_LENGTH: + g_value_set_uint (value, gst_source_buffer_list_get_length (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_source_buffer_list_class_init (GstSourceBufferListClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = GST_DEBUG_FUNCPTR (gst_source_buffer_list_dispose); + oclass->finalize = GST_DEBUG_FUNCPTR (gst_source_buffer_list_finalize); + oclass->get_property = + GST_DEBUG_FUNCPTR (gst_source_buffer_list_get_property); + + /** + * GstSourceBufferList:length: + * + * The number of #GstSourceBuffers contained by this structure + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length) + * + * Since: 1.24 + */ + properties[PROP_LENGTH] = g_param_spec_ulong ("length", + "Length", + "The number of SourceBuffers contained by this structure", + 0, G_MAXULONG, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPS, properties); + + /** + * GstSourceBufferList::on-sourcebuffer-added: + * @self: The #GstSourceBufferList that has just added a + * #GstSourceBuffer + * + * Emitted when a #GstSourceBuffer has been added to this list. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onaddsourcebuffer) + * + * Since: 1.24 + */ + signals[ON_SOURCEBUFFER_ADDED] = g_signal_new ("on-sourcebuffer-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * GstSourceBufferList::on-sourcebuffer-removed: + * @self: The #GstSourceBufferList that has just removed a + * #GstSourceBuffer + * + * Emitted when a #GstSourceBuffer has been removed from this list. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-onremovesourcebuffer) + * + * Since: 1.24 + */ + signals[ON_SOURCEBUFFER_REMOVED] = g_signal_new ("on-sourcebuffer-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void +gst_source_buffer_list_init (GstSourceBufferList * self) +{ + set_frozen (self); + self->buffers = g_ptr_array_new_with_free_func (gst_object_unref); + self->event_queue = + gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self); + clear_pending_added (self); + clear_pending_removed (self); + clear_frozen (self); +} + +/** + * gst_source_buffer_list_index: + * @self: #GstSourceBufferList instance + * @index: index of requested Source Buffer + * + * Retrieves the #GstSourceBuffer at @index from @self. If @index is greater than + * the highest index in the list, it will return `NULL`. + * + * [Specification](https://www.w3.org/TR/media-source-2/#dfn-sourcebufferlist-getter) + * + * Returns: (transfer full) (nullable): The requested #GstSourceBuffer or `NULL` + * Since: 1.24 + */ +GstSourceBuffer * +gst_source_buffer_list_index (GstSourceBufferList * self, guint index) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), NULL); + if (index >= gst_source_buffer_list_get_length (self)) + return NULL; + return gst_object_ref (g_ptr_array_index (self->buffers, index)); +} + +/** + * gst_source_buffer_list_get_length: + * @self: #GstSourceBufferList instance + * + * [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebufferlist-length) + * + * Returns: The number of #GstSourceBuffer objects in the list + * Since: 1.24 + */ +guint +gst_source_buffer_list_get_length (GstSourceBufferList * self) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), 0); + return self->buffers->len; +} + +gboolean +gst_source_buffer_list_contains (GstSourceBufferList * self, + GstSourceBuffer * buf) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), FALSE); + g_return_val_if_fail (GST_IS_SOURCE_BUFFER (buf), FALSE); + return g_ptr_array_find (self->buffers, buf, NULL); +} + +void +gst_source_buffer_list_append (GstSourceBufferList * self, + GstSourceBuffer * buf) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + g_ptr_array_add (self->buffers, gst_object_ref (buf)); + call_source_buffer_added (self); +} + +gboolean +gst_source_buffer_list_remove (GstSourceBufferList * self, + GstSourceBuffer * buf) +{ + g_return_val_if_fail (GST_IS_SOURCE_BUFFER_LIST (self), FALSE); + gboolean removed = g_ptr_array_remove (self->buffers, buf); + if (removed) { + call_source_buffer_removed (self); + return TRUE; + } else { + return FALSE; + } +} + +void +gst_source_buffer_list_remove_all (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + if (self->buffers->len < 1) { + return; + } + g_ptr_array_set_size (self->buffers, 0); + call_source_buffer_removed (self); +} + +/** + * gst_source_buffer_list_notify_freeze: + * @self: #GstSourceBufferList instance + * + * Prevents any notifications from being emitted by @self until the next call to + * gst_source_buffer_list_notify_thaw(). + * + */ +void +gst_source_buffer_list_notify_freeze (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + clear_pending_added (self); + clear_pending_removed (self); + set_frozen (self); +} + +/** + * gst_source_buffer_list_notify_cancel: + * @self: #GstSourceBufferList instance + * + * Cancels any pending notifications that are waiting between calls to + * gst_source_buffer_list_notify_freeze() and + * gst_source_buffer_list_notify_thaw(). + * + */ +void +gst_source_buffer_list_notify_cancel (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + clear_pending_added (self); + clear_pending_removed (self); +} + +/** + * gst_source_buffer_list_notify_added: + * @self: #GstSourceBufferList instance + * + * Explicitly notifies subscribers to the ::on-sourcebuffer-added signal that an + * item has been added to @self. + * + */ +void +gst_source_buffer_list_notify_added (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + g_return_if_fail (!is_frozen (self)); + call_source_buffer_added (self); +} + +/** + * gst_source_buffer_list_notify_removed: + * @self: #GstSourceBufferList instance + * + * Explicitly notifies subscribers to the ::on-sourcebuffer-removed signal that + * an item has been removed from @self. + * + */ +void +gst_source_buffer_list_notify_removed (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + g_return_if_fail (!is_frozen (self)); + call_source_buffer_removed (self); +} + +/** + * gst_source_buffer_list_notify_thaw: + * @self: #GstSourceBufferList instance + * + * Resumes notifications emitted from @self after a call to + * gst_source_buffer_list_notify_freeze(). If any notifications are pending, + * they will be emitted as a result of this call. To prevent pending + * notifications from being published, use + * gst_source_buffer_list_notify_cancel() before calling this method. + * + */ +void +gst_source_buffer_list_notify_thaw (GstSourceBufferList * self) +{ + g_return_if_fail (GST_IS_SOURCE_BUFFER_LIST (self)); + clear_frozen (self); + if (clear_pending_added (self)) { + g_signal_emit (self, signals[ON_SOURCEBUFFER_ADDED], 0); + } + if (clear_pending_removed (self)) { + g_signal_emit (self, signals[ON_SOURCEBUFFER_REMOVED], 0); + } +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h new file mode 100644 index 0000000000..d80ee52078 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/gstsourcebufferlist.h @@ -0,0 +1,44 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +#include "gstsourcebuffer.h" + +G_BEGIN_DECLS + +#define GST_TYPE_SOURCE_BUFFER_LIST (gst_source_buffer_list_get_type()) + +GST_MSE_API +G_DECLARE_FINAL_TYPE (GstSourceBufferList, gst_source_buffer_list, GST, + SOURCE_BUFFER_LIST, GstObject); + +GST_MSE_API +GstSourceBuffer *gst_source_buffer_list_index (GstSourceBufferList * self, + guint index); +GST_MSE_API +guint gst_source_buffer_list_get_length (GstSourceBufferList * self); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build b/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build new file mode 100644 index 0000000000..fba8613ff0 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/meson.build @@ -0,0 +1,149 @@ +gstmse_headers_private = files( + 'gstappendpipeline-private.h', + 'gstmediasourcesamplemap-private.h', + 'gstmediasourcetrackbuffer-private.h', + 'gstmediasourcetrack-private.h', + 'gstmseeventqueue-private.h', + 'gstmselogging-private.h', + 'gstmsemediatype-private.h', +) + +gstmse_sources_private = files( + 'gstappendpipeline.c', + 'gstmediasourcesamplemap.c', + 'gstmediasourcetrackbuffer.c', + 'gstmediasourcetrack.c', + 'gstmseeventqueue.c', + 'gstmselogging.c', + 'gstmsemediatype.c', +) + +gstmse_headers_public = files( + 'gstmediasource.h', + 'gstsourcebuffer.h', + 'gstsourcebufferlist.h', + 'gstmsesrc.h', + 'mse.h', + 'mse-prelude.h', +) + +gstmse_sources_public = files( + 'gstmediasource.c', + 'gstmsesrc.c', + 'gstsourcebuffer.c', + 'gstsourcebufferlist.c', +) + +gstmse_header_dir = 'gstreamer-' + api_version + '/gst/mse/' + +gstmse_enums_private = gnome.mkenums_simple('mse-enumtypes-private', + sources : gstmse_headers_private, + body_prefix : '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif', + header_prefix : '#include ', + decorator: 'GST_MSE_PRIVATE', + install_header: false, +) + +gstmse_enums = gnome.mkenums_simple('mse-enumtypes', + sources : gstmse_headers_public, + body_prefix : '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif', + header_prefix : '#include ', + decorator: 'GST_MSE_API', + install_header: true, + install_dir : join_paths(get_option('includedir'), gstmse_header_dir), +) + +gstmse_deps = [gstbase_dep, gstapp_dep] + +gstmse_enums_private_c = gstmse_enums_private[0] +gstmse_enums_private_h = gstmse_enums_private[1] + +gstmse_enums_c = gstmse_enums[0] +gstmse_enums_h = gstmse_enums[1] + +gstmse_c_args = gst_plugins_bad_args + [ + '-DBUILDING_GST_MSE', + '-DGST_USE_UNSTABLE_API', + '-DG_LOG_DOMAIN="GStreamer-MSE"', +] + +gstmse_sources_all = [ + gstmse_sources_private, + gstmse_sources_public, + gstmse_enums_c, + gstmse_enums_h, + gstmse_enums_private_c, + gstmse_enums_private_h, +] + +gstmse_private_test = library('gstmse-private-test', + gstmse_sources_all, + c_args : [gstmse_c_args, '-DBUILDING_GST_MSE_TEST'], + include_directories : [configinc, libsinc], + dependencies : gstmse_deps, +) + +gstmse_private_test_dep = declare_dependency( + compile_args : [gstmse_c_args, '-DBUILDING_GST_MSE_TEST'], + link_with : [gstmse_private_test], + include_directories : [libsinc], +) + +gstmse = library('gstmse-' + api_version, + gstmse_sources_all, + c_args : gstmse_c_args, + include_directories : [configinc, libsinc], + version : libversion, + soversion : soversion, + darwin_versions : osxversion, + install : true, + dependencies : gstmse_deps, +) + +pkg_name = 'gstreamer-mse-' + api_version +gst_libraries += [[pkg_name, {'lib': gstmse}]] +pkgconfig.generate(gstmse, + libraries : [gst_dep], + variables : pkgconfig_variables, + subdirs : pkgconfig_subdirs, + name : pkg_name, + description : 'GStreamer Support for W3C Media Source Extensions', +) + +library_def = {'lib': gstmse} +gen_sources = [] +if build_gir + gir = { + 'sources' : [ + gstmse_sources_public, + gstmse_headers_public, + gstmse_enums_h, + gstmse_enums_c, + ], + 'namespace' : 'GstMse', + 'nsversion' : api_version, + 'identifier_prefix' : 'Gst', + 'symbol_prefix' : 'gst', + 'export_packages' : pkg_name, + 'includes' : ['Gst-1.0'], + 'install' : true, + 'extra_args' : gir_init_section + ['-DGST_USE_UNSTABLE_API'] + ['--c-include=gst/mse/mse.h'], + 'dependencies' : gstmse_deps, + } + library_def += {'gir': [gir]} + if not static_build + mse_gir = gnome.generate_gir(gstmse, kwargs: gir) + library_def += {'gir_targets': library_def.get('gir_targets', []) + [mse_gir]} + gen_sources += mse_gir + endif +endif +gst_libraries += [[pkg_name, library_def]] + +gstmse_dep = declare_dependency( + link_with : [gstmse], + include_directories : [libsinc], + dependencies : gstmse_deps, + sources: [gstmse_enums_h], +) +install_headers(gstmse_headers_public, subdir: gstmse_header_dir) +meson.override_dependency(pkg_name, gstmse_dep) diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h new file mode 100644 index 0000000000..eb807e1874 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse-prelude.h @@ -0,0 +1,48 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#ifndef GST_MSE_API +# if defined(BUILDING_GST_MSE) && defined(GST_API_EXPORT) +# define GST_MSE_API GST_API_EXPORT +# elif defined(GST_API_IMPORT) +# define GST_MSE_API GST_API_IMPORT +# else +# define GST_MSE_API +# endif +#endif + +#ifndef GST_MSE_PRIVATE +# if defined(BUILDING_GST_MSE_TEST) +# define GST_MSE_PRIVATE GST_MSE_API +# else +# define GST_MSE_PRIVATE G_GNUC_INTERNAL +# endif +#endif + +#ifndef GST_USE_UNSTABLE_API +#warning "The MSE library from gst-plugins-bad is an unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h new file mode 100644 index 0000000000..343213e17a --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mse/mse.h @@ -0,0 +1,32 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include "mse-prelude.h" +#include +#include "gstmsesrc.h" +#include "gstmediasource.h" +#include "gstsourcebufferlist.h" +#include "gstsourcebuffer.h" diff --git a/subprojects/gst-plugins-bad/gst/meson.build b/subprojects/gst-plugins-bad/gst/meson.build index 81f8f62092..4232a34aef 100644 --- a/subprojects/gst-plugins-bad/gst/meson.build +++ b/subprojects/gst-plugins-bad/gst/meson.build @@ -8,7 +8,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux', 'geometrictransform', 'id3tag', 'insertbin', 'inter', 'interlace', 'ivfparse', 'ivtc', 'jp2kdecimator', 'jpegformat', 'librfb', 'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux', - 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy', + 'mse', 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy', 'rawparse', 'removesilence', 'rist', 'rtmp2', 'rtp', 'sdp', 'segmentclip', 'siren', 'smooth', 'speed', 'subenc', 'switchbin', 'timecode', 'transcode', 'unixfd', 'videofilters', diff --git a/subprojects/gst-plugins-bad/gst/mse/gstmse.c b/subprojects/gst-plugins-bad/gst/mse/gstmse.c new file mode 100644 index 0000000000..7e8938f42c --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/mse/gstmse.c @@ -0,0 +1,49 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * plugin-mse: + * + * Since: 1.24 + */ + +#include "gstmse.h" + +/** + * SECTION:element-msesrc + * @title: msesrc + * + * Since: 1.24 + */ +GST_ELEMENT_REGISTER_DEFINE (msesrc, "msesrc", GST_RANK_NONE, GST_TYPE_MSE_SRC); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return GST_ELEMENT_REGISTER (msesrc, plugin); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + mse, + "W3C Media Source Extensions Support", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-bad/gst/mse/gstmse.h b/subprojects/gst-plugins-bad/gst/mse/gstmse.h new file mode 100644 index 0000000000..0500b65963 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/mse/gstmse.h @@ -0,0 +1,33 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +GST_ELEMENT_REGISTER_DECLARE (msesrc); diff --git a/subprojects/gst-plugins-bad/gst/mse/meson.build b/subprojects/gst-plugins-bad/gst/mse/meson.build new file mode 100644 index 0000000000..e4ac5e72d7 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/mse/meson.build @@ -0,0 +1,12 @@ +mse_sources = ['gstmse.c'] + +gstmse_plugin = library('gstmse', + mse_sources, + c_args: gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories: [configinc], + dependencies : [gstbase_dep, gstmse_dep], + install : true, + install_dir : plugins_install_dir, +) + +plugins += [gstmse_plugin] diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 8fe3b8fc7f..d4f087791c 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -45,6 +45,7 @@ option('mpegdemux', type : 'feature', value : 'auto') option('mpegpsmux', type : 'feature', value : 'auto') option('mpegtsdemux', type : 'feature', value : 'auto') option('mpegtsmux', type : 'feature', value : 'auto') +option('mse', type : 'feature', value : 'auto') option('mxf', type : 'feature', value : 'auto') option('netsim', type : 'feature', value : 'auto') option('onvif', type : 'feature', value : 'auto') diff --git a/subprojects/gst-plugins-bad/tests/check/libs/mse.c b/subprojects/gst-plugins-bad/tests/check/libs/mse.c new file mode 100644 index 0000000000..f3ae4bed16 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/check/libs/mse.c @@ -0,0 +1,1119 @@ +/* GStreamer + * + * SPDX-License-Identifier: LGPL-2.1 + * + * Copyright (C) 2022, 2023 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_VALGRIND +# include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +static GstCheckLogFilter * +add_log_filter (GLogLevelFlags level, const gchar * regex) +{ + GRegex *gregex = g_regex_new (regex, 0, 0, NULL); + return gst_check_add_log_filter ("GStreamer-MSE", level, gregex, NULL, NULL, + NULL); +} + +static gchar * +test_mp4_path (void) +{ + return g_build_filename (GST_TEST_FILES_PATH, "mse.mp4", NULL); +} + +static gchar * +test_webm_path (void) +{ + return g_build_filename (GST_TEST_FILES_PATH, "mse.webm", NULL); +} + +static GstMediaSource * +opened_media_source (void) +{ + GstMediaSource *media_source = gst_media_source_new (); + media_source->ready_state = GST_MEDIA_SOURCE_READY_STATE_OPEN; + return media_source; +} + +static GstSample * +new_empty_sample_full (GstClockTime dts, GstClockTime pts, + GstClockTime duration, GstBufferFlags flags, GstCaps * caps, + GstSegment * segment, GstStructure * info) +{ + GstBuffer *buffer = gst_buffer_new (); + GST_BUFFER_DTS (buffer) = dts; + GST_BUFFER_PTS (buffer) = pts; + GST_BUFFER_DURATION (buffer) = duration; + GST_BUFFER_FLAGS (buffer) = flags; + GstSample *sample = gst_sample_new (buffer, caps, segment, info); + gst_buffer_unref (buffer); + return sample; +} + +static GstSample * +new_empty_sample_with_timing (GstClockTime dts, GstClockTime pts, + GstClockTime duration) +{ + return new_empty_sample_full (dts, pts, duration, 0, NULL, NULL, NULL); +} + +static GstSample * +new_sample_with_bytes_and_timing (GBytes * bytes, GstClockTime dts, + GstClockTime pts, GstClockTime duration) +{ + GstBuffer *buffer = gst_buffer_new_wrapped_bytes (bytes); + GST_BUFFER_DTS (buffer) = dts; + GST_BUFFER_PTS (buffer) = pts; + GST_BUFFER_DURATION (buffer) = duration; + GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL); + gst_buffer_unref (buffer); + g_bytes_unref (bytes); + return sample; +} + +GST_START_TEST (test_create_and_free) +{ + GstMediaSource *media_source = gst_media_source_new (); + fail_unless (GST_IS_MEDIA_SOURCE (media_source)); + gst_check_object_destroyed_on_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_create_initial_state) +{ + GstMediaSource *media_source = gst_media_source_new (); + + GstSourceBufferList *buffers = + gst_media_source_get_source_buffers (media_source); + GstSourceBufferList *active_buffers = + gst_media_source_get_active_source_buffers (media_source); + + fail_unless (gst_media_source_get_ready_state (media_source) == + GST_MEDIA_SOURCE_READY_STATE_CLOSED); + fail_unless (gst_source_buffer_list_get_length (buffers) == 0); + fail_unless (gst_source_buffer_list_get_length (active_buffers) == 0); + fail_unless (gst_media_source_get_position (media_source) == + GST_CLOCK_TIME_NONE); + + gst_object_unref (media_source); + gst_object_unref (buffers); + gst_object_unref (active_buffers); +} + +GST_END_TEST; + +GST_START_TEST (test_add_source_buffer_with_content_type_null) +{ + add_log_filter (G_LOG_LEVEL_CRITICAL, + "^.*_add_source_buffer: assertion 'type != NULL' failed"); + + GstMediaSource *media_source = gst_media_source_new (); + + g_assert_null (gst_media_source_add_source_buffer (media_source, NULL, NULL)); + + gst_object_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_add_source_buffer_with_content_type_empty) +{ + GError *error = NULL; + GstMediaSource *media_source = gst_media_source_new (); + GstSourceBuffer *source_buffer = + gst_media_source_add_source_buffer (media_source, "", &error); + + g_assert_null (source_buffer); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_add_source_buffer_with_content_type_fake) +{ + GError *error = NULL; + GstMediaSource *media_source = gst_media_source_new (); + GstSourceBuffer *source_buffer = + gst_media_source_add_source_buffer (media_source, "fake/type", &error); + + g_assert_null (source_buffer); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_add_source_buffer_to_unopened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = gst_media_source_new (); + GstSourceBuffer *source_buffer = + gst_media_source_add_source_buffer (media_source, "video/webm", &error); + + g_assert_null (source_buffer); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_INVALID_STATE); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_add_source_buffer_to_opened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = opened_media_source (); + GstSourceBufferList *buffers = + gst_media_source_get_source_buffers (media_source); + guint n_buffers_before = gst_source_buffer_list_get_length (buffers); + GstSourceBuffer *source_buffer = + gst_media_source_add_source_buffer (media_source, "video/webm", &error); + guint n_buffers_after = gst_source_buffer_list_get_length (buffers); + + fail_unless (GST_IS_SOURCE_BUFFER (source_buffer)); + g_assert_no_error (error); + fail_unless (n_buffers_before < n_buffers_after); + + g_object_unref (media_source); + g_object_unref (buffers); + g_object_unref (source_buffer); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_remove_source_buffer_from_unrelated_media_source) +{ + GError *error = NULL; + GstMediaSource *a = opened_media_source (); + GstMediaSource *b = opened_media_source (); + GstSourceBuffer *buffer_in_b = + gst_media_source_add_source_buffer (b, "video/webm", &error); + + gst_media_source_remove_source_buffer (a, buffer_in_b, &error); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_NOT_FOUND); + + gst_object_unref (a); + gst_object_unref (b); + gst_object_unref (buffer_in_b); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_remove_source_buffer_from_parent_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = opened_media_source (); + GstSourceBufferList *buffers = + gst_media_source_get_source_buffers (media_source); + GstSourceBuffer *buffer = + gst_media_source_add_source_buffer (media_source, "video/webm", &error); + + guint n_buffers_before = gst_source_buffer_list_get_length (buffers); + gst_media_source_remove_source_buffer (media_source, buffer, &error); + guint n_buffers_after = gst_source_buffer_list_get_length (buffers); + + g_assert_no_error (error); + fail_unless (n_buffers_before > n_buffers_after); + + gst_object_unref (media_source); + gst_object_unref (buffers); + gst_object_unref (buffer); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_set_live_seekable_range_on_unopened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = gst_media_source_new (); + + gst_media_source_set_live_seekable_range (media_source, 0, 1, &error); + + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_INVALID_STATE); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_set_backwards_live_seekable_range_on_opened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = opened_media_source (); + + gst_media_source_set_live_seekable_range (media_source, 2, 1, &error); + + GstMediaSourceRange range = { + .start = GST_CLOCK_TIME_NONE, + .end = GST_CLOCK_TIME_NONE, + }; + gst_media_source_get_live_seekable_range (media_source, &range); + + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE); + fail_unless (range.start == 0); + fail_unless (range.end == 0); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_set_live_seekable_range_on_opened_media_source) +{ + GError *error = NULL; + GstClockTime start = 1, end = 2; + GstMediaSource *media_source = opened_media_source (); + + gst_media_source_set_live_seekable_range (media_source, start, end, &error); + + GstMediaSourceRange range = { + .start = GST_CLOCK_TIME_NONE, + .end = GST_CLOCK_TIME_NONE, + }; + gst_media_source_get_live_seekable_range (media_source, &range); + + g_assert_no_error (error); + fail_unless (range.start == start); + fail_unless (range.end == end); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_clear_live_seekable_range_on_unopened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = gst_media_source_new (); + + gst_media_source_clear_live_seekable_range (media_source, &error); + + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_INVALID_STATE); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_clear_live_seekable_range_on_opened_media_source) +{ + GError *error = NULL; + GstMediaSource *media_source = opened_media_source (); + gst_media_source_set_live_seekable_range (media_source, 1, 2, NULL); + + gst_media_source_clear_live_seekable_range (media_source, &error); + GstMediaSourceRange range = { + .start = GST_CLOCK_TIME_NONE, + .end = GST_CLOCK_TIME_NONE, + }; + gst_media_source_get_live_seekable_range (media_source, &range); + + g_assert_no_error (error); + fail_unless (range.start == 0); + fail_unless (range.end == 0); + + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +GST_START_TEST (test_append_pipeline_create_and_free) +{ + GError *error = NULL; + GstAppendPipeline *pipeline = gst_append_pipeline_new (NULL, NULL, &error); + g_assert_no_error (error); + fail_unless (GST_IS_APPEND_PIPELINE (pipeline)); + gst_check_object_destroyed_on_unref (pipeline); + g_clear_error (&error); +} + +GST_END_TEST; + +typedef struct +{ + GMutex mutex; + GCond eos_cond; + GCond error_cond; +} AppendPipelineTestContext; + +static void +test_append_pipeline_eos (GstAppendPipeline * pipeline, + GstMediaSourceTrack * track, gpointer user_data) +{ + AppendPipelineTestContext *context = user_data; + g_mutex_lock (&context->mutex); + g_cond_signal (&context->eos_cond); + g_mutex_unlock (&context->mutex); + GST_DEBUG_OBJECT (pipeline, "signalled eos"); +} + +static void +test_append_pipeline_error (GstAppendPipeline * pipeline, gpointer user_data) +{ + AppendPipelineTestContext *context = user_data; + g_mutex_lock (&context->mutex); + g_cond_signal (&context->error_cond); + g_mutex_unlock (&context->mutex); + GST_DEBUG_OBJECT (pipeline, "signalled error"); +} + +static void +test_append_pipeline_await_eos (GstAppendPipeline * pipeline, + AppendPipelineTestContext * context) +{ + GST_DEBUG_OBJECT (pipeline, "waiting for eos"); + g_mutex_lock (&context->mutex); + while (!gst_append_pipeline_get_eos (pipeline)) { + g_cond_wait (&context->eos_cond, &context->mutex); + } + g_mutex_unlock (&context->mutex); + GST_DEBUG_OBJECT (pipeline, "received eos"); +} + +static void +test_append_pipeline_await_error (GstAppendPipeline * pipeline, + AppendPipelineTestContext * context) +{ + GST_DEBUG_OBJECT (pipeline, "waiting for error"); + g_mutex_lock (&context->mutex); + while (!gst_append_pipeline_get_failed (pipeline)) { + g_cond_wait (&context->error_cond, &context->mutex); + } + g_mutex_unlock (&context->mutex); + GST_DEBUG_OBJECT (pipeline, "received error"); +} + +static void +test_append_pipeline (const gchar * filename) +{ + AppendPipelineTestContext context = { 0 }; + GstAppendPipelineCallbacks callbacks = { + .eos = test_append_pipeline_eos, + }; + GstAppendPipeline *pipeline = + gst_append_pipeline_new (&callbacks, &context, NULL); + GError *error = NULL; + + gchar *data; + gsize length; + + g_file_get_contents (filename, &data, &length, &error); + g_assert_no_error (error); + + fail_unless (gst_append_pipeline_append (pipeline, + gst_buffer_new_wrapped (data, length)) == GST_FLOW_OK); + + gst_append_pipeline_eos (pipeline); + + test_append_pipeline_await_eos (pipeline, &context); + + fail_if (gst_append_pipeline_get_failed (pipeline)); + + gst_object_unref (pipeline); + g_clear_error (&error); +} + +GST_START_TEST (test_append_pipeline_mp4) +{ + gchar *filename = test_mp4_path (); + test_append_pipeline (filename); + g_free (filename); +} + +GST_END_TEST; + +GST_START_TEST (test_append_pipeline_webm) +{ + gchar *filename = test_webm_path (); + test_append_pipeline (filename); + g_free (filename); +} + +GST_END_TEST; + +static GstAppendPipeline * +failed_append_pipeline (GstAppendPipelineCallbacks * callbacks, + AppendPipelineTestContext * context) +{ + GstAppendPipeline *pipeline = + gst_append_pipeline_new (callbacks, context, NULL); + + gst_append_pipeline_fail (pipeline); + + return pipeline; +} + +GST_START_TEST (test_append_pipeline_invalid_data_triggers_error) +{ + AppendPipelineTestContext context = { 0 }; + GstAppendPipelineCallbacks callbacks = { + .eos = test_append_pipeline_eos, + .error = test_append_pipeline_error, + }; + GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context); + + test_append_pipeline_await_error (pipeline, &context); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_append_pipeline_invalid_data_triggers_eos) +{ + AppendPipelineTestContext context = { 0 }; + GstAppendPipelineCallbacks callbacks = { + .eos = test_append_pipeline_eos, + .error = test_append_pipeline_error, + }; + GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context); + + test_append_pipeline_await_eos (pipeline, &context); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_append_pipeline_reset_recovery) +{ + AppendPipelineTestContext context = { 0 }; + GstAppendPipelineCallbacks callbacks = { + .eos = test_append_pipeline_eos, + .error = test_append_pipeline_error, + }; + GstAppendPipeline *pipeline = failed_append_pipeline (&callbacks, &context); + + test_append_pipeline_await_error (pipeline, &context); + fail_unless (gst_append_pipeline_get_failed (pipeline)); + + fail_unless (gst_append_pipeline_reset (pipeline)); + fail_if (gst_append_pipeline_get_failed (pipeline)); + + gchar *data; + gsize length; + { + GError *error = NULL; + gchar *filename = test_webm_path (); + g_file_get_contents (filename, &data, &length, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_free (filename); + } + + fail_unless (gst_append_pipeline_append (pipeline, + gst_buffer_new_wrapped (data, length)) == GST_FLOW_OK); + + gst_append_pipeline_eos (pipeline); + + test_append_pipeline_await_eos (pipeline, &context); + + fail_if (gst_append_pipeline_get_failed (pipeline)); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_track_create_and_free) +{ + GstMediaSourceTrack *track = + gst_media_source_track_new (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, ""); + fail_unless (GST_IS_MEDIA_SOURCE_TRACK (track)); + gst_check_object_destroyed_on_unref (track); +} + +GST_END_TEST; + +GST_START_TEST (test_track_create_with_invalid_type) +{ + add_log_filter (G_LOG_LEVEL_CRITICAL, + "^.*track_new_full: assertion .*type .* failed"); + + g_assert_null (gst_media_source_track_new (-1, "")); + g_assert_null (gst_media_source_track_new (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER + + 1, "")); +} + +GST_END_TEST; + +GST_START_TEST (test_track_push_with_adequate_space) +{ + GstMediaSourceTrack *track = + gst_media_source_track_new_with_size (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, + "", 1); + GstBuffer *buffer = gst_buffer_new (); + GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL); + gboolean result = gst_media_source_track_push (track, sample); + fail_unless (result); + gst_buffer_unref (buffer); + gst_object_unref (track); +} + +GST_END_TEST; + +GST_START_TEST (test_track_push_with_inadequate_space) +{ + GstMediaSourceTrack *track = + gst_media_source_track_new_with_size (GST_MEDIA_SOURCE_TRACK_TYPE_OTHER, + "", 0); + GstBuffer *buffer = gst_buffer_new (); + GstSample *sample = gst_sample_new (buffer, NULL, NULL, NULL); + gboolean result = gst_media_source_track_try_push (track, sample); + fail_if (result); + gst_sample_unref (sample); + gst_buffer_unref (buffer); + gst_object_unref (track); +} + +GST_END_TEST; + +GST_START_TEST (test_track_buffer_empty) +{ + GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new (); + + GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer); + fail_unless_equals_uint64 (ranges->len, 0); + + gst_object_unref (buffer); + g_array_unref (ranges); +} + +GST_END_TEST; + +GST_START_TEST (test_track_buffer_single_span) +{ + GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new (); + + GstSample *sample = new_empty_sample_with_timing (0, 0, 1); + gst_media_source_track_buffer_add (buffer, sample); + + GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer); + fail_unless_equals_uint64 (ranges->len, 1); + + GstMediaSourceRange range = g_array_index (ranges, GstMediaSourceRange, 0); + fail_unless_equals_uint64 (range.start, 0); + fail_unless_equals_uint64 (range.end, 1); + + gst_sample_unref (sample); + gst_object_unref (buffer); + g_array_unref (ranges); +} + +GST_END_TEST; + +GST_START_TEST (test_track_buffer_continuous_span) +{ + GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new (); + + GstClockTime a_start = 0; + GstClockTime a_duration = GST_SECOND; + GstClockTime b_start = a_start + a_duration; + GstClockTime b_duration = a_duration; + GstSample *a = new_empty_sample_with_timing (a_start, a_start, a_duration); + GstSample *b = new_empty_sample_with_timing (b_start, b_start, b_duration); + gst_media_source_track_buffer_add (buffer, a); + gst_media_source_track_buffer_add (buffer, b); + + GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer); + fail_unless_equals_uint64 (ranges->len, 1); + + GstMediaSourceRange range = g_array_index (ranges, GstMediaSourceRange, 0); + fail_unless_equals_uint64 (range.start, a_start); + fail_unless_equals_uint64 (range.end, a_start + a_duration + b_duration); + + gst_sample_unref (a); + gst_sample_unref (b); + gst_object_unref (buffer); + g_array_unref (ranges); +} + +GST_END_TEST; + +GST_START_TEST (test_track_buffer_discontinuous_span) +{ + GstMediaSourceTrackBuffer *buffer = gst_media_source_track_buffer_new (); + + GstClockTime a_start = 0; + GstClockTime a_duration = GST_SECOND; + GstClockTime b_start = a_start + a_duration + GST_SECOND; + GstClockTime b_duration = a_duration; + GstSample *a = new_empty_sample_with_timing (a_start, a_start, a_duration); + GstSample *b = new_empty_sample_with_timing (b_start, b_start, b_duration); + gst_media_source_track_buffer_add (buffer, a); + gst_media_source_track_buffer_add (buffer, b); + + GArray *ranges = gst_media_source_track_buffer_get_ranges (buffer); + fail_unless_equals_uint64 (ranges->len, 2); + + GstMediaSourceRange range_a = g_array_index (ranges, GstMediaSourceRange, 0); + fail_unless_equals_uint64 (range_a.start, a_start); + fail_unless_equals_uint64 (range_a.end, a_start + a_duration); + + GstMediaSourceRange range_b = g_array_index (ranges, GstMediaSourceRange, 1); + fail_unless_equals_uint64 (range_b.start, b_start); + fail_unless_equals_uint64 (range_b.end, b_start + b_duration); + + gst_sample_unref (a); + gst_sample_unref (b); + gst_object_unref (buffer); + g_array_unref (ranges); +} + +GST_END_TEST; + +GST_START_TEST (test_source_buffer_generate_timestamps_mp4) +{ + GstMediaSource *media_source = opened_media_source (); + GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer + (media_source, "video/mp4", NULL); + + fail_unless_equals_uint64 (gst_source_buffer_get_append_mode (source_buffer), + GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS); + + gst_object_unref (source_buffer); + gst_object_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_source_buffer_generate_timestamps_aac) +{ + GstMediaSource *media_source = opened_media_source (); + GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer + (media_source, "audio/aac", NULL); + + fail_unless (GST_IS_SOURCE_BUFFER (source_buffer)); + + fail_unless_equals_uint64 (gst_source_buffer_get_append_mode (source_buffer), + GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE); + + gst_object_unref (source_buffer); + gst_object_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_source_buffer_change_content_type_null) +{ + GstMediaSource *media_source = opened_media_source (); + GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer + (media_source, "video/mp4", NULL); + + fail_unless (GST_IS_SOURCE_BUFFER (source_buffer)); + + GError *error = NULL; + gst_source_buffer_change_content_type (source_buffer, NULL, &error); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE); + + g_clear_error (&error); + gst_object_unref (source_buffer); + gst_object_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_source_buffer_change_content_type_empty) +{ + GstMediaSource *media_source = opened_media_source (); + GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer + (media_source, "video/mp4", NULL); + + fail_unless (GST_IS_SOURCE_BUFFER (source_buffer)); + + GError *error = NULL; + gst_source_buffer_change_content_type (source_buffer, "", &error); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE); + + g_clear_error (&error); + gst_object_unref (source_buffer); + gst_object_unref (media_source); +} + +GST_END_TEST; + +GST_START_TEST (test_source_buffer_change_content_type) +{ + GstMediaSource *media_source = opened_media_source (); + GstSourceBuffer *source_buffer = gst_media_source_add_source_buffer + (media_source, "video/mp4", NULL); + + fail_unless (GST_IS_SOURCE_BUFFER (source_buffer)); + + GError *error = NULL; + gst_source_buffer_change_content_type (source_buffer, "video/webm", &error); + g_assert_error (error, GST_MEDIA_SOURCE_ERROR, + GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED); + + gst_object_unref (source_buffer); + gst_object_unref (media_source); + g_clear_error (&error); +} + +GST_END_TEST; + +static const gchar *unsupported_content_types[] = { + "xxx", + "text/html", + "image/jpeg", +}; + +GST_START_TEST (test_media_source_unsupported_content_type) +{ + const gchar *content_type = unsupported_content_types[__i__]; + ck_assert_msg (!gst_media_source_is_type_supported (content_type), + "%s should be rejected as an unsupported MIME type", content_type); +} + +GST_END_TEST; + +static const gchar *valid_mp4_content_types[] = { + "video/mp4;codecs=\"avc1.4d001e\"", // H.264 Main Profile level 3.0 + "video/mp4;codecs=\"avc1.42001e\"", // H.264 Baseline Profile level 3.0 + "audio/mp4;codecs=\"mp4a.40.2\"", // MPEG4 AAC-LC + "audio/mp4;codecs=\"mp4a.40.5\"", // MPEG4 HE-AAC + "audio/mp4;codecs=\"mp4a.67\"", // MPEG2 AAC-LC + "video/mp4;codecs=\"mp4a.40.2\"", + "video/mp4;codecs=\"avc1.4d001e,mp4a.40.2\"", + "video/mp4;codecs=\"mp4a.40.2 , avc1.4d001e \"", + "video/mp4;codecs=\"avc1.4d001e,mp4a.40.5\"", + "audio/mp4;codecs=\"Opus\"", + "video/mp4;codecs=\"Opus\"", + "audio/mp4;codecs=\"fLaC\"", + "video/mp4;codecs=\"fLaC\"", +}; + +GST_START_TEST (test_media_source_supported_mp4_content_type) +{ + const gchar *content_type = valid_mp4_content_types[__i__]; + ck_assert_msg (gst_media_source_is_type_supported (content_type), + "%s must be a supported MP4 content type", content_type); +} + +GST_END_TEST; + +static const gchar *valid_webm_content_types[] = { + "video/webm;codecs=\"vp8\"", + "video/webm;codecs=\"vorbis\"", + "video/webm;codecs=\"vp8,vorbis\"", + "video/webm;codecs=\"vorbis, vp8\"", + "audio/webm;codecs=\"vorbis\"", + "AUDIO/WEBM;CODECS=\"vorbis\"", + "audio/webm;codecs=vorbis;test=\"6\"", + "audio/webm;codecs=\"opus\"", + "video/webm;codecs=\"opus\"", +}; + +GST_START_TEST (test_media_source_supported_webm_content_type) +{ + const gchar *content_type = valid_webm_content_types[__i__]; + ck_assert_msg (gst_media_source_is_type_supported (content_type), + "%s must be a supported WebM content type", content_type); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_create_and_destroy) +{ + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + gst_check_object_destroyed_on_unref (map); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_add_valid_sample) +{ + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + + GstSample *sample = new_empty_sample_with_timing (0, 0, 0); + + fail_if (gst_media_source_sample_map_contains (map, sample)); + + gst_media_source_sample_map_add (map, sample); + + fail_unless (gst_media_source_sample_map_contains (map, sample)); + + gst_object_unref (map); + gst_sample_unref (sample); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_add_invalid_sample) +{ + add_log_filter (G_LOG_LEVEL_CRITICAL, + "^.*_sample_map_add: assertion .* failed"); + + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + + GstSample *sample = new_empty_sample_with_timing (GST_CLOCK_TIME_NONE, + GST_CLOCK_STIME_NONE, GST_CLOCK_TIME_NONE); + + gst_media_source_sample_map_add (map, sample); + + fail_if (gst_media_source_sample_map_contains (map, sample)); + + gst_object_unref (map); + gst_sample_unref (sample); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_remove_sample) +{ + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + + GstSample *sample = new_empty_sample_with_timing (0, 0, 0); + gst_media_source_sample_map_add (map, sample); + + gst_media_source_sample_map_remove (map, sample); + + fail_if (gst_media_source_sample_map_contains (map, sample)); + + gst_object_unref (map); + gst_sample_unref (sample); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_remove_range_from_start) +{ + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + + GstSample *samples_to_remove[100] = { NULL }; + for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) { + GstClockTime time = i; + GstSample *sample = new_empty_sample_with_timing (time, time, 1); + gst_media_source_sample_map_add (map, sample); + samples_to_remove[i] = sample; + } + GstSample *samples_to_preserve[100] = { NULL }; + for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) { + GstClockTime time = i + G_N_ELEMENTS (samples_to_remove); + GstSample *sample = new_empty_sample_with_timing (time, time, 0); + gst_media_source_sample_map_add (map, sample); + samples_to_preserve[i] = sample; + } + + gst_media_source_sample_map_remove_range_from_start (map, 100); + + for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) { + GstSample *sample = samples_to_remove[i]; + fail_if (gst_media_source_sample_map_contains (map, sample)); + gst_sample_unref (sample); + } + for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) { + GstSample *sample = samples_to_preserve[i]; + fail_unless (gst_media_source_sample_map_contains (map, sample)); + gst_sample_unref (sample); + } + + gst_object_unref (map); +} + +GST_END_TEST; + +GST_START_TEST (test_sample_map_remove_range_from_start_byte_count) +{ + GstMediaSourceSampleMap *map = gst_media_source_sample_map_new (); + + GstSample *samples_to_remove[100] = { NULL }; + const guint8 chunk[1000] = { 0 }; + gsize total_bytes_to_remove = 0; + for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) { + GstClockTime time = i; + gsize buffer_size = g_random_int_range (0, G_N_ELEMENTS (chunk)); + GBytes *bytes = g_bytes_new_static (chunk, buffer_size); + total_bytes_to_remove += buffer_size; + GstSample *sample = new_sample_with_bytes_and_timing (bytes, time, time, 1); + gst_media_source_sample_map_add (map, sample); + samples_to_remove[i] = sample; + } + GstSample *samples_to_preserve[100] = { NULL }; + for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) { + GstClockTime time = i + G_N_ELEMENTS (samples_to_remove); + GBytes *bytes = g_bytes_new_static (chunk, 1); + GstSample *sample = new_sample_with_bytes_and_timing (bytes, time, time, 0); + gst_media_source_sample_map_add (map, sample); + samples_to_preserve[i] = sample; + } + + gsize bytes_removed = + gst_media_source_sample_map_remove_range_from_start (map, + G_N_ELEMENTS (samples_to_remove)); + fail_unless_equals_uint64 (bytes_removed, total_bytes_to_remove); + + for (guint i = 0; i < G_N_ELEMENTS (samples_to_remove); i++) { + gst_sample_unref (samples_to_remove[i]); + } + for (guint i = 0; i < G_N_ELEMENTS (samples_to_preserve); i++) { + gst_sample_unref (samples_to_preserve[i]); + } + + gst_object_unref (map); +} + +GST_END_TEST; + +#define DEFAULT_TCASE_TIMEOUT 15 + +#ifdef HAVE_VALGRIND +#define TCASE_TIMEOUT (RUNNING_ON_VALGRIND ? (5 * 60) : DEFAULT_TCASE_TIMEOUT) +#else +#define TCASE_TIMEOUT DEFAULT_TCASE_TIMEOUT +#endif + +static inline TCase * +new_tcase (const gchar * name) +{ + TCase *tcase = tcase_create (name); + tcase_set_timeout (tcase, TCASE_TIMEOUT); + return tcase; +} + +static Suite * +mse_suite (void) +{ + Suite *s = suite_create ("GstMse"); + + TCase *tc_media_source = new_tcase ("GstMediaSource"); + TCase *tc_source_buffer = new_tcase ("GstSourceBuffer"); + TCase *tc_source_buffer_list = new_tcase ("GstSourceBufferList"); + TCase *tc_append_pipeline = new_tcase ("GstAppendPipeline"); + TCase *tc_track = new_tcase ("GstMediaSourceTrack"); + TCase *tc_track_buffer = new_tcase ("GstMediaSourceTrackBuffer"); + TCase *tc_sample_map = new_tcase ("GstMediaSourceSampleMap"); + + tcase_add_test (tc_media_source, test_create_and_free); + tcase_add_test (tc_media_source, test_create_initial_state); + tcase_add_test (tc_media_source, + test_add_source_buffer_with_content_type_null); + tcase_add_test (tc_media_source, + test_add_source_buffer_with_content_type_empty); + tcase_add_test (tc_media_source, + test_add_source_buffer_with_content_type_fake); + tcase_add_test (tc_media_source, + test_add_source_buffer_to_unopened_media_source); + tcase_add_test (tc_media_source, + test_add_source_buffer_to_opened_media_source); + tcase_add_test (tc_media_source, + test_remove_source_buffer_from_unrelated_media_source); + tcase_add_test (tc_media_source, + test_remove_source_buffer_from_parent_media_source); + tcase_add_test (tc_media_source, + test_set_live_seekable_range_on_unopened_media_source); + tcase_add_test (tc_media_source, + test_set_backwards_live_seekable_range_on_opened_media_source); + tcase_add_test (tc_media_source, + test_set_live_seekable_range_on_opened_media_source); + tcase_add_test (tc_media_source, + test_clear_live_seekable_range_on_unopened_media_source); + tcase_add_test (tc_media_source, + test_clear_live_seekable_range_on_opened_media_source); + tcase_add_loop_test (tc_media_source, + test_media_source_unsupported_content_type, + 0, G_N_ELEMENTS (unsupported_content_types)); + tcase_add_loop_test (tc_media_source, + test_media_source_supported_mp4_content_type, + 0, G_N_ELEMENTS (valid_mp4_content_types)); + tcase_add_loop_test (tc_media_source, + test_media_source_supported_webm_content_type, + 0, G_N_ELEMENTS (valid_webm_content_types)); + + tcase_add_test (tc_source_buffer, test_source_buffer_generate_timestamps_mp4); + tcase_add_test (tc_source_buffer, test_source_buffer_generate_timestamps_aac); + + tcase_add_test (tc_source_buffer, + test_source_buffer_change_content_type_null); + tcase_add_test (tc_source_buffer, + test_source_buffer_change_content_type_empty); + tcase_add_test (tc_source_buffer, test_source_buffer_change_content_type); + + tcase_add_test (tc_append_pipeline, test_append_pipeline_create_and_free); + tcase_add_test (tc_append_pipeline, test_append_pipeline_mp4); + tcase_add_test (tc_append_pipeline, test_append_pipeline_webm); + tcase_add_test (tc_append_pipeline, + test_append_pipeline_invalid_data_triggers_eos); + tcase_add_test (tc_append_pipeline, + test_append_pipeline_invalid_data_triggers_error); + tcase_add_test (tc_append_pipeline, test_append_pipeline_reset_recovery); + + tcase_add_test (tc_track, test_track_create_and_free); + tcase_add_test (tc_track, test_track_create_with_invalid_type); + tcase_add_test (tc_track, test_track_push_with_adequate_space); + tcase_add_test (tc_track, test_track_push_with_inadequate_space); + + tcase_add_test (tc_track_buffer, test_track_buffer_empty); + tcase_add_test (tc_track_buffer, test_track_buffer_single_span); + tcase_add_test (tc_track_buffer, test_track_buffer_continuous_span); + tcase_add_test (tc_track_buffer, test_track_buffer_discontinuous_span); + + tcase_add_test (tc_sample_map, test_sample_map_create_and_destroy); + tcase_add_test (tc_sample_map, test_sample_map_add_valid_sample); + tcase_add_test (tc_sample_map, test_sample_map_add_invalid_sample); + tcase_add_test (tc_sample_map, test_sample_map_remove_sample); + tcase_add_test (tc_sample_map, test_sample_map_remove_range_from_start); + tcase_add_test (tc_sample_map, + test_sample_map_remove_range_from_start_byte_count); + + suite_add_tcase (s, tc_media_source); + suite_add_tcase (s, tc_source_buffer); + suite_add_tcase (s, tc_source_buffer_list); + suite_add_tcase (s, tc_append_pipeline); + suite_add_tcase (s, tc_track); + suite_add_tcase (s, tc_track_buffer); + suite_add_tcase (s, tc_sample_map); + + return s; +} + +GST_CHECK_MAIN (mse) diff --git a/subprojects/gst-plugins-bad/tests/check/meson.build b/subprojects/gst-plugins-bad/tests/check/meson.build index 173722f44f..dcdd098d3c 100644 --- a/subprojects/gst-plugins-bad/tests/check/meson.build +++ b/subprojects/gst-plugins-bad/tests/check/meson.build @@ -85,6 +85,7 @@ base_tests = [ [['libs/nalutils.c', '../../gst-libs/gst/codecparsers/nalutils.c'], false, [nalutils_dep]], [['libs/mpegts.c'], false, [gstmpegts_dep]], [['libs/mpegvideoparser.c'], false, [gstcodecparsers_dep]], + [['libs/mse.c'], false, [gstmse_private_test_dep]], [['libs/planaraudioadapter.c'], false, [gstbadaudio_dep]], [['libs/play.c'], not enable_gst_play_tests, [gstplay_dep, libsoup_dep]], [['libs/vc1parser.c'], false, [gstcodecparsers_dep]], diff --git a/subprojects/gst-plugins-bad/tests/files/mse.mp4 b/subprojects/gst-plugins-bad/tests/files/mse.mp4 new file mode 100644 index 0000000000..41315ca3c7 Binary files /dev/null and b/subprojects/gst-plugins-bad/tests/files/mse.mp4 differ diff --git a/subprojects/gst-plugins-bad/tests/files/mse.webm b/subprojects/gst-plugins-bad/tests/files/mse.webm new file mode 100644 index 0000000000..11c4a2db34 Binary files /dev/null and b/subprojects/gst-plugins-bad/tests/files/mse.webm differ diff --git a/subprojects/gstreamer/libs/gst/check/gstcheck.c b/subprojects/gstreamer/libs/gst/check/gstcheck.c index 3ff00be8b1..accf41abae 100644 --- a/subprojects/gstreamer/libs/gst/check/gstcheck.c +++ b/subprojects/gstreamer/libs/gst/check/gstcheck.c @@ -353,6 +353,7 @@ static const gchar *log_domains[] = { "GStreamer-GL", "GStreamer-InsertBin", "GStreamer-ISOFF", + "GStreamer-MSE", "GStreamer-MpegTS", "GStreamer-Net", "GStreamer-OpenCV",