bad: Added W3C Media Source Extensions library

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2992>
This commit is contained in:
Jordan Yelloz 2023-08-03 17:05:17 -06:00 committed by GStreamer Marge Bot
parent 6c7956ab93
commit 66f51f642f
45 changed files with 11973 additions and 1 deletions

1135
girs/GstMse-1.0.gir Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
# W3C Media Source Extensions Library
> NOTE: This library API is considered *unstable*

View file

@ -0,0 +1 @@
gi-index

View file

@ -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()

View file

@ -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": {

View file

@ -13,6 +13,7 @@ subdir('insertbin')
subdir('interfaces')
subdir('isoff')
subdir('mpegts')
subdir('mse')
subdir('opencv')
subdir('play')
subdir('player')

View file

@ -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 <gst/gst.h>
#include <glib-object.h>
#include <gst/mse/mse-prelude.h>
#include <gst/mse/gstmediasourcetrack-private.h>
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

View file

@ -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 <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>
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;
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 <sebastian@centricular.com>
* 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 <gst/gst.h>
#include <gst/mse/mse-prelude.h>
#include <gst/mse/gstmsesrc.h>
#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

View file

@ -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 <gst/gst.h>
#include <glib-object.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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);
}

View file

@ -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 <gst/gst.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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 <gst/mse/mse-enumtypes.h>
#include <gst/mse/mse-enumtypes-private.h>
#include "gstmediasourcetrack-private.h"
#include "gstmselogging-private.h"
#include <gst/base/gstdataqueue.h>
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;
}
}

View file

@ -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 <gst/gst.h>
#include <glib-object.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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* */
}

View file

@ -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 <gst/gst.h>
#include <gst/base/gstdataqueue.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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 <gst/base/gstdataqueue.h>
#include <gst/mse/mse-prelude.h>
#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);
}

View file

@ -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 <gst/gst.h>
#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);

View file

@ -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");
}

View file

@ -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 <glib.h>
#include <gst/gst.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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 <glib.h>
#include <gst/gst.h>
#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, &param_name, &codecs_value)) {
gboolean is_codecs = g_strcmp0 (param_name, "codecs") == 0;
g_clear_pointer (&param_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);
}

View file

@ -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 <glib.h>
#include <gst/gst.h>
#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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,94 @@
/* GStreamer
*
* SPDX-License-Identifier: LGPL-2.1
*
* Copyright (C) 2009, 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Copyright (C) 2013, 2022, 2023 Collabora Ltd.
* Copyright (C) 2013 Orange
* Copyright (C) 2014, 2015 Sebastian Dröge <sebastian@centricular.com>
* 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 <gst/gst.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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 <glib.h>
#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

File diff suppressed because it is too large Load diff

View file

@ -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 <sebastian@centricular.com>
* 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 <gst/gst.h>
#include <glib-object.h>
#include <gst/mse/mse-prelude.h>
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

View file

@ -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 <glib.h>
#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

View file

@ -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 #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.
*
* 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 #GstSourceBuffer<!-- -->s 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);
}
}

View file

@ -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 <gst/gst.h>
#include <gst/mse/mse-prelude.h>
#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

View file

@ -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 <gst/mse/mse-prelude.h>',
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 <gst/mse/mse-prelude.h>',
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)

View file

@ -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 <gst/gst.h>
#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

View file

@ -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 <gst/gst.h>
#include "mse-prelude.h"
#include <gst/mse/mse-enumtypes.h>
#include "gstmsesrc.h"
#include "gstmediasource.h"
#include "gstsourcebufferlist.h"
#include "gstsourcebuffer.h"

View file

@ -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',

View file

@ -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)

View file

@ -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 <gst/gst.h>
#include <gst/mse/mse.h>
#include <gst/mse/gstmsesrc.h>
GST_ELEMENT_REGISTER_DECLARE (msesrc);

View file

@ -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]

View file

@ -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')

File diff suppressed because it is too large Load diff

View file

@ -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]],

Binary file not shown.

Binary file not shown.

View file

@ -353,6 +353,7 @@ static const gchar *log_domains[] = {
"GStreamer-GL",
"GStreamer-InsertBin",
"GStreamer-ISOFF",
"GStreamer-MSE",
"GStreamer-MpegTS",
"GStreamer-Net",
"GStreamer-OpenCV",