mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 12:10:37 +00:00
2020 lines
58 KiB
C
2020 lines
58 KiB
C
|
/* 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.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SECTION:gstsourcebuffer
|
||
|
* @title: GstSourceBuffer
|
||
|
* @short_description: Source Buffer
|
||
|
* @include: mse/mse.h
|
||
|
* @symbols:
|
||
|
* - GstSourceBuffer
|
||
|
*
|
||
|
* The Source Buffer is the primary means of data flow between an application
|
||
|
* and the Media Source API. It represents a single timeline of media,
|
||
|
* containing some combination of audio, video, and text tracks.
|
||
|
* An application is responsible for feeding raw data into the Source Buffer
|
||
|
* using gst_source_buffer_append_buffer() and the Source Buffer will
|
||
|
* asynchronously process the data into tracks of time-coded multimedia samples.
|
||
|
*
|
||
|
* The application as well as the associated playback component can then select
|
||
|
* to play media from any subset of tracks across all Source Buffers of a Media
|
||
|
* Source.
|
||
|
*
|
||
|
* A few control points are also provided to customize the behavior.
|
||
|
*
|
||
|
* - #GstSourceBuffer:append-mode controls how timestamps of processed samples are
|
||
|
* interpreted. They are either inserted in the timeline directly where the
|
||
|
* decoded media states they should, or inserted directly after the previously
|
||
|
* encountered sample.
|
||
|
*
|
||
|
* - #GstSourceBuffer:append-window-start / #GstSourceBuffer:append-window-end
|
||
|
* control the planned time window where media from appended data can be added
|
||
|
* to the current timeline. Any samples outside that range may be ignored.
|
||
|
*
|
||
|
* - #GstSourceBuffer:timestamp-offset is added to the start time of any sample
|
||
|
* processed.
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include "config.h"
|
||
|
#endif
|
||
|
|
||
|
#include <gst/mse/mse-enumtypes.h>
|
||
|
#include "gstsourcebuffer.h"
|
||
|
#include "gstsourcebuffer-private.h"
|
||
|
|
||
|
#include "gstmselogging-private.h"
|
||
|
#include "gstmsemediatype-private.h"
|
||
|
#include "gstmseeventqueue-private.h"
|
||
|
|
||
|
#include "gstappendpipeline-private.h"
|
||
|
#include "gstmediasource.h"
|
||
|
#include "gstmediasource-private.h"
|
||
|
#include "gstmediasourcetrack-private.h"
|
||
|
#include "gstmediasourcetrackbuffer-private.h"
|
||
|
#include "gstsourcebufferlist-private.h"
|
||
|
#include "gstmsesrc.h"
|
||
|
#include "gstmsesrc-private.h"
|
||
|
|
||
|
#define g_array_new_ranges() \
|
||
|
(g_array_new (TRUE, FALSE, sizeof (GstMediaSourceRange)))
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
GstSourceBufferCallbacks callbacks;
|
||
|
gpointer user_data;
|
||
|
} Callbacks;
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
typedef struct
|
||
|
{
|
||
|
GstSourceBuffer *parent;
|
||
|
|
||
|
GstMediaSourceTrack *track;
|
||
|
GstMediaSourceTrackBuffer *buffer;
|
||
|
|
||
|
GstTask *task;
|
||
|
GRecMutex lock;
|
||
|
|
||
|
gboolean cancelled;
|
||
|
} TrackFeedTask;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
gsize n_samples;
|
||
|
GstSample *current_sample;
|
||
|
GstClockTime current_dts;
|
||
|
} TrackFeedAccumulator;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
const GstClockTime time;
|
||
|
gboolean buffered;
|
||
|
} IsBufferedAccumulator;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
const GstClockTime start;
|
||
|
const GstClockTime end;
|
||
|
gboolean start_buffered;
|
||
|
gboolean end_buffered;
|
||
|
} IsRangeBufferedAccumulator;
|
||
|
|
||
|
struct _GstSourceBuffer
|
||
|
{
|
||
|
GstObject parent_instance;
|
||
|
|
||
|
GstSourceBufferAppendMode append_mode;
|
||
|
GstClockTime append_window_start;
|
||
|
GstClockTime append_window_end;
|
||
|
gchar *content_type;
|
||
|
gboolean generate_timestamps;
|
||
|
GstClockTime timestamp_offset;
|
||
|
gboolean updating;
|
||
|
gboolean errored;
|
||
|
gsize size_limit;
|
||
|
gsize size;
|
||
|
GstBuffer *pending_data;
|
||
|
GstTask *append_to_buffer_task;
|
||
|
GRecMutex append_to_buffer_lock;
|
||
|
GstClockTime seek_time;
|
||
|
GstAppendPipeline *append_pipeline;
|
||
|
GstMseEventQueue *event_queue;
|
||
|
|
||
|
gboolean processed_init_segment;
|
||
|
|
||
|
GHashTable *track_buffers;
|
||
|
GHashTable *track_feeds;
|
||
|
|
||
|
Callbacks callbacks;
|
||
|
};
|
||
|
|
||
|
G_DEFINE_TYPE (GstSourceBuffer, gst_source_buffer, GST_TYPE_OBJECT);
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
|
||
|
PROP_APPEND_MODE,
|
||
|
PROP_APPEND_WINDOW_START,
|
||
|
PROP_APPEND_WINDOW_END,
|
||
|
PROP_BUFFERED,
|
||
|
PROP_CONTENT_TYPE,
|
||
|
PROP_TIMESTAMP_OFFSET,
|
||
|
PROP_UDPATING,
|
||
|
|
||
|
N_PROPS,
|
||
|
};
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
ON_UPDATE_START,
|
||
|
ON_UPDATE,
|
||
|
ON_UPDATE_END,
|
||
|
ON_ERROR,
|
||
|
ON_ABORT,
|
||
|
|
||
|
N_SIGNALS,
|
||
|
} SourceBufferEvent;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
GstDataQueueItem item;
|
||
|
SourceBufferEvent event;
|
||
|
} SourceBufferEventItem;
|
||
|
|
||
|
#define DEFAULT_BUFFER_SIZE 1 << 24
|
||
|
#define DEFAULT_APPEND_MODE GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS
|
||
|
|
||
|
static GParamSpec *properties[N_PROPS];
|
||
|
static guint signals[N_SIGNALS];
|
||
|
|
||
|
static void call_received_init_segment (GstSourceBuffer * self);
|
||
|
static void call_duration_changed (GstSourceBuffer * self);
|
||
|
static void call_active_state_changed (GstSourceBuffer * self);
|
||
|
|
||
|
static inline gboolean is_removed (GstSourceBuffer * self);
|
||
|
static void reset_parser_state (GstSourceBuffer * self);
|
||
|
static void append_error (GstSourceBuffer * self);
|
||
|
|
||
|
static void seek_track_buffer (GstMediaSourceTrack * track,
|
||
|
GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self);
|
||
|
static void dispatch_event (SourceBufferEventItem * item, GstSourceBuffer *
|
||
|
self);
|
||
|
static void schedule_event (GstSourceBuffer * self, SourceBufferEvent event);
|
||
|
static void append_to_buffer_task (GstSourceBuffer * self);
|
||
|
static void track_feed_task (TrackFeedTask * feed);
|
||
|
static void clear_track_feed (TrackFeedTask * feed);
|
||
|
static void stop_track_feed (TrackFeedTask * feed);
|
||
|
static void start_track_feed (TrackFeedTask * feed);
|
||
|
static void reset_track_feed (TrackFeedTask * feed);
|
||
|
static TrackFeedTask *get_track_feed (GstSourceBuffer * self,
|
||
|
GstMediaSourceTrack * track);
|
||
|
static GstMediaSourceTrackBuffer *get_track_buffer (GstSourceBuffer * self,
|
||
|
GstMediaSourceTrack * track);
|
||
|
static void add_track_feed (GstMediaSourceTrack * track,
|
||
|
GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self);
|
||
|
static void add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer *
|
||
|
self);
|
||
|
static void update_msesrc_ready_state (GstSourceBuffer * self);
|
||
|
|
||
|
static void on_duration_changed (GstAppendPipeline * pipeline,
|
||
|
gpointer user_data);
|
||
|
static void on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
|
||
|
gpointer user_data);
|
||
|
static void on_error (GstAppendPipeline * pipeline, gpointer user_data);
|
||
|
static void on_new_sample (GstAppendPipeline * pipeline,
|
||
|
GstMediaSourceTrack * track, GstSample * sample, gpointer user_data);
|
||
|
static void on_received_init_segment (GstAppendPipeline * pipeline,
|
||
|
gpointer user_data);
|
||
|
|
||
|
static inline GstMediaSource *
|
||
|
get_media_source (GstSourceBuffer * self)
|
||
|
{
|
||
|
return GST_MEDIA_SOURCE (gst_object_get_parent (GST_OBJECT (self)));
|
||
|
}
|
||
|
|
||
|
static GstMseSrc *
|
||
|
get_msesrc (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstMediaSource *media_source = get_media_source (self);
|
||
|
if (media_source == NULL) {
|
||
|
return NULL;
|
||
|
}
|
||
|
return gst_media_source_get_source_element (media_source);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
clear_pending_data (GstSourceBuffer * self)
|
||
|
{
|
||
|
gst_clear_buffer (&self->pending_data);
|
||
|
}
|
||
|
|
||
|
static GstBuffer *
|
||
|
take_pending_data (GstSourceBuffer * self)
|
||
|
{
|
||
|
return g_steal_pointer (&self->pending_data);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
set_pending_data (GstSourceBuffer * self, GstBuffer * buffer)
|
||
|
{
|
||
|
clear_pending_data (self);
|
||
|
self->pending_data = buffer;
|
||
|
}
|
||
|
|
||
|
GstSourceBuffer *
|
||
|
gst_source_buffer_new (const gchar * content_type, GstObject * parent,
|
||
|
GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_MEDIA_SOURCE (parent), NULL);
|
||
|
g_return_val_if_fail (content_type != NULL, NULL);
|
||
|
|
||
|
gst_mse_init_logging ();
|
||
|
|
||
|
GstMediaSourceMediaType media_type = GST_MEDIA_SOURCE_MEDIA_TYPE_INIT;
|
||
|
gst_media_source_media_type_parse (&media_type, content_type);
|
||
|
|
||
|
gboolean generate_timestamps = gst_media_source_media_type_generates_timestamp
|
||
|
(&media_type);
|
||
|
gst_media_source_media_type_reset (&media_type);
|
||
|
|
||
|
GstSourceBufferAppendMode append_mode = generate_timestamps
|
||
|
? GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE
|
||
|
: GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS;
|
||
|
|
||
|
GstSourceBuffer *self = g_object_new (GST_TYPE_SOURCE_BUFFER,
|
||
|
"parent", parent, NULL);
|
||
|
|
||
|
self->generate_timestamps = generate_timestamps;
|
||
|
self->append_mode = append_mode;
|
||
|
self->content_type = g_strdup (content_type);
|
||
|
|
||
|
GstAppendPipelineCallbacks callbacks = {
|
||
|
.duration_changed = on_duration_changed,
|
||
|
.eos = on_eos,
|
||
|
.error = on_error,
|
||
|
.new_sample = on_new_sample,
|
||
|
.received_init_segment = on_received_init_segment,
|
||
|
};
|
||
|
GError *append_pipeline_error = NULL;
|
||
|
self->append_pipeline =
|
||
|
gst_append_pipeline_new (&callbacks, self, &append_pipeline_error);
|
||
|
if (append_pipeline_error) {
|
||
|
g_propagate_prefixed_error (error, append_pipeline_error,
|
||
|
"failed to create source buffer");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
return gst_object_ref_sink (self);
|
||
|
error:
|
||
|
gst_clear_object (&self);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
GstSourceBuffer *
|
||
|
gst_source_buffer_new_with_callbacks (const gchar * content_type,
|
||
|
GstObject * parent, GstSourceBufferCallbacks * callbacks,
|
||
|
gpointer user_data, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (callbacks, NULL);
|
||
|
|
||
|
GError *source_buffer_error = NULL;
|
||
|
GstSourceBuffer *self =
|
||
|
gst_source_buffer_new (content_type, parent, &source_buffer_error);
|
||
|
if (source_buffer_error) {
|
||
|
g_propagate_error (error, source_buffer_error);
|
||
|
gst_clear_object (&self);
|
||
|
return NULL;
|
||
|
}
|
||
|
self->callbacks.callbacks = *callbacks;
|
||
|
self->callbacks.user_data = user_data;
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_dispose (GObject * object)
|
||
|
{
|
||
|
GstSourceBuffer *self = (GstSourceBuffer *) object;
|
||
|
|
||
|
if (self->append_to_buffer_task) {
|
||
|
gst_task_join (self->append_to_buffer_task);
|
||
|
}
|
||
|
gst_clear_object (&self->append_to_buffer_task);
|
||
|
|
||
|
gst_clear_object (&self->append_pipeline);
|
||
|
|
||
|
g_hash_table_remove_all (self->track_feeds);
|
||
|
|
||
|
if (!is_removed (self)) {
|
||
|
GstMediaSource *parent = get_media_source (self);
|
||
|
gst_media_source_remove_source_buffer (parent, self, NULL);
|
||
|
gst_object_unref (parent);
|
||
|
}
|
||
|
|
||
|
gst_clear_object (&self->event_queue);
|
||
|
|
||
|
G_OBJECT_CLASS (gst_source_buffer_parent_class)->dispose (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_finalize (GObject * object)
|
||
|
{
|
||
|
GstSourceBuffer *self = (GstSourceBuffer *) object;
|
||
|
|
||
|
g_clear_pointer (&self->content_type, g_free);
|
||
|
g_rec_mutex_clear (&self->append_to_buffer_lock);
|
||
|
|
||
|
g_hash_table_unref (self->track_buffers);
|
||
|
g_hash_table_unref (self->track_feeds);
|
||
|
|
||
|
G_OBJECT_CLASS (gst_source_buffer_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_get_property (GObject * object, guint prop_id, GValue * value,
|
||
|
GParamSpec * pspec)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (object);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_APPEND_MODE:
|
||
|
g_value_set_enum (value, gst_source_buffer_get_append_mode (self));
|
||
|
break;
|
||
|
case PROP_APPEND_WINDOW_START:
|
||
|
g_value_set_uint64 (value,
|
||
|
gst_source_buffer_get_append_window_start (self));
|
||
|
break;
|
||
|
case PROP_APPEND_WINDOW_END:
|
||
|
g_value_set_uint64 (value,
|
||
|
gst_source_buffer_get_append_window_end (self));
|
||
|
break;
|
||
|
case PROP_BUFFERED:
|
||
|
g_value_take_boxed (value, gst_source_buffer_get_buffered (self, NULL));
|
||
|
break;
|
||
|
case PROP_CONTENT_TYPE:
|
||
|
g_value_take_string (value, gst_source_buffer_get_content_type (self));
|
||
|
break;
|
||
|
case PROP_TIMESTAMP_OFFSET:
|
||
|
g_value_set_int64 (value, gst_source_buffer_get_timestamp_offset (self));
|
||
|
break;
|
||
|
case PROP_UDPATING:
|
||
|
g_value_set_boolean (value, gst_source_buffer_get_updating (self));
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (object);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_APPEND_MODE:
|
||
|
gst_source_buffer_set_append_mode (self, g_value_get_enum (value), NULL);
|
||
|
break;
|
||
|
case PROP_CONTENT_TYPE:
|
||
|
gst_source_buffer_change_content_type (self,
|
||
|
g_value_get_string (value), NULL);
|
||
|
break;
|
||
|
case PROP_TIMESTAMP_OFFSET:
|
||
|
gst_source_buffer_set_timestamp_offset (self,
|
||
|
g_value_get_int64 (value), NULL);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_class_init (GstSourceBufferClass * klass)
|
||
|
{
|
||
|
GObjectClass *oclass = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
oclass->dispose = GST_DEBUG_FUNCPTR (gst_source_buffer_dispose);
|
||
|
oclass->finalize = GST_DEBUG_FUNCPTR (gst_source_buffer_finalize);
|
||
|
oclass->get_property = GST_DEBUG_FUNCPTR (gst_source_buffer_get_property);
|
||
|
oclass->set_property = GST_DEBUG_FUNCPTR (gst_source_buffer_set_property);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:append-mode:
|
||
|
*
|
||
|
* Affects how timestamps of processed media segments are interpreted.
|
||
|
* In %GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS, the start timestamp of a
|
||
|
* processed media segment is used directly along with
|
||
|
* #GstSourceBuffer:timestamp-offset .
|
||
|
* In %GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE, the timestamp of a
|
||
|
* processed media segment is ignored and replaced with the end time of the
|
||
|
* most recently appended segment.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_APPEND_MODE] = g_param_spec_enum ("append-mode",
|
||
|
"Append Mode",
|
||
|
"Either Segments or Sequence",
|
||
|
GST_TYPE_SOURCE_BUFFER_APPEND_MODE, DEFAULT_APPEND_MODE,
|
||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:append-window-start:
|
||
|
*
|
||
|
* Any segments processed which end before this value will be ignored by this
|
||
|
* Source Buffer.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_APPEND_WINDOW_START] =
|
||
|
g_param_spec_uint64 ("append-window-start", "Append Window Start",
|
||
|
"The timestamp representing the start of the append window", 0,
|
||
|
GST_CLOCK_TIME_NONE, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:append-window-end:
|
||
|
*
|
||
|
* Any segments processed which have a start time greater than this value will
|
||
|
* be ignored by this Source Buffer.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_APPEND_WINDOW_END] = g_param_spec_uint64 ("append-window-end",
|
||
|
"Append Window End",
|
||
|
"The timestamp representing the end of the append window",
|
||
|
0, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE, G_PARAM_READABLE |
|
||
|
G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:buffered:
|
||
|
*
|
||
|
* The set of Time Intervals that have been loaded into the current Source
|
||
|
* Buffer
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_BUFFERED] = g_param_spec_boxed ("buffered",
|
||
|
"Buffered Time Intervals",
|
||
|
"The set of Time Intervals that have been loaded into"
|
||
|
" the current Source Buffer", G_TYPE_ARRAY, G_PARAM_READABLE |
|
||
|
G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:content-type:
|
||
|
*
|
||
|
* The MIME content-type of the data stream
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_CONTENT_TYPE] = g_param_spec_string ("content-type",
|
||
|
"Content Type",
|
||
|
"The MIME content-type of the data stream", NULL,
|
||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:timestamp-offset:
|
||
|
*
|
||
|
* The next media segment appended to the current Source Buffer will have its
|
||
|
* start timestamp increased by this amount.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_TIMESTAMP_OFFSET] = g_param_spec_int64 ("timestamp-offset",
|
||
|
"Timestamp Offset",
|
||
|
"The next media segment appended to the current Source Buffer"
|
||
|
" will have its start timestamp increased by this amount",
|
||
|
0, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer:updating:
|
||
|
*
|
||
|
* Whether the current source buffer is still asynchronously processing
|
||
|
* previously issued commands.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
properties[PROP_UDPATING] = g_param_spec_boolean ("updating",
|
||
|
"Updating",
|
||
|
"Whether the current Source Buffer is still"
|
||
|
" asynchronously processing previously issued commands",
|
||
|
FALSE, G_PARAM_READABLE);
|
||
|
|
||
|
g_object_class_install_properties (oclass, N_PROPS, properties);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer::on-update-start:
|
||
|
* @self: The #GstSourceBuffer that has just started updating
|
||
|
*
|
||
|
* Emitted when @self has begun to process data after a call to
|
||
|
* gst_source_buffer_append_buffer().
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdatestart)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
signals[ON_UPDATE_START] = g_signal_new ("on-update-start",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer::on-update:
|
||
|
* @self: The #GstSourceBuffer that has just updated
|
||
|
*
|
||
|
* Emitted when @self has successfully processed data after a call to
|
||
|
* gst_source_buffer_append_buffer().
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdate)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
signals[ON_UPDATE] = g_signal_new ("on-update",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer::on-update-end:
|
||
|
* @self: The #GstSourceBuffer that is no longer updating
|
||
|
*
|
||
|
* Emitted when @self is no longer in the updating state after a call to
|
||
|
* gst_source_buffer_append_buffer(). This can happen after a successful or
|
||
|
* unsuccessful append.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onupdateend)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
signals[ON_UPDATE_END] = g_signal_new ("on-update-end",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer::on-error:
|
||
|
* @self: The #GstSourceBuffer that has encountered an error
|
||
|
*
|
||
|
* Emitted when @self has encountered an error after a call to
|
||
|
* gst_source_buffer_append_buffer().
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onerror)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
signals[ON_ERROR] = g_signal_new ("on-error",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
|
||
|
/**
|
||
|
* GstSourceBuffer::on-abort:
|
||
|
* @self: The #GstSourceBuffer that has been aborted.
|
||
|
*
|
||
|
* Emitted when @self was aborted after a call to gst_source_buffer_abort().
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-onabort)
|
||
|
*
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
signals[ON_ABORT] = g_signal_new ("on-abort",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_duration_changed (GstAppendPipeline * pipeline, gpointer user_data)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
|
||
|
if (is_removed (self)) {
|
||
|
return;
|
||
|
}
|
||
|
call_duration_changed (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_eos (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
|
||
|
if (GST_IS_MEDIA_SOURCE_TRACK (track)) {
|
||
|
GST_DEBUG_OBJECT (self, "got EOS event on %" GST_PTR_FORMAT, track);
|
||
|
GstMediaSourceTrackBuffer *buffer = get_track_buffer (self, track);
|
||
|
gst_media_source_track_buffer_eos (buffer);
|
||
|
}
|
||
|
update_msesrc_ready_state (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_error (GstAppendPipeline * pipeline, gpointer user_data)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
|
||
|
append_error (self);
|
||
|
}
|
||
|
|
||
|
static GstMediaSourceTrackBuffer *
|
||
|
get_track_buffer (GstSourceBuffer * self, GstMediaSourceTrack * track)
|
||
|
{
|
||
|
g_return_val_if_fail (g_hash_table_contains (self->track_buffers, track),
|
||
|
NULL);
|
||
|
return g_hash_table_lookup (self->track_buffers, track);
|
||
|
}
|
||
|
|
||
|
static inline TrackFeedTask *
|
||
|
get_track_feed (GstSourceBuffer * self, GstMediaSourceTrack * track)
|
||
|
{
|
||
|
g_return_val_if_fail (g_hash_table_contains (self->track_feeds, track), NULL);
|
||
|
return g_hash_table_lookup (self->track_feeds, track);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
add_track_buffer (GstMediaSourceTrack * track, GstSourceBuffer * self)
|
||
|
{
|
||
|
const gchar *id = gst_media_source_track_get_id (track);
|
||
|
if (g_hash_table_contains (self->track_buffers, track)) {
|
||
|
GST_DEBUG_OBJECT (self, "already have a track buffer for track %s", id);
|
||
|
return;
|
||
|
}
|
||
|
GstMediaSourceTrackBuffer *buf = gst_media_source_track_buffer_new ();
|
||
|
g_hash_table_insert (self->track_buffers, track, buf);
|
||
|
GST_DEBUG_OBJECT (self, "added track buffer for track %s", id);
|
||
|
|
||
|
add_track_feed (track, buf, self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
add_track_feed (GstMediaSourceTrack * track,
|
||
|
GstMediaSourceTrackBuffer * track_buffer, GstSourceBuffer * self)
|
||
|
{
|
||
|
TrackFeedTask *feed = g_new0 (TrackFeedTask, 1);
|
||
|
GstTask *task = gst_task_new ((GstTaskFunction) track_feed_task, feed, NULL);
|
||
|
g_rec_mutex_init (&feed->lock);
|
||
|
gst_task_set_lock (task, &feed->lock);
|
||
|
feed->task = task;
|
||
|
feed->buffer = track_buffer;
|
||
|
feed->track = gst_object_ref (track);
|
||
|
feed->parent = self;
|
||
|
feed->cancelled = FALSE;
|
||
|
g_hash_table_insert (self->track_feeds, track, feed);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
clear_track_feed (TrackFeedTask * feed)
|
||
|
{
|
||
|
gst_object_unref (feed->task);
|
||
|
g_rec_mutex_clear (&feed->lock);
|
||
|
gst_object_unref (feed->track);
|
||
|
g_free (feed);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
stop_track_feed (TrackFeedTask * feed)
|
||
|
{
|
||
|
g_return_if_fail (feed != NULL);
|
||
|
gst_media_source_track_flush (feed->track);
|
||
|
g_atomic_int_set (&feed->cancelled, TRUE);
|
||
|
gst_task_join (feed->task);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
start_track_feed (TrackFeedTask * feed)
|
||
|
{
|
||
|
g_return_if_fail (feed != NULL);
|
||
|
g_atomic_int_set (&feed->cancelled, FALSE);
|
||
|
gst_media_source_track_resume (feed->track);
|
||
|
gst_task_start (feed->task);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
reset_track_feed (TrackFeedTask * feed)
|
||
|
{
|
||
|
stop_track_feed (feed);
|
||
|
start_track_feed (feed);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
is_within_append_window (GstSourceBuffer * self, GstSample * sample)
|
||
|
{
|
||
|
GstBuffer *buffer = gst_sample_get_buffer (sample);
|
||
|
GstClockTime start = GST_BUFFER_PTS (buffer);
|
||
|
GstClockTime end = start + GST_BUFFER_DURATION (buffer);
|
||
|
|
||
|
if (start < self->append_window_start) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (!GST_CLOCK_TIME_IS_VALID (self->append_window_end)) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return end <= self->append_window_end;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_new_sample (GstAppendPipeline * pipeline, GstMediaSourceTrack * track,
|
||
|
GstSample * sample, gpointer user_data)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
|
||
|
|
||
|
g_return_if_fail (self->processed_init_segment);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
if (is_within_append_window (self, sample)) {
|
||
|
GstMediaSourceTrackBuffer *track_buffer = get_track_buffer (self, track);
|
||
|
GST_TRACE_OBJECT (self, "new sample on %s with %" GST_PTR_FORMAT,
|
||
|
gst_media_source_track_get_id (track), gst_sample_get_buffer (sample));
|
||
|
gst_media_source_track_buffer_add (track_buffer, sample);
|
||
|
TrackFeedTask *feed = get_track_feed (self, track);
|
||
|
start_track_feed (feed);
|
||
|
}
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
update_msesrc_ready_state (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
call_received_init_segment (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
|
||
|
if (callbacks->received_init_segment) {
|
||
|
callbacks->received_init_segment (self, self->callbacks.user_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
call_duration_changed (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
|
||
|
if (callbacks->duration_changed) {
|
||
|
callbacks->duration_changed (self, self->callbacks.user_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
call_active_state_changed (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstSourceBufferCallbacks *callbacks = &self->callbacks.callbacks;
|
||
|
if (callbacks->active_state_changed) {
|
||
|
callbacks->active_state_changed (self, self->callbacks.user_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_track_buffer_modes (GstSourceBuffer * self)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
gboolean enabled =
|
||
|
self->append_mode == GST_SOURCE_BUFFER_APPEND_MODE_SEQUENCE;
|
||
|
for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
|
||
|
GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value);
|
||
|
gst_media_source_track_buffer_process_init_segment (buffer, enabled);
|
||
|
gst_media_source_track_buffer_set_group_start (buffer,
|
||
|
self->timestamp_offset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_received_init_segment (GstAppendPipeline * pipeline, gpointer user_data)
|
||
|
{
|
||
|
GstSourceBuffer *self = GST_SOURCE_BUFFER (user_data);
|
||
|
GST_DEBUG_OBJECT (self, "got init segment, have duration %" GST_TIME_FORMAT,
|
||
|
GST_TIME_ARGS (gst_append_pipeline_get_duration (pipeline)));
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
if (!self->processed_init_segment) {
|
||
|
GST_DEBUG_OBJECT (self, "processing first init segment");
|
||
|
|
||
|
GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks (pipeline);
|
||
|
GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks (pipeline);
|
||
|
GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks (pipeline);
|
||
|
|
||
|
g_ptr_array_foreach (audio_tracks, (GFunc) add_track_buffer, self);
|
||
|
g_ptr_array_foreach (text_tracks, (GFunc) add_track_buffer, self);
|
||
|
g_ptr_array_foreach (video_tracks, (GFunc) add_track_buffer, self);
|
||
|
}
|
||
|
|
||
|
self->processed_init_segment = TRUE;
|
||
|
|
||
|
update_track_buffer_modes (self);
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
call_received_init_segment (self);
|
||
|
call_active_state_changed (self);
|
||
|
}
|
||
|
|
||
|
static guint
|
||
|
track_buffer_hash (GstMediaSourceTrack * track)
|
||
|
{
|
||
|
return g_str_hash (gst_media_source_track_get_id (track));
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
track_buffer_equal (GstMediaSourceTrack * a, GstMediaSourceTrack * b)
|
||
|
{
|
||
|
return g_str_equal (gst_media_source_track_get_id (a),
|
||
|
gst_media_source_track_get_id (b));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_source_buffer_init (GstSourceBuffer * self)
|
||
|
{
|
||
|
|
||
|
self->append_mode = DEFAULT_APPEND_MODE;
|
||
|
self->append_window_start = 0;
|
||
|
self->append_window_end = GST_CLOCK_TIME_NONE;
|
||
|
self->content_type = NULL;
|
||
|
self->timestamp_offset = 0;
|
||
|
self->updating = FALSE;
|
||
|
self->errored = FALSE;
|
||
|
self->size_limit = DEFAULT_BUFFER_SIZE;
|
||
|
self->size = 0;
|
||
|
self->pending_data = NULL;
|
||
|
self->processed_init_segment = FALSE;
|
||
|
self->event_queue =
|
||
|
gst_mse_event_queue_new ((GstMseEventQueueCallback) dispatch_event, self);
|
||
|
|
||
|
g_rec_mutex_init (&self->append_to_buffer_lock);
|
||
|
self->append_to_buffer_task = gst_task_new (
|
||
|
(GstTaskFunction) append_to_buffer_task, self, NULL);
|
||
|
gst_task_set_lock (self->append_to_buffer_task, &self->append_to_buffer_lock);
|
||
|
self->track_buffers = g_hash_table_new_full ((GHashFunc) track_buffer_hash,
|
||
|
(GEqualFunc) track_buffer_equal, NULL, gst_object_unref);
|
||
|
|
||
|
self->track_feeds = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
||
|
NULL, (GDestroyNotify) clear_track_feed);
|
||
|
self->seek_time = 0;
|
||
|
self->callbacks.callbacks.duration_changed = NULL;
|
||
|
self->callbacks.user_data = NULL;
|
||
|
}
|
||
|
|
||
|
static inline gboolean
|
||
|
is_removed (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstObject *parent = gst_object_get_parent (GST_OBJECT (self));
|
||
|
if (parent == NULL) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
gst_object_unref (parent);
|
||
|
|
||
|
GstMediaSource *source = get_media_source (self);
|
||
|
GstSourceBufferList *buffers = gst_media_source_get_source_buffers (source);
|
||
|
gboolean removed = !gst_source_buffer_list_contains (buffers, self);
|
||
|
|
||
|
gst_object_unref (source);
|
||
|
gst_object_unref (buffers);
|
||
|
|
||
|
return removed;
|
||
|
}
|
||
|
|
||
|
static inline gboolean
|
||
|
is_updating (GstSourceBuffer * self)
|
||
|
{
|
||
|
return g_atomic_int_get (&self->updating);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
set_updating (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_atomic_int_set (&self->updating, TRUE);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
clear_updating (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_atomic_int_set (&self->updating, FALSE);
|
||
|
}
|
||
|
|
||
|
static inline gboolean
|
||
|
is_errored (GstSourceBuffer * self)
|
||
|
{
|
||
|
return g_atomic_int_get (&self->errored);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
set_errored (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_atomic_int_set (&self->errored, TRUE);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
clear_errored (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_atomic_int_set (&self->errored, FALSE);
|
||
|
}
|
||
|
|
||
|
static inline gboolean
|
||
|
is_ended (GstSourceBuffer * self)
|
||
|
{
|
||
|
if (is_removed (self)) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
GstMediaSource *source = get_media_source (self);
|
||
|
gboolean ended = gst_media_source_get_ready_state (source) ==
|
||
|
GST_MEDIA_SOURCE_READY_STATE_ENDED;
|
||
|
|
||
|
gst_object_unref (source);
|
||
|
|
||
|
return ended;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
open_parent (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_if_fail (!is_removed (self));
|
||
|
GstMediaSource *source = get_media_source (self);
|
||
|
gst_media_source_open (source);
|
||
|
gst_object_unref (source);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_append_mode:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
|
||
|
*
|
||
|
* Returns: The current #GstSourceBufferAppendMode
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
GstSourceBufferAppendMode
|
||
|
gst_source_buffer_get_append_mode (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), DEFAULT_APPEND_MODE);
|
||
|
return self->append_mode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_set_append_mode:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @mode: #GstSourceBufferAppendMode the desired Append Mode
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Changes the Append Mode of @self. This influences what timestamps will be
|
||
|
* assigned to media processed by this Source Buffer. In Segment mode, the
|
||
|
* timestamps in each segment determine the position of each sample after it
|
||
|
* is processed. In Sequence mode, the timestamp of each processed sample is
|
||
|
* generated based on the end of the most recently processed segment.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-mode)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_set_append_mode (GstSourceBuffer * self,
|
||
|
GstSourceBufferAppendMode mode, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
|
||
|
if (is_removed (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"buffer is removed");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"buffer is still updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (self->generate_timestamps && mode ==
|
||
|
GST_SOURCE_BUFFER_APPEND_MODE_SEGMENTS) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
||
|
"cannot change to segments mode while generate timestamps is active");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_ended (self)) {
|
||
|
open_parent (self);
|
||
|
}
|
||
|
|
||
|
self->append_mode = mode;
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APPEND_MODE]);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_append_window_start:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* Returns the current append window start time. Any segment processed that ends
|
||
|
* earlier than this value will be ignored.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
|
||
|
*
|
||
|
* Returns: The current Append Window start time as a #GstClockTime
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
GstClockTime
|
||
|
gst_source_buffer_get_append_window_start (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
|
||
|
return self->append_window_start;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_set_append_window_start:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @start: the append window end
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Modifies the current append window start of @self. If successful, samples
|
||
|
* processed after setting this value that end before this point will be
|
||
|
* ignored.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowstart)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_set_append_window_start (GstSourceBuffer * self,
|
||
|
GstClockTime start, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
|
||
|
if (is_removed (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"append window start cannot be set on source buffer "
|
||
|
"with no media source");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"append window start cannot be set on source buffer while updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (start < 0 || start <= self->append_window_end) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
||
|
"append window start must be between zero and append window end");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
self->append_window_start = start;
|
||
|
g_object_notify_by_pspec (G_OBJECT (self),
|
||
|
properties[PROP_APPEND_WINDOW_START]);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_append_window_end:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* Returns the current append window end time. Any segment processed that starts
|
||
|
* after this value will be ignored.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
|
||
|
*
|
||
|
* Returns: The current Append Window end time as a #GstClockTime
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
GstClockTime
|
||
|
gst_source_buffer_get_append_window_end (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
|
||
|
return self->append_window_end;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_set_append_window_end:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @end: the append window end
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Modifies the current append window end of @self. If successful, samples
|
||
|
* processed after setting this value that start after this point will be
|
||
|
* ignored.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendwindowend)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_set_append_window_end (GstSourceBuffer * self,
|
||
|
GstClockTime end, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
|
||
|
if (is_removed (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"append window end cannot be set on source buffer "
|
||
|
"with no media source");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"append window end cannot be set on source buffer while updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (end <= self->append_window_start) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
||
|
"append window end must be after append window start");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
self->append_window_end = end;
|
||
|
g_object_notify_by_pspec (G_OBJECT (self),
|
||
|
properties[PROP_APPEND_WINDOW_END]);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
get_intersection (GstMediaSourceRange * a, GstMediaSourceRange * b,
|
||
|
GstMediaSourceRange * intersection)
|
||
|
{
|
||
|
g_return_val_if_fail (a != NULL, FALSE);
|
||
|
g_return_val_if_fail (b != NULL, FALSE);
|
||
|
g_return_val_if_fail (intersection != NULL, FALSE);
|
||
|
GstMediaSourceRange range = {
|
||
|
.start = MAX (a->start, b->start),
|
||
|
.end = MIN (a->end, b->end),
|
||
|
};
|
||
|
if (range.start >= range.end) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
*intersection = range;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static GArray *
|
||
|
intersect_ranges (GstMediaSourceRange * a, GstMediaSourceRange * a_end,
|
||
|
GstMediaSourceRange * b, GstMediaSourceRange * b_end)
|
||
|
{
|
||
|
GArray *intersection = g_array_new_ranges ();
|
||
|
while (a < a_end && b < b_end) {
|
||
|
GstMediaSourceRange range;
|
||
|
if (!get_intersection (a, b, &range)) {
|
||
|
if (a->end < b->end) {
|
||
|
a++;
|
||
|
} else {
|
||
|
b++;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (a->end < b->end) {
|
||
|
a++;
|
||
|
} else {
|
||
|
b++;
|
||
|
}
|
||
|
|
||
|
g_array_append_val (intersection, range);
|
||
|
}
|
||
|
return intersection;
|
||
|
}
|
||
|
|
||
|
static inline gboolean
|
||
|
contributes_to_buffered (GstMediaSourceTrack * track)
|
||
|
{
|
||
|
switch (gst_media_source_track_get_track_type (track)) {
|
||
|
case GST_MEDIA_SOURCE_TRACK_TYPE_AUDIO:
|
||
|
case GST_MEDIA_SOURCE_TRACK_TYPE_VIDEO:
|
||
|
return TRUE;
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_buffered:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Returns a sequence of #GstMediaSourceRange values representing which segments
|
||
|
* of @self are buffered in memory.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-buffered)
|
||
|
*
|
||
|
* Returns: (transfer full) (element-type GstMediaSourceRange): a #GArray of #GstMediaSourceRange values.
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
GArray *
|
||
|
gst_source_buffer_get_buffered (GstSourceBuffer * self, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
|
||
|
GHashTableIter iter;
|
||
|
GArray *buffered = NULL;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer key, value; g_hash_table_iter_next (&iter, &key, &value);) {
|
||
|
GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key);
|
||
|
GstMediaSourceTrackBuffer *buffer = GST_MEDIA_SOURCE_TRACK_BUFFER (value);
|
||
|
if (!contributes_to_buffered (track)) {
|
||
|
continue;
|
||
|
}
|
||
|
GArray *current_ranges = gst_media_source_track_buffer_get_ranges (buffer);
|
||
|
if (buffered == NULL) {
|
||
|
buffered = current_ranges;
|
||
|
continue;
|
||
|
}
|
||
|
GArray *intersection = intersect_ranges (
|
||
|
(GstMediaSourceRange *) buffered->data,
|
||
|
((GstMediaSourceRange *) buffered->data) + buffered->len,
|
||
|
(GstMediaSourceRange *) current_ranges->data,
|
||
|
((GstMediaSourceRange *) current_ranges->data) + current_ranges->len);
|
||
|
g_array_unref (buffered);
|
||
|
buffered = intersection;
|
||
|
}
|
||
|
if (buffered == NULL) {
|
||
|
return g_array_new_ranges ();
|
||
|
} else {
|
||
|
return buffered;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_content_type:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* Returns the current content type of @self.
|
||
|
*
|
||
|
* Returns: (transfer full): a string representing the content type
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gchar *
|
||
|
gst_source_buffer_get_content_type (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
gchar *content_type = g_strdup (self->content_type);
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
return content_type;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_change_content_type:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @type: (transfer none): the desired content type
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Attempts to change the content type of @self to @type. Any new data appended
|
||
|
* to the Source Buffer must be of the supplied @type afterward.
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_change_content_type (GstSourceBuffer * self,
|
||
|
const gchar * type, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
|
||
|
if (type == NULL || g_strcmp0 (type, "") == 0) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_TYPE,
|
||
|
"content type must not be empty");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_removed (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"content type cannot be set on source buffer with no media source");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"content type cannot be set on source buffer that is updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_NOT_SUPPORTED,
|
||
|
"content type cannot be changed");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_remove:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @start: The beginning timestamp of data to remove
|
||
|
* @end: The end timestamp of data to remove
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Attempts to remove any parsed data between @start and @end from @self.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-remove)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_remove (GstSourceBuffer * self, GstClockTime start,
|
||
|
GstClockTime end, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_timestamp_offset:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
|
||
|
*
|
||
|
* Returns: The current timestamp offset as a #GstClockTime
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
GstClockTime
|
||
|
gst_source_buffer_get_timestamp_offset (GstSourceBuffer * self)
|
||
|
{
|
||
|
return self->timestamp_offset;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_set_timestamp_offset:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @offset: The new timestamp offset
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Attempt to set the timestamp offset of @self. Any media processed after this
|
||
|
* value is set will have this value added to its start time.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-timestampoffset)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_set_timestamp_offset (GstSourceBuffer * self, GstClockTime
|
||
|
offset, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
if (is_removed (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"source buffer is removed");
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"source buffer is still updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (is_ended (self)) {
|
||
|
GstMediaSource *parent = get_media_source (self);
|
||
|
gst_media_source_open (parent);
|
||
|
gst_clear_object (&parent);
|
||
|
}
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
GHashTableIter iter;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
|
||
|
GstMediaSourceTrackBuffer *buffer = value;
|
||
|
gst_media_source_track_buffer_set_group_start (buffer, offset);
|
||
|
}
|
||
|
self->timestamp_offset = offset;
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP_OFFSET]);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_get_updating:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-updating)
|
||
|
*
|
||
|
* Returns: Whether @self is currently adding or removing media content.
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_get_updating (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
return is_updating (self);
|
||
|
}
|
||
|
|
||
|
static gsize
|
||
|
compute_total_size_unlocked (GstSourceBuffer * self)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
gsize total_size = 0;
|
||
|
for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
|
||
|
GstMediaSourceTrackBuffer *buffer = value;
|
||
|
total_size += gst_media_source_track_buffer_get_storage_size (buffer);
|
||
|
}
|
||
|
return total_size;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
will_overflow (GstSourceBuffer * self, gsize bytes)
|
||
|
{
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
gsize total = compute_total_size_unlocked (self);
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
return total + bytes > self->size_limit;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
evict_coded_frames (GstSourceBuffer * self, gsize space_required,
|
||
|
gsize size_limit, GstClockTime position, GstClockTime duration)
|
||
|
{
|
||
|
if (!will_overflow (self, space_required)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!GST_CLOCK_TIME_IS_VALID (position)) {
|
||
|
GST_ERROR ("invalid position, cannot delete anything");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GstClockTime min_distance_from_position = GST_SECOND * 5;
|
||
|
GstClockTime max_dts = position > min_distance_from_position ?
|
||
|
position - min_distance_from_position : 0;
|
||
|
|
||
|
GST_DEBUG_OBJECT (self, "position=%" GST_TIMEP_FORMAT
|
||
|
", attempting removal from 0 to %" GST_TIMEP_FORMAT, &position, &max_dts);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
GHashTableIter iter;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer value; g_hash_table_iter_next (&iter, NULL, &value);) {
|
||
|
GstMediaSourceTrackBuffer *buffer = value;
|
||
|
gst_media_source_track_buffer_remove_range (buffer, 0, max_dts);
|
||
|
}
|
||
|
self->size = compute_total_size_unlocked (self);
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
GST_DEBUG_OBJECT (self, "capacity=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT
|
||
|
"(%" G_GSIZE_FORMAT "%%)", self->size, self->size_limit,
|
||
|
self->size * 100 / size_limit);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
reset_parser_state (GstSourceBuffer * self)
|
||
|
{
|
||
|
clear_pending_data (self);
|
||
|
if (gst_append_pipeline_reset (self->append_pipeline)) {
|
||
|
clear_errored (self);
|
||
|
} else {
|
||
|
set_errored (self);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
append_error (GstSourceBuffer * self)
|
||
|
{
|
||
|
gst_task_stop (self->append_to_buffer_task);
|
||
|
reset_parser_state (self);
|
||
|
clear_updating (self);
|
||
|
|
||
|
if (is_removed (self)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
schedule_event (self, ON_ERROR);
|
||
|
schedule_event (self, ON_UPDATE_END);
|
||
|
|
||
|
GstMediaSource *source = get_media_source (self);
|
||
|
gst_media_source_end_of_stream (source, GST_MEDIA_SOURCE_EOS_ERROR_DECODE,
|
||
|
NULL);
|
||
|
gst_object_unref (source);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
append_successful (GstSourceBuffer * self, gboolean ended)
|
||
|
{
|
||
|
gst_task_stop (self->append_to_buffer_task);
|
||
|
clear_updating (self);
|
||
|
schedule_event (self, ON_UPDATE);
|
||
|
schedule_event (self, ON_UPDATE_END);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
encountered_bad_bytes (GstSourceBuffer * self)
|
||
|
{
|
||
|
return gst_append_pipeline_get_failed (self->append_pipeline);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
append_to_buffer_task (GstSourceBuffer * self)
|
||
|
{
|
||
|
if (is_removed (self)) {
|
||
|
append_successful (self, TRUE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (encountered_bad_bytes (self)) {
|
||
|
append_error (self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GstBuffer *pending_data = take_pending_data (self);
|
||
|
|
||
|
if (!GST_IS_BUFFER (pending_data)) {
|
||
|
GST_LOG_OBJECT (self, "no pending data");
|
||
|
append_successful (self, is_ended (self));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GstFlowReturn result = gst_append_pipeline_append (self->append_pipeline,
|
||
|
pending_data);
|
||
|
|
||
|
if (result != GST_FLOW_OK) {
|
||
|
GST_ERROR_OBJECT (self, "failed to append: %s", gst_flow_get_name (result));
|
||
|
append_error (self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
append_successful (self, is_ended (self));
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
track_feed_fold (const GValue * item, TrackFeedAccumulator * acc,
|
||
|
TrackFeedTask * feed)
|
||
|
{
|
||
|
GstSample *sample = gst_sample_ref (gst_value_get_sample (item));
|
||
|
GstClockTime dts = GST_BUFFER_DTS (gst_sample_get_buffer (sample));
|
||
|
acc->n_samples++;
|
||
|
acc->current_dts = dts;
|
||
|
gst_clear_sample (&acc->current_sample);
|
||
|
acc->current_sample = gst_sample_ref (sample);
|
||
|
if (gst_media_source_track_push (feed->track, sample)) {
|
||
|
return TRUE;
|
||
|
} else {
|
||
|
gst_sample_unref (sample);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
track_feed_task (TrackFeedTask * feed)
|
||
|
{
|
||
|
GstSourceBuffer *self = feed->parent;
|
||
|
GstMediaSourceTrack *track = feed->track;
|
||
|
GstMediaSourceTrackBuffer *buffer = feed->buffer;
|
||
|
GstClockTime time = feed->parent->seek_time;
|
||
|
const gchar *track_id = gst_media_source_track_get_id (track);
|
||
|
|
||
|
GST_DEBUG_OBJECT (self, "%s: feed starting@%" GST_TIMEP_FORMAT, track_id,
|
||
|
&time);
|
||
|
|
||
|
TrackFeedAccumulator acc = {
|
||
|
.n_samples = 0,
|
||
|
.current_dts = time,
|
||
|
.current_sample = NULL,
|
||
|
};
|
||
|
while (TRUE) {
|
||
|
gboolean eos = gst_media_source_track_buffer_is_eos (buffer);
|
||
|
GstIterator *it = gst_media_source_track_buffer_iter_samples (buffer,
|
||
|
acc.current_dts, acc.current_sample);
|
||
|
while (TRUE) {
|
||
|
GstIteratorResult fold_result = gst_iterator_fold (it,
|
||
|
(GstIteratorFoldFunction) track_feed_fold, (GValue *) & acc, feed);
|
||
|
if (fold_result != GST_ITERATOR_RESYNC) {
|
||
|
break;
|
||
|
}
|
||
|
if (g_atomic_int_get (&feed->cancelled)) {
|
||
|
break;
|
||
|
}
|
||
|
gst_iterator_resync (it);
|
||
|
}
|
||
|
gst_iterator_free (it);
|
||
|
|
||
|
if (eos) {
|
||
|
GST_DEBUG_OBJECT (self, "%s: enqueued all %" G_GSIZE_FORMAT " samples",
|
||
|
track_id, acc.n_samples);
|
||
|
gst_media_source_track_push_eos (track);
|
||
|
GST_DEBUG_OBJECT (self, "%s: marked EOS", track_id);
|
||
|
gst_task_stop (feed->task);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (g_atomic_int_get (&feed->cancelled)) {
|
||
|
GST_DEBUG_OBJECT (self, "feed is cancelled, stopping task");
|
||
|
gst_task_stop (feed->task);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
GST_DEBUG_OBJECT (self, "%s: resume after %" G_GSIZE_FORMAT " samples",
|
||
|
track_id, acc.n_samples);
|
||
|
gint64 deadline = g_get_monotonic_time () + G_TIME_SPAN_SECOND;
|
||
|
gst_media_source_track_buffer_await_eos_until (buffer, deadline);
|
||
|
}
|
||
|
|
||
|
gst_clear_sample (&acc.current_sample);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dispatch_event (SourceBufferEventItem * item, GstSourceBuffer * self)
|
||
|
{
|
||
|
g_signal_emit (self, signals[item->event], 0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
schedule_event (GstSourceBuffer * self, SourceBufferEvent event)
|
||
|
{
|
||
|
g_return_if_fail (event < N_SIGNALS);
|
||
|
if (is_removed (self)) {
|
||
|
return;
|
||
|
}
|
||
|
SourceBufferEventItem item = {
|
||
|
.item = {.destroy = g_free,.visible = TRUE,.size = 1,.object = NULL},
|
||
|
.event = event,
|
||
|
};
|
||
|
gst_mse_event_queue_push (self->event_queue, g_memdup2 (&item,
|
||
|
sizeof (SourceBufferEventItem)));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
schedule_append_to_buffer_task (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstTask *task = self->append_to_buffer_task;
|
||
|
g_return_if_fail (GST_IS_TASK (task));
|
||
|
g_return_if_fail (gst_task_get_state (task) != GST_TASK_STARTED);
|
||
|
gst_task_start (task);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_msesrc_ready_state (GstSourceBuffer * self)
|
||
|
{
|
||
|
GstMseSrc *element = get_msesrc (self);
|
||
|
if (element == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
gst_mse_src_update_ready_state (element);
|
||
|
gst_object_unref (element);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_append_buffer:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @buf: (transfer full):The media data to append
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Schedules the bytes inside @buf to be processed by @self. When it is possible
|
||
|
* to accept the supplied data, it will be processed asynchronously and fill in
|
||
|
* the track buffers for playback purposes.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-appendbuffer)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_append_buffer (GstSourceBuffer * self, GstBuffer * buf,
|
||
|
GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
g_return_val_if_fail (GST_IS_BUFFER (buf), FALSE);
|
||
|
|
||
|
if (is_removed (self) || is_updating (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"source buffer is removed or still updating");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_errored (self)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"source buffer has encountered error");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (is_ended (self)) {
|
||
|
open_parent (self);
|
||
|
}
|
||
|
|
||
|
GstMediaSource *source = get_media_source (self);
|
||
|
gsize buffer_size = gst_buffer_get_size (buf);
|
||
|
GstClockTime position = gst_media_source_get_position (source);
|
||
|
GstClockTime duration = gst_media_source_get_duration (source);
|
||
|
|
||
|
gst_object_unref (source);
|
||
|
|
||
|
evict_coded_frames (self, buffer_size, self->size_limit, position, duration);
|
||
|
|
||
|
if (will_overflow (self, buffer_size)) {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_QUOTA_EXCEEDED,
|
||
|
"buffer is full");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
g_return_val_if_fail (self->pending_data == NULL, FALSE);
|
||
|
|
||
|
set_pending_data (self, buf);
|
||
|
set_updating (self);
|
||
|
|
||
|
schedule_event (self, ON_UPDATE_START);
|
||
|
schedule_append_to_buffer_task (self);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_source_buffer_abort:
|
||
|
* @self: #GstSourceBuffer instance
|
||
|
* @error: (out) (optional) (nullable) (transfer full): the resulting error or `NULL`
|
||
|
*
|
||
|
* Attempts to end any processing of the currently pending data and reset the
|
||
|
* media parser.
|
||
|
*
|
||
|
* [Specification](https://www.w3.org/TR/media-source-2/#dom-sourcebuffer-abort)
|
||
|
*
|
||
|
* Returns: `TRUE` on success, `FALSE` otherwise
|
||
|
* Since: 1.24
|
||
|
*/
|
||
|
gboolean
|
||
|
gst_source_buffer_abort (GstSourceBuffer * self, GError ** error)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
if (gst_append_pipeline_eos (self->append_pipeline) == GST_FLOW_OK) {
|
||
|
return TRUE;
|
||
|
} else {
|
||
|
g_set_error (error,
|
||
|
GST_MEDIA_SOURCE_ERROR, GST_MEDIA_SOURCE_ERROR_INVALID_STATE,
|
||
|
"failed to abort source buffer");
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_source_buffer_has_init_segment (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), FALSE);
|
||
|
return gst_append_pipeline_has_init_segment (self->append_pipeline);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
is_buffered_fold (const GValue * item, IsBufferedAccumulator * acc,
|
||
|
GstSourceBuffer * self)
|
||
|
{
|
||
|
GstSample *sample = gst_value_get_sample (item);
|
||
|
GstBuffer *buffer = gst_sample_get_buffer (sample);
|
||
|
GstClockTime buffer_start = GST_BUFFER_DTS (buffer);
|
||
|
GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer);
|
||
|
if (acc->time < buffer_start) {
|
||
|
GST_TRACE_OBJECT (self, "position precedes buffer start, done");
|
||
|
acc->buffered = FALSE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (acc->time >= buffer_start && acc->time < buffer_end) {
|
||
|
GST_TRACE_OBJECT (self, "position is within buffer, done");
|
||
|
acc->buffered = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_source_buffer_is_buffered (GstSourceBuffer * self, GstClockTime time)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gboolean buffered = TRUE;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer key, value;
|
||
|
buffered && g_hash_table_iter_next (&iter, &key, &value);) {
|
||
|
GstMediaSourceTrack *track = key;
|
||
|
if (!gst_media_source_track_get_active (track)) {
|
||
|
continue;
|
||
|
}
|
||
|
GstMediaSourceTrackBuffer *track_buffer = value;
|
||
|
IsBufferedAccumulator acc = {
|
||
|
.time = time,
|
||
|
.buffered = FALSE,
|
||
|
};
|
||
|
GstIterator *iter =
|
||
|
gst_media_source_track_buffer_iter_samples (track_buffer, time, NULL);
|
||
|
while (gst_iterator_fold (iter, (GstIteratorFoldFunction) is_buffered_fold,
|
||
|
(GValue *) & acc, self) == GST_ITERATOR_RESYNC) {
|
||
|
gst_iterator_resync (iter);
|
||
|
}
|
||
|
gst_iterator_free (iter);
|
||
|
buffered = acc.buffered;
|
||
|
}
|
||
|
return buffered;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
is_range_buffered_fold (const GValue * item, IsRangeBufferedAccumulator * acc,
|
||
|
GstSourceBuffer * self)
|
||
|
{
|
||
|
GstSample *sample = gst_value_get_sample (item);
|
||
|
GstBuffer *buffer = gst_sample_get_buffer (sample);
|
||
|
GstClockTime buffer_start = GST_BUFFER_DTS (buffer);
|
||
|
GstClockTime buffer_end = buffer_start + GST_BUFFER_DURATION (buffer);
|
||
|
|
||
|
GstClockTime start = acc->start;
|
||
|
GstClockTime end = acc->end;
|
||
|
|
||
|
if (!acc->start_buffered) {
|
||
|
if (start < buffer_start) {
|
||
|
GST_TRACE_OBJECT (self, "start position precedes buffer start, done");
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (start >= buffer_start && start < buffer_end) {
|
||
|
GST_TRACE_OBJECT (self, "start position is within buffer, checking end");
|
||
|
acc->start_buffered = TRUE;
|
||
|
return TRUE;
|
||
|
}
|
||
|
} else {
|
||
|
if (end < buffer_start) {
|
||
|
GST_TRACE_OBJECT (self, "end position precedes buffer start, done");
|
||
|
return FALSE;
|
||
|
}
|
||
|
if (end <= buffer_end) {
|
||
|
GST_TRACE_OBJECT (self, "end position is within buffer, done");
|
||
|
acc->end_buffered = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_source_buffer_is_range_buffered (GstSourceBuffer * self, GstClockTime start,
|
||
|
GstClockTime end)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gboolean buffered = TRUE;
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer key, value;
|
||
|
buffered && g_hash_table_iter_next (&iter, &key, &value);) {
|
||
|
GstMediaSourceTrack *track = key;
|
||
|
if (!gst_media_source_track_get_active (track)) {
|
||
|
continue;
|
||
|
}
|
||
|
GstMediaSourceTrackBuffer *track_buffer = value;
|
||
|
IsRangeBufferedAccumulator acc = {
|
||
|
.start = start,
|
||
|
.end = end,
|
||
|
.start_buffered = FALSE,
|
||
|
.end_buffered = FALSE,
|
||
|
};
|
||
|
GstIterator *iter =
|
||
|
gst_media_source_track_buffer_iter_samples (track_buffer, start, NULL);
|
||
|
while (gst_iterator_fold (iter,
|
||
|
(GstIteratorFoldFunction) is_range_buffered_fold, (GValue *) & acc,
|
||
|
self) == GST_ITERATOR_RESYNC) {
|
||
|
gst_iterator_resync (iter);
|
||
|
}
|
||
|
buffered = acc.end_buffered;
|
||
|
gst_iterator_free (iter);
|
||
|
}
|
||
|
return buffered;
|
||
|
}
|
||
|
|
||
|
GstClockTime
|
||
|
gst_source_buffer_get_duration (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), GST_CLOCK_TIME_NONE);
|
||
|
return gst_append_pipeline_get_duration (self->append_pipeline);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gst_source_buffer_teardown (GstSourceBuffer * self)
|
||
|
{
|
||
|
reset_parser_state (self);
|
||
|
clear_updating (self);
|
||
|
}
|
||
|
|
||
|
GPtrArray *
|
||
|
gst_source_buffer_get_all_tracks (GstSourceBuffer * self)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_SOURCE_BUFFER (self), NULL);
|
||
|
|
||
|
GPtrArray *tracks = g_ptr_array_new ();
|
||
|
|
||
|
GPtrArray *audio_tracks = gst_append_pipeline_get_audio_tracks
|
||
|
(self->append_pipeline);
|
||
|
GPtrArray *text_tracks = gst_append_pipeline_get_text_tracks
|
||
|
(self->append_pipeline);
|
||
|
GPtrArray *video_tracks = gst_append_pipeline_get_video_tracks
|
||
|
(self->append_pipeline);
|
||
|
|
||
|
if (audio_tracks) {
|
||
|
g_ptr_array_extend (tracks, audio_tracks, NULL, NULL);
|
||
|
}
|
||
|
if (text_tracks) {
|
||
|
g_ptr_array_extend (tracks, text_tracks, NULL, NULL);
|
||
|
}
|
||
|
if (video_tracks) {
|
||
|
g_ptr_array_extend (tracks, video_tracks, NULL, NULL);
|
||
|
}
|
||
|
|
||
|
return tracks;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
seek_track_buffer (GstMediaSourceTrack * track,
|
||
|
GstMediaSourceTrackBuffer * buffer, GstSourceBuffer * self)
|
||
|
{
|
||
|
TrackFeedTask *feed = get_track_feed (self, track);
|
||
|
|
||
|
const gchar *track_id = gst_media_source_track_get_id (track);
|
||
|
GST_DEBUG_OBJECT (self, "%s: seeking", track_id);
|
||
|
reset_track_feed (feed);
|
||
|
GST_DEBUG_OBJECT (self, "%s: restarted track feed", track_id);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gst_source_buffer_seek (GstSourceBuffer * self, GstClockTime time)
|
||
|
{
|
||
|
g_return_if_fail (GST_IS_SOURCE_BUFFER (self));
|
||
|
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (time));
|
||
|
self->seek_time = time;
|
||
|
g_hash_table_foreach (self->track_buffers, (GHFunc) seek_track_buffer, self);
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_source_buffer_get_active (GstSourceBuffer * self)
|
||
|
{
|
||
|
gboolean active = FALSE;
|
||
|
GHashTableIter iter;
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
g_hash_table_iter_init (&iter, self->track_buffers);
|
||
|
for (gpointer key; !active && g_hash_table_iter_next (&iter, &key, NULL);) {
|
||
|
GstMediaSourceTrack *track = GST_MEDIA_SOURCE_TRACK (key);
|
||
|
active |= gst_media_source_track_get_active (track);
|
||
|
}
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
return active;
|
||
|
}
|