New HLS, DASH and MSS adaptive demuxer elements

This provides new HLS, DASH and MSS adaptive demuxer elements as a single plugin.

These elements offer many improvements over the legacy elements. They will only
work within a streams-aware context (`urisourcebin`, `uridecodebin3`,
`decodebin3`, `playbin3`, ...).

Stream selection and buffering is handled internally, this allows them to
directly manage the elementary streams and stream selection.

Authors:
* Edward Hervey <edward@centricular.com>
* Jan Schmidt <jan@centricular.com>
* Piotrek Brzeziński <piotr@centricular.com>
* Tim-Philipp Müller <tim@centricular.com>

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2117>
This commit is contained in:
Edward Hervey 2022-03-11 17:11:50 +01:00 committed by GStreamer Marge Bot
parent 8dcb8a28af
commit af78c16dd5
101 changed files with 45968 additions and 0 deletions

View file

@ -0,0 +1,319 @@
# Adaptive Demuxers v2
The existing adaptive demuxer support in `gst-plugins-bad` has several pitfalls
that prevents improving it easily. The existing design uses a model where an
adaptive streaming element (`dashdemux`, `hlsdemux`) downloads multiplexed
fragments of media, but then relies on other components in the pipeline to
provide download buffering, demuxing, elementary stream handling and decoding.
The problems with the old design include:
1. An assumption that fragment streams (to download) are equal to output
(elementary) streams.
* This made it hard to expose `GstStream` and `GstStreamCollection`
describing the available media streams, and by extension it is difficult to
provide efficient stream selection support
2. By performing download buffering outside the adaptive streaming element,
the download scheduling has no visibility into the presentation timeline.
* This made it impossible to handle more efficient variant selection and
download strategy
3. Several issues with establishing accurate timing/duration of fragments due to
not dealing with parsed data
* Especially with HLS, which does not provide detailed timing information
about the underlying media streams to the same extent that DASH does.
4. Aging design that grew organically since initial adaptive demuxers and miss
better understanding of how they should work in 2020
* The code is complicated and interwoven in ways that are hard to follow
and reason about.
5. Use of GStreamer pipeline sources for downloading.
* An internal download pipeline that contains a `httpsrc -> queue2 -> src`
chain makes download management, bandwidth estimation and stream parsing
more difficult, and uses a new thread for each download.
# New design
## High-level overview of the new AdaptiveDemux base class:
* Buffering is handled inside the adaptive streaming element, based on
elementary streams (i.e. de-multiplexed from the downloaded fragments) and
stored inside the `adaptivedemux`-based element.
* The download strategy has full visibility on bitrates, bandwidth, per-stream
queueing level (in time and bytes), playback position, etc. This opens up the
possibility of much more intelligent adaptive download strategies.
* Output pads are not handled directly by the subclasses. Instead subclasses
specify which `tracks` of elementary streams they can provide and what
"download streams" can provide contents for those tracks. The baseclass
handles usage and activation of the `tracks` based on application
`select-streams` requests, and activation of the `stream` needed to feed each
selected `track`.
* Output is done from a single thread, with the various elementary streams
packets being output in time order (i.e. behaving like a regular demuxer, with
interleaving reduced to its minimum). There is minimal buffering downstream
in the pipeline - only the amount required to perform decode and display.
* The adaptive streaming element only exposes `src` pads for the selected
`GstStream`s. Typically, there will be one video track, one audio track and
perhaps one subtitle track exposed at a time, for example.
* Stream selection is handled by the element directly. When switching on a
new media stream, the output to the relevant source pad is switched once
there is enough content buffered on the newly requested stream,
providing rapid and seamless stream switching.
* Only 3 threads are used regardless of the number of streams/tracks. One is
dedicated to download, one for output, and one for scheduling and feeding
contents to the tracks.
The main components of the new adaptive demuxers are:
* `GstAdaptiveDemuxTrack` : end-user meaningful elementary streams. Those can be
selected by the user. They are provided by the subclasses based on the
manifest.
* They each correspond to a `GstStream` of a `GstStreamCollection`
* They are unique by `GstStreamType` and any other unique identifier specified
by the manifest (ex: language)
* The caps *can* change through time
* `OutputSlot` : A track being exposed via a source pad. This is handled by the
parent class.
* `GstAdaptiveDemuxStream` : implementation-specific download stream. This is
linked to one or more `GstAdaptiveDemuxTrack`. The contents of that stream
will be parsed (via `parsebin`) and fed to the target tracks.
* What tracks are provided by a given `GstAdaptiveDemuxStream` is specified by
the subclass. But can also be discovered at runtime if the manifest did not
provide enough information (for example with HLS).
* Download thread : Receives download requests from the scheduling thread that
can be queried and interrupted. Performs all download servicing in a
single dedicated thread that can estimate download bandwidth across all
simultaneous requests.
* Scheduling thread : In charge of deciding what new downloads should be started
based on overall position, track buffering levels, selected tracks, current
time ... It is also in charge of handling completed downloads. Fragment
downloads are sent to dedicated `parsebin` elements that feed the parsed
elementary data to `GstAdaptiveDemuxTrack`
* Output thread : In charge of deciding which track should be
outputted/removed/switched (via `OutputSlot`) based on requested selection and
track levels.
## Track(s) and Stream(s)
Adaptive Demuxers provide one or more *Track* of elementary streams. They are
each unique in terms of:
* Their type (audio, video, text, ..). Ex : `GST_STREAM_TYPE_AUDIO`
* Optional: Their codec. Ex : `video/x-h264`
* Optional: Their language. ex : `GST_TAG_LANGUAGE : "fr"`
* Optional: Their number of channels (ex: stereo vs 5.1). ex
`audio/x-vorbis,channels=2`
* Any other feature which would make the stream "unique" either because of their
nature (ex: video angle) or specified by the manifest as being "unique".
But tracks can vary over time by:
* bitrate
* profile or level
* resolution
They correspond to what an end-user might want to select (i.e. will be exposed
in a `GstStreamCollection`). They are each identified by a `stream_id` provided
by the subclass.
> **Note:** A manifest *can* specify that tracks that would normally be separate
> based on the above rules (for example different codecs or channels) are
> actually the same "end-user selectable stream" (i.e. track). In such case only
> one track is provided and the nature of the elementary stream can change
> through time.
Adaptive Demuxers subclasses also need to provide one or more *Download Stream*
(`GstAdaptiveDemuxStream`) which are the implementation-/manifest-specific
"streams" that each feed one or more *Track*. Those streams can also vary over
time by bitrate/profile/resolution/... but always target the same tracks.
The downloaded data from each of those `GstAdaptiveDemuxStream` is fed to a
`parsebin` element which will put the output in the associated
`GstAdaptiveDemuxTrack`.
The tracks have some buffering capability, handled by the baseclass.
This separation allows the base-class to:
* decide which download stream(s) should be (de)activated based on the current
track selection
* decide when to (re)start download requests based on buffering levels, positions and
external actions.
* Handle buffering, output and stream selection.
The subclass is responsible for deciding:
* *Which* next download should be requested for that stream based on current
playback position, the provided encoded bitrates, estimates of download
bandwidth, buffering levels, etc..
Subclasses can also decide, before passing the downloaded data over, to:
* pre-parse specific headers (ex: ID3 and webvtt headers in HLS, MP4 fragment
position, etc..).
* pre-parse actual content if needed because a position estimation is needed
(ex: HLS missing accurate positioning of fragments in the overall timeline)
* rewrite the content altogether (for example webvtt fragments which require
timing to be re-computed)
## Timeline, position, playout
Adaptive Demuxers decide what to download based on a *Timeline* made of one or
more *Tracks*.
The output of that *Timeline* is synchronized (each *Track* pushes downstream at
more or less the same position in time). That position is the "Global Output
Position".
The *Timeline* should have sufficient data in each track to allow all tracks to
be decoded and played back downstream without introducing stalls. It is the goal
of the *Scheduling thread* of adaptive demuxers to determine which fragment of
data to download and at which moment, in order for:
* each track to have sufficient data for continuous playback downstream
* the overall buffering to not exceed specified limits (in time and/or bytes)
* the playback position to not stray away in case of live sources and
low-latency scenarios.
Which *Track* is selected on that *Timeline* is either:
* decided by the element (default choices)
* decided by the user (via `GST_EVENT_SELECT_STREAMS`)
The goal of an Adaptive Demuxer is to establish *which* fragment to download and
*when* based on:
* The selected *Tracks*
* The current *Timeline* output position
* The current *Track* download position (i.e. how much is buffered)
* The available bandwidth (calculated based on download speed)
* The bitrate of each fragment stream provided
* The current time (for live sources)
In the future, an Adaptive Demuxer will be able to decide to discard a fragment
if it estimates it can switch to a higher/lower variant in time to still satisfy
the above requirements.
## Download helper and thread
Based on the above, each Adaptive Demuxer implementation specifies to a
*Download Loop* which fragment to download next and when.
Multiple downloads can be requested at the same time on that thread. It is the
responsibility of the *Scheduler thread* to decide what to do when a download is
completed.
Since all downloads are done in a dedicated thread without any blocking, it can
estimate current bandwidth and latency, which the element can use to switch
variants and improve buffering strategy.
> **Note**: Unlike the old design, the `libsoup` library is used directly for
> downloading, and not via external GStreamer elements. In the future, this
> could be made modular so that other HTTP libraries can be used instead.
## Stream Selection
When sending `GST_EVENT_STREAM_COLLECTION` downstream, the adaptive demuxer also
specifies on the event that it can handle stream-selection. Downstream elements
(i.e. `decodebin3`) won't attempt to do any selection but will
handle/decode/expose all the streams provided by the adaptive demuxer (including
streams that get added/removed at runtime).
When handling a `GST_EVENT_SELECT_STREAMS`, the adaptive demuxer will:
* mark the requested tracks as `selected` (and no-longer requested ones as not
selected)
* instruct the streams associated to no-longer selected tracks to stop
* set the current output position on streams associated to newly selected
tracks and instruct them to be started
* return
The actual changes in output (because of a stream selection change) are done in
the output thread
* If a track is no longer selected and there are no candidate replacement tracks
of the same type, the associated output/pad is removed and the track is
drained.
* If a track is selected and doesn't have a candidate replacement slot of the
same type, a new output/pad is added for that track
* If a track is selected and has a candidate replacement slot, it will only be
switched if the track it is replacing is empty *OR* when it has enough
buffering so the switch can happen without re-triggering buffering.
## Periods
The number and type of `GstAdaptiveDemuxTrack` and `GstAdaptiveDemuxStream` can
not change once the initial manifests are parsed.
In order to change that (for example in the case of a new DASH period), a new
`GstAdaptiveDemuxPeriod` must be started.
All the tracks and streams that are created at any given time are associated to
the current `input period`. The streams of the input period are the ones that
are active (i.e. downloading), and by extension the tracks of that input period
are the ones that are being filled (if selected).
That period *could* also be the `output period`. The (selected) tracks of that
period are the ones that are used for output by the output thread.
But due to buffering, the input and output period *could* be different, the
baseclass will automatically handle switch over.
The only requirement for subclasses is to ask the parent class to start a new
period when needed and then create the new tracks and streams.
## Responsibility split
The `GstAdaptiveDemux2` base class is in charge of:
* helper for all downloads.
* helper for parsing (using `parsebin` and custom parsing functions) stream data.
* provides *parsed* elementary content for each fragment (note: could be more
than one output stream for a given fragment)
* helper for providing `Tracks` that can be filled by subclasses.
* dealing with stream selection and output, including notifying subclasses which
of those *are* active or not
* handling buffering and deciding when to request new data from associated stream
Subclasses are in charge of:
* specifying which `GstAdaptiveDemuxTrack` and `GstAdaptiveDemuxStream` they
provide (based on the manifest) and their relationship.
* when requested by the base class, specify which `GstAdaptiveDemuxFragment`
should be downloaded next for a given (selected) stream.

View file

@ -992,6 +992,426 @@
"tracers": {}, "tracers": {},
"url": "Unknown package origin" "url": "Unknown package origin"
}, },
"adaptivedemux2": {
"description": "Adaptive Streaming 2 plugin",
"elements": {
"dashdemux2": {
"author": "Edward Hervey <edward@centricular.com>\nJan Schmidt <jan@centricular.com>",
"description": "Dynamic Adaptive Streaming over HTTP demuxer",
"hierarchy": [
"GstDashDemux2",
"GstAdaptiveDemux2",
"GstBin",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"interfaces": [
"GstChildProxy"
],
"klass": "Codec/Demuxer/Adaptive",
"long-name": "DASH Demuxer",
"pad-templates": {
"audio_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"sink": {
"caps": "application/dash+xml:\n",
"direction": "sink",
"presence": "always"
},
"subtitle_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"video_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
}
},
"properties": {
"max-bitrate": {
"blurb": "Max of bitrate supported by target video decoder (0 = no maximum)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"max-video-framerate": {
"blurb": "Max video framerate to select (0/1 = no maximum)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0/1",
"max": "2147483647/1",
"min": "0/1",
"mutable": "null",
"readable": true,
"type": "GstFraction",
"writable": true
},
"max-video-height": {
"blurb": "Max video height to select (0 = no maximum)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"max-video-width": {
"blurb": "Max video width to select (0 = no maximum)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"presentation-delay": {
"blurb": "Default presentation delay (in seconds, milliseconds or fragments) (e.g. 12s, 2500ms, 3f)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "10s",
"mutable": "null",
"readable": true,
"type": "gchararray",
"writable": true
}
},
"rank": "primary + 1"
},
"hlsdemux2": {
"author": "Edward Hervey <edward@centricular.com>\nJan Schmidt <jan@centricular.com>",
"description": "HTTP Live Streaming demuxer",
"hierarchy": [
"GstHLSDemux2",
"GstAdaptiveDemux2",
"GstBin",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"interfaces": [
"GstChildProxy"
],
"klass": "Codec/Demuxer/Adaptive",
"long-name": "HLS Demuxer",
"pad-templates": {
"audio_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"sink": {
"caps": "application/x-hls:\n",
"direction": "sink",
"presence": "always"
},
"subtitle_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"video_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
}
},
"properties": {
"start-bitrate": {
"blurb": "Initial bitrate to use to choose first alternate (0 = automatic) (bits/s)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
}
},
"rank": "primary + 1"
},
"mssdemux2": {
"author": "Thiago Santos <thiago.sousa.santos@collabora.com>",
"description": "Parse and demultiplex a Smooth Streaming manifest into audio and video streams",
"hierarchy": [
"GstMssDemux2",
"GstAdaptiveDemux2",
"GstBin",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"interfaces": [
"GstChildProxy"
],
"klass": "Codec/Demuxer/Adaptive",
"long-name": "Smooth Streaming demuxer (v2)",
"pad-templates": {
"audio_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"sink": {
"caps": "application/vnd.ms-sstr+xml:\n",
"direction": "sink",
"presence": "always"
},
"subtitle_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
},
"video_%%02u": {
"caps": "ANY",
"direction": "src",
"presence": "sometimes"
}
},
"rank": "primary + 1"
}
},
"filename": "gstadaptivedemux2",
"license": "LGPL",
"other-types": {
"GstAdaptiveDemux2": {
"hierarchy": [
"GstAdaptiveDemux2",
"GstBin",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"interfaces": [
"GstChildProxy"
],
"kind": "object",
"properties": {
"bandwidth-target-ratio": {
"blurb": "Limit of the available bitrate to use when switching to alternates",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0.8",
"max": "1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "gfloat",
"writable": true
},
"connection-bitrate": {
"blurb": "Network connection speed to use (0 = automatic) (bits/s)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"connection-speed": {
"blurb": "Network connection speed to use in kbps (0 = calculate from downloaded fragments)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "4294967",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"current-bandwidth": {
"blurb": "Report of current download bandwidth (based on arriving data) (bits/s)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": false
},
"current-level-time-audio": {
"blurb": "Currently buffered level of audio track(s) (ns)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "18446744073709551615",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "guint64",
"writable": false
},
"current-level-time-video": {
"blurb": "Currently buffered level of video track(s) (ns)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "18446744073709551615",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "guint64",
"writable": false
},
"high-watermark-fragments": {
"blurb": "High watermark for parsed data above which downloads are paused (in fragments, 0=disable)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "3.40282e+38",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "gdouble",
"writable": true
},
"high-watermark-time": {
"blurb": "High watermark for parsed data above which downloads are paused (in ns, 0=disable)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "30000000000",
"max": "18446744073709551615",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "guint64",
"writable": true
},
"low-watermark-fragments": {
"blurb": "Low watermark for parsed data below which downloads are resumed (in fragments, 0=disable)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "3.40282e+38",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "gdouble",
"writable": true
},
"low-watermark-time": {
"blurb": "Low watermark for parsed data below which downloads are resumed (in ns, 0=disable)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "3000000000",
"max": "18446744073709551615",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "guint64",
"writable": true
},
"max-bitrate": {
"blurb": "Maximum bitrate to use when switching to alternates (bits/s)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"max-buffering-time": {
"blurb": "Upper limit on the high watermark for parsed data, above which downloads are paused (in ns, 0=disable)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "30000000000",
"max": "18446744073709551615",
"min": "0",
"mutable": "playing",
"readable": true,
"type": "guint64",
"writable": true
},
"min-bitrate": {
"blurb": "Minimum bitrate to use when switching to alternates (bits/s)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
}
}
}
},
"package": "GStreamer Good Plug-ins",
"source": "gst-plugins-good",
"tracers": {},
"url": "Unknown package origin"
},
"alaw": { "alaw": {
"description": "ALaw audio conversion routines", "description": "ALaw audio conversion routines",
"elements": { "elements": {

View file

@ -95,6 +95,7 @@ foreach plugin_name: list_plugin_res.stdout().split(':')
gst_c_sources: [ gst_c_sources: [
'../sys/*/*.[cmh]', '../sys/*/*.[cmh]',
'../ext/*/*.[ch]', '../ext/*/*.[ch]',
'../ext/*/*/*.[ch]',
'../gst/*/*.[ch]', '../gst/*/*.[ch]',
], ],
gst_c_source_filters: excludes, gst_c_source_filters: excludes,

View file

@ -0,0 +1,13 @@
#ifndef __GST_DASH_DEBUG_H__
#define __GST_DASH_DEBUG_H__
#include <gst/gst.h>
G_BEGIN_DECLS
GST_DEBUG_CATEGORY_EXTERN (gst_dash_demux2_debug);
G_END_DECLS
#endif /* __GST_DASH_DEBUG_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,172 @@
/*
* DASH demux plugin for GStreamer
*
* gstdashdemux.h
*
* Copyright (C) 2012 Orange
* Authors:
* David Corvoysier <david.corvoysier@orange.com>
* Hamid Zakari <hamid.zakari@gmail.com>
*
* Copyright (C) 2013 Smart TV Alliance
* Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
*
* Copyright (C) 2021 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_DASH_DEMUX_H__
#define __GST_DASH_DEMUX_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <gst/base/gstdataqueue.h>
#include "gstisoff.h"
#include "gstadaptivedemux.h"
#include "gstmpdclient.h"
G_BEGIN_DECLS
#define GST_TYPE_DASH_DEMUX2 \
(gst_dash_demux2_get_type())
#define GST_DASH_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DASH_DEMUX2,GstDashDemux2))
#define GST_DASH_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DASH_DEMUX2,GstDashDemux2Class))
#define GST_IS_DASH_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DASH_DEMUX2))
#define GST_IS_DASH_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DASH_DEMUX2))
#define GST_DASH_DEMUX_CAST(obj) \
((GstDashDemux2 *)obj)
#define GST_TYPE_DASH_DEMUX_STREAM \
(gst_dash_demux_stream_get_type())
#define GST_DASH_DEMUX_STREAM(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DASH_DEMUX_STREAM,GstDashDemux2Stream))
#define GST_DASH_DEMUX_STREAM_CAST(obj) ((GstDashDemux2Stream *)obj)
typedef struct _GstDashDemux2Stream GstDashDemux2Stream;
typedef GstAdaptiveDemux2StreamClass GstDashDemux2StreamClass;
typedef struct _GstDashDemux2ClockDrift GstDashDemux2ClockDrift;
typedef struct _GstDashDemux2 GstDashDemux2;
typedef struct _GstDashDemux2Class GstDashDemux2Class;
struct _GstDashDemux2Stream
{
GstAdaptiveDemux2Stream parent;
gint index;
GstActiveStream *active_stream;
/* Track provided by this stream */
GstAdaptiveDemuxTrack *track;
GstMediaFragmentInfo current_fragment;
/* index parsing */
GstSidxParser sidx_parser;
GstClockTime sidx_position;
gint64 sidx_base_offset;
gboolean allow_sidx;
GstClockTime pending_seek_ts;
GstAdapter *adapter;
/* current offset of the first byte in the adapter / last byte we pushed or
* dropped*/
guint64 current_offset;
/* index = 1, header = 2, data = 3 */
guint current_index_header_or_data;
/* ISOBMFF box parsing */
gboolean is_isobmff;
struct {
/* index = 1, header = 2, data = 3 */
guint32 current_fourcc;
guint64 current_start_offset;
guint64 current_size;
} isobmff_parser;
GstMoofBox *moof;
guint64 moof_offset, moof_size;
GArray *moof_sync_samples;
guint current_sync_sample;
guint64 moof_average_size;
guint64 keyframe_average_size;
guint64 keyframe_average_distance;
gboolean first_sync_sample_after_moof, first_sync_sample_always_after_moof;
/* Internal position value, at the keyframe/entry level */
GstClockTime actual_position;
/* Timestamp of the beginning of the current fragment */
GstClockTime current_fragment_timestamp;
GstClockTime current_fragment_duration;
GstClockTime current_fragment_keyframe_distance;
/* Average keyframe download time (only in trickmode-key-units) */
GstClockTime average_download_time;
/* Cached target time (only in trickmode-key-units) */
GstClockTime target_time;
/* Average skip-ahead time (only in trickmode-key-units) */
GstClockTime average_skip_size;
};
/**
* GstDashDemux2:
*
* Opaque #GstDashDemux2 data structure.
*/
struct _GstDashDemux2
{
GstAdaptiveDemux parent;
GSList *next_periods;
GstMPDClient2 *client; /* MPD client */
GMutex client_lock;
GstDashDemux2ClockDrift *clock_drift;
gboolean end_of_period;
gboolean end_of_manifest;
/* Properties */
gint max_video_width, max_video_height;
gint max_video_framerate_n, max_video_framerate_d;
gchar* default_presentation_delay; /* presentation time delay if MPD@suggestedPresentationDelay is not present */
gboolean allow_trickmode_key_units;
};
struct _GstDashDemux2Class
{
GstAdaptiveDemuxClass parent_class;
};
GType gst_dash_demux2_get_type (void);
GType gst_dash_demux_stream_get_type (void);
GST_ELEMENT_REGISTER_DECLARE (dashdemux2);
G_END_DECLS
#endif /* __GST_DASH_DEMUX_H__ */

View file

@ -0,0 +1,273 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdadaptationsetnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDAdaptationSetNode2, gst_mpd_adaptation_set_node,
GST_TYPE_MPD_REPRESENTATION_BASE_NODE);
enum
{
PROP_MPD_ADAPTATION_SET_0,
PROP_MPD_ADAPTATION_SET_ID,
PROP_MPD_ADAPTATION_SET_CONTENT_TYPE,
};
/* GObject VMethods */
static void
gst_mpd_adaptation_set_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDAdaptationSetNode *self = GST_MPD_ADAPTATION_SET_NODE (object);
switch (prop_id) {
case PROP_MPD_ADAPTATION_SET_ID:
self->id = g_value_get_int (value);
break;
case PROP_MPD_ADAPTATION_SET_CONTENT_TYPE:
g_free (self->contentType);
self->contentType = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_adaptation_set_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDAdaptationSetNode *self = GST_MPD_ADAPTATION_SET_NODE (object);
switch (prop_id) {
case PROP_MPD_ADAPTATION_SET_ID:
g_value_set_int (value, self->id);
break;
case PROP_MPD_ADAPTATION_SET_CONTENT_TYPE:
g_value_set_string (value, self->contentType);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_adaptation_set_node_finalize (GObject * object)
{
GstMPDAdaptationSetNode *self = GST_MPD_ADAPTATION_SET_NODE (object);
if (self->lang)
xmlFree (self->lang);
if (self->contentType)
xmlFree (self->contentType);
g_slice_free (GstXMLRatio, self->par);
g_slice_free (GstXMLConditionalUintType, self->segmentAlignment);
g_slice_free (GstXMLConditionalUintType, self->subsegmentAlignment);
g_list_free_full (self->Accessibility,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Role,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Rating,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Viewpoint,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
gst_mpd_segment_base_node_free (self->SegmentBase);
gst_mpd_segment_list_node_free (self->SegmentList);
gst_mpd_segment_template_node_free (self->SegmentTemplate);
g_list_free_full (self->BaseURLs, (GDestroyNotify) gst_mpd_baseurl_node_free);
g_list_free_full (self->Representations,
(GDestroyNotify) gst_mpd_representation_node_free);
g_list_free_full (self->ContentComponents,
(GDestroyNotify) gst_mpd_content_component_node_free);
if (self->xlink_href)
xmlFree (self->xlink_href);
G_OBJECT_CLASS (gst_mpd_adaptation_set_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_adaptation_set_get_xml_node (GstMPDNode * node)
{
xmlNodePtr adaptation_set_xml_node = NULL;
GstMPDAdaptationSetNode *self = GST_MPD_ADAPTATION_SET_NODE (node);
adaptation_set_xml_node = xmlNewNode (NULL, (xmlChar *) "AdaptationSet");
if (self->id)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "id", self->id);
if (self->group)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "group",
self->group);
if (self->lang)
gst_xml_helper_set_prop_string (adaptation_set_xml_node, "lang",
self->lang);
if (self->contentType)
gst_xml_helper_set_prop_string (adaptation_set_xml_node, "contentType",
self->contentType);
if (self->minBandwidth)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "minBandwidth",
self->minBandwidth);
if (self->maxBandwidth)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "maxBandwidth",
self->maxBandwidth);
if (self->minWidth)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "minWidth",
self->minWidth);
if (self->maxWidth)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "maxWidth",
self->maxWidth);
if (self->minHeight)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "minHeight",
self->minHeight);
if (self->maxHeight)
gst_xml_helper_set_prop_uint (adaptation_set_xml_node, "maxHeight",
self->maxHeight);
if (self->par)
gst_xml_helper_set_prop_ratio (adaptation_set_xml_node, "par", self->par);
gst_xml_helper_set_prop_cond_uint (adaptation_set_xml_node,
"segmentAlignment", self->segmentAlignment);
gst_xml_helper_set_prop_cond_uint (adaptation_set_xml_node,
"subsegmentAlignment", self->subsegmentAlignment);
gst_xml_helper_set_prop_uint (adaptation_set_xml_node,
"subsegmentStartsWithSAP", self->subsegmentStartsWithSAP);
gst_xml_helper_set_prop_boolean (adaptation_set_xml_node,
"bitstreamSwitching", self->bitstreamSwitching);
g_list_foreach (self->Accessibility, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
g_list_foreach (self->Role, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
g_list_foreach (self->Rating, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
g_list_foreach (self->Viewpoint, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
gst_mpd_node_add_child_node (GST_MPD_NODE (self->SegmentBase),
adaptation_set_xml_node);
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentList), adaptation_set_xml_node);
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentTemplate), adaptation_set_xml_node);
g_list_foreach (self->BaseURLs, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
g_list_foreach (self->Representations,
gst_mpd_representation_base_node_get_list_item, adaptation_set_xml_node);
g_list_foreach (self->ContentComponents, gst_mpd_node_get_list_item,
adaptation_set_xml_node);
if (self->xlink_href)
gst_xml_helper_set_prop_string (adaptation_set_xml_node, "xlink_href",
self->xlink_href);
if (self->actuate == GST_MPD_XLINK_ACTUATE_ON_LOAD)
gst_xml_helper_set_prop_string (adaptation_set_xml_node, "actuate",
(gchar *) GST_MPD_XLINK_ACTUATE_ON_LOAD_STR);
return adaptation_set_xml_node;
}
static void
gst_mpd_adaptation_set_node_class_init (GstMPDAdaptationSetNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_adaptation_set_node_finalize;
object_class->set_property = gst_mpd_adaptation_set_node_set_property;
object_class->get_property = gst_mpd_adaptation_set_node_get_property;
m_klass->get_xml_node = gst_mpd_adaptation_set_get_xml_node;
g_object_class_install_property (object_class, PROP_MPD_ADAPTATION_SET_ID,
g_param_spec_int ("id", "id",
"adaptation set id", 0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ADAPTATION_SET_CONTENT_TYPE, g_param_spec_string ("content-type",
"content type", "content type of the adaptation set", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_adaptation_set_node_init (GstMPDAdaptationSetNode * self)
{
self->id = 0;
self->group = 0;
self->lang = NULL; /* LangVectorType RFC 5646 */
self->contentType = NULL;
self->par = 0;
self->minBandwidth = 0;
self->maxBandwidth = 0;
self->minWidth = 0;
self->maxWidth = 0;
self->minHeight = 0;
self->maxHeight = 0;
self->segmentAlignment = NULL;
self->subsegmentAlignment = NULL;
self->subsegmentStartsWithSAP = GST_SAP_TYPE_0;
self->bitstreamSwitching = FALSE;
/* list of Accessibility DescriptorType nodes */
self->Accessibility = NULL;
/* list of Role DescriptorType nodes */
self->Role = NULL;
/* list of Rating DescriptorType nodes */
self->Rating = NULL;
/* list of Viewpoint DescriptorType nodes */
self->Viewpoint = NULL;
/* SegmentBase node */
self->SegmentBase = NULL;
/* SegmentList node */
self->SegmentList = NULL;
/* SegmentTemplate node */
self->SegmentTemplate = NULL;
/* list of BaseURL nodes */
self->BaseURLs = NULL;
/* list of Representation nodes */
self->Representations = NULL;
/* list of ContentComponent nodes */
self->ContentComponents = NULL;
self->xlink_href = NULL;
self->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST;
}
GstMPDAdaptationSetNode *
gst_mpd_adaptation_set_node_new (void)
{
return g_object_new (GST_TYPE_MPD_ADAPTATION_SET_NODE, NULL);
}
void
gst_mpd_adaptation_set_node_free (GstMPDAdaptationSetNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,86 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDADAPTATIONSETNODE_H__
#define __GSTMPDADAPTATIONSETNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdrepresentationbasenode.h"
#include "gstmpdsegmentlistnode.h"
#include "gstmpdsegmenttemplatenode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_ADAPTATION_SET_NODE gst_mpd_adaptation_set_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDAdaptationSetNode2, gst_mpd_adaptation_set_node, GST, MPD_ADAPTATION_SET_NODE, GstMPDRepresentationBaseNode)
typedef GstMPDAdaptationSetNode2 GstMPDAdaptationSetNode;
typedef GstMPDAdaptationSetNode2Class GstMPDAdaptationSetNodeClass;
struct _GstMPDAdaptationSetNode2
{
GstMPDRepresentationBaseNode parent_instance;
guint id;
guint group;
gchar *lang; /* LangVectorType RFC 5646 */
gchar *contentType;
GstXMLRatio *par;
guint minBandwidth;
guint maxBandwidth;
guint minWidth;
guint maxWidth;
guint minHeight;
guint maxHeight;
GstXMLConditionalUintType *segmentAlignment;
GstXMLConditionalUintType *subsegmentAlignment;
GstMPDSAPType subsegmentStartsWithSAP;
gboolean bitstreamSwitching;
/* list of Accessibility DescriptorType nodes */
GList *Accessibility;
/* list of Role DescriptorType nodes */
GList *Role;
/* list of Rating DescriptorType nodes */
GList *Rating;
/* list of Viewpoint DescriptorType nodes */
GList *Viewpoint;
/* SegmentBase node */
GstMPDSegmentBaseNode *SegmentBase;
/* SegmentList node */
GstMPDSegmentListNode *SegmentList;
/* SegmentTemplate node */
GstMPDSegmentTemplateNode *SegmentTemplate;
/* list of BaseURL nodes */
GList *BaseURLs;
/* list of Representation nodes */
GList *Representations;
/* list of ContentComponent nodes */
GList *ContentComponents;
gchar *xlink_href;
GstMPDXLinkActuate actuate;
};
GstMPDAdaptationSetNode * gst_mpd_adaptation_set_node_new (void);
void gst_mpd_adaptation_set_node_free (GstMPDAdaptationSetNode* self);
G_END_DECLS
#endif /* __GSTMPDADAPTATIONSETNODE_H__ */

View file

@ -0,0 +1,166 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdbaseurlnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDBaseURLNode2, gst_mpd_baseurl_node, GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_BASEURL_0,
PROP_MPD_BASEURL_URL,
PROP_MPD_BASEURL_SERVICE_LOCATION,
PROP_MPD_BASEURL_BYTE_RANGE,
};
/* GObject VMethods */
static void
gst_mpd_baseurl_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDBaseURLNode *self = GST_MPD_BASEURL_NODE (object);
switch (prop_id) {
case PROP_MPD_BASEURL_URL:
g_free (self->baseURL);
self->baseURL = g_value_dup_string (value);
break;
case PROP_MPD_BASEURL_SERVICE_LOCATION:
g_free (self->serviceLocation);
self->serviceLocation = g_value_dup_string (value);
break;
case PROP_MPD_BASEURL_BYTE_RANGE:
g_free (self->byteRange);
self->byteRange = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_baseurl_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDBaseURLNode *self = GST_MPD_BASEURL_NODE (object);
switch (prop_id) {
case PROP_MPD_BASEURL_URL:
g_value_set_string (value, self->baseURL);
break;
case PROP_MPD_BASEURL_SERVICE_LOCATION:
g_value_set_string (value, self->serviceLocation);
break;
case PROP_MPD_BASEURL_BYTE_RANGE:
g_value_set_string (value, self->byteRange);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_baseurl_node_finalize (GObject * object)
{
GstMPDBaseURLNode *self = GST_MPD_BASEURL_NODE (object);
g_free (self->baseURL);
g_free (self->serviceLocation);
g_free (self->byteRange);
G_OBJECT_CLASS (gst_mpd_baseurl_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_baseurl_get_xml_node (GstMPDNode * node)
{
xmlNodePtr baseurl_xml_node = NULL;
GstMPDBaseURLNode *self = GST_MPD_BASEURL_NODE (node);
baseurl_xml_node = xmlNewNode (NULL, (xmlChar *) "BaseURL");
if (self->serviceLocation)
gst_xml_helper_set_prop_string (baseurl_xml_node, "serviceLocation",
self->serviceLocation);
if (self->byteRange)
gst_xml_helper_set_prop_string (baseurl_xml_node, "byteRange",
self->byteRange);
if (self->baseURL)
gst_xml_helper_set_content (baseurl_xml_node, self->baseURL);
return baseurl_xml_node;
}
static void
gst_mpd_baseurl_node_class_init (GstMPDBaseURLNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_baseurl_node_finalize;
object_class->set_property = gst_mpd_baseurl_node_set_property;
object_class->get_property = gst_mpd_baseurl_node_get_property;
m_klass->get_xml_node = gst_mpd_baseurl_get_xml_node;
g_object_class_install_property (object_class, PROP_MPD_BASEURL_URL,
g_param_spec_string ("url", "base url",
"url of the base url", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_BASEURL_SERVICE_LOCATION,
g_param_spec_string ("service-location", "service location",
"service location", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_BASEURL_BYTE_RANGE,
g_param_spec_string ("byte-range", "byte range", "byte range", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_baseurl_node_init (GstMPDBaseURLNode * self)
{
self->baseURL = NULL;
self->serviceLocation = NULL;
self->byteRange = NULL;
}
GstMPDBaseURLNode *
gst_mpd_baseurl_node_new (void)
{
return g_object_new (GST_TYPE_MPD_BASEURL_NODE, NULL);
}
void
gst_mpd_baseurl_node_free (GstMPDBaseURLNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,49 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDBASEURLNODE_H__
#define __GSTMPDBASEURLNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_BASEURL_NODE gst_mpd_baseurl_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDBaseURLNode2, gst_mpd_baseurl_node, GST, MPD_BASEURL_NODE, GstMPDNode)
typedef GstMPDBaseURLNode2 GstMPDBaseURLNode;
typedef GstMPDBaseURLNode2Class GstMPDBaseURLNodeClass;
struct _GstMPDBaseURLNode2
{
GstObject parent_instance;
gchar *baseURL;
gchar *serviceLocation;
gchar *byteRange;
/* TODO add missing fields such as weight etc.*/
};
GstMPDBaseURLNode * gst_mpd_baseurl_node_new (void);
void gst_mpd_baseurl_node_free (GstMPDBaseURLNode* self);
G_END_DECLS
#endif /* __GSTMPDBASEURLNODE_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,191 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MPDCLIENT_H__
#define __GST_MPDCLIENT_H__
#include "gstmpdparser.h"
#include "downloadhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_CLIENT gst_mpd_client2_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDClient2, gst_mpd_client2, GST, MPD_CLIENT, GstObject)
struct _GstMPDClient2
{
GstObject parent_instance;
GstMPDRootNode *mpd_root_node; /* mpd root node */
GList *periods; /* list of GstStreamPeriod */
guint period_idx; /* index of current Period */
GList *active_streams; /* list of GstActiveStream */
guint update_failed_count;
gchar *mpd_uri; /* manifest file URI */
gchar *mpd_base_uri; /* base URI for resolving relative URIs.
* this will be different for redirects */
/* profiles */
gboolean profile_isoff_ondemand;
DownloadHelper *download_helper;
};
/* Basic initialization/deinitialization functions */
GstMPDClient2 *gst_mpd_client2_new (void);
GstMPDClient2 *gst_mpd_client2_new_static (void);
void gst_mpd_client2_active_streams_free (GstMPDClient2 * client);
void gst_mpd_client2_free (GstMPDClient2 * client);
/* main mpd parsing methods from xml data */
gboolean gst_mpd_client2_parse (GstMPDClient2 * client, const gchar * data, gint size);
/* xml generator */
gboolean gst_mpd_client2_get_xml_content (GstMPDClient2 * client, gchar ** data, gint * size);
void gst_mpd_client2_set_download_helper (GstMPDClient2 * client, DownloadHelper *dh);
void gst_mpd_client2_check_profiles (GstMPDClient2 * client);
void gst_mpd_client2_fetch_on_load_external_resources (GstMPDClient2 * client);
/* Streaming management */
gboolean gst_mpd_client2_setup_media_presentation (GstMPDClient2 *client, GstClockTime time, gint period_index, const gchar *period_id);
gboolean gst_mpd_client2_setup_streaming (GstMPDClient2 * client, GstMPDAdaptationSetNode * adapt_set);
gboolean gst_mpd_client2_setup_representation (GstMPDClient2 *client, GstActiveStream *stream, GstMPDRepresentationNode *representation);
GstClockTime gst_mpd_client2_get_next_fragment_duration (GstMPDClient2 * client, GstActiveStream * stream);
GstClockTime gst_mpd_client2_get_media_presentation_duration (GstMPDClient2 *client);
GstClockTime gst_mpd_client2_get_maximum_segment_duration (GstMPDClient2 * client);
gboolean gst_mpd_client2_get_last_fragment_timestamp_end (GstMPDClient2 * client, guint stream_idx, GstClockTime * ts);
gboolean gst_mpd_client2_get_next_fragment_timestamp (GstMPDClient2 * client, guint stream_idx, GstClockTime * ts);
gboolean gst_mpd_client2_get_next_fragment (GstMPDClient2 *client, guint indexStream, GstMediaFragmentInfo * fragment);
gboolean gst_mpd_client2_get_next_header (GstMPDClient2 *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end);
gboolean gst_mpd_client2_get_next_header_index (GstMPDClient2 *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end);
gboolean gst_mpd_client2_is_live (GstMPDClient2 * client);
gboolean gst_mpd_client2_stream_seek (GstMPDClient2 * client, GstActiveStream * stream, gboolean forward, GstSeekFlags flags, GstClockTime ts, GstClockTime * final_ts);
gboolean gst_mpd_client2_seek_to_time (GstMPDClient2 * client, GDateTime * time);
GstClockTime gst_mpd_client2_get_stream_presentation_offset (GstMPDClient2 *client, guint stream_idx);
gchar** gst_mpd_client2_get_utc_timing_sources (GstMPDClient2 *client, guint methods, GstMPDUTCTimingType *selected_method);
GstClockTime gst_mpd_client2_get_period_start_time (GstMPDClient2 *client);
GstCaps *gst_mpd_client2_get_codec_caps (GstActiveStream *stream);
/* Period selection */
guint gst_mpd_client2_get_period_index_at_time (GstMPDClient2 * client, GstDateTime * time);
gboolean gst_mpd_client2_set_period_index (GstMPDClient2 *client, guint period_idx);
gboolean gst_mpd_client2_set_period_id (GstMPDClient2 *client, const gchar * period_id);
guint gst_mpd_client2_get_period_index (GstMPDClient2 *client);
const gchar *gst_mpd_client2_get_period_id (GstMPDClient2 *client);
gboolean gst_mpd_client2_has_next_period (GstMPDClient2 *client);
gboolean gst_mpd_client2_has_previous_period (GstMPDClient2 * client);
/* Representation selection */
gint gst_mpd_client2_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d);
gint gst_mpd_client2_get_rep_idx_with_min_bandwidth (GList * Representations);
GstDateTime *
gst_mpd_client2_get_availability_start_time (GstMPDClient2 * client);
/* URL management */
const gchar *gst_mpd_client2_get_baseURL (GstMPDClient2 *client, guint indexStream);
gchar *gst_mpd_client2_parse_baseURL (GstMPDClient2 * client, GstActiveStream * stream, gchar ** query);
/* Active stream */
guint gst_mpd_client2_get_nb_active_stream (GstMPDClient2 *client);
GstActiveStream *gst_mpd_client2_get_active_stream_by_index (GstMPDClient2 *client, guint stream_idx);
gboolean gst_mpd_client2_active_stream_contains_subtitles (GstActiveStream * stream);
/* AdaptationSet */
guint gst_mpd_client2_get_nb_adaptationSet (GstMPDClient2 *client);
GList * gst_mpd_client2_get_adaptation_sets (GstMPDClient2 * client);
/* Segment */
gboolean gst_mpd_client2_has_next_segment (GstMPDClient2 * client, GstActiveStream * stream, gboolean forward);
GstFlowReturn gst_mpd_client2_advance_segment (GstMPDClient2 * client, GstActiveStream * stream, gboolean forward);
void gst_mpd_client2_seek_to_first_segment (GstMPDClient2 * client);
GstDateTime *gst_mpd_client2_get_next_segment_availability_start_time (GstMPDClient2 * client, GstActiveStream * stream);
/* Get audio/video stream parameters (caps, width, height, rate, number of channels) */
GstCaps * gst_mpd_client2_get_stream_caps (GstActiveStream * stream);
gboolean gst_mpd_client2_get_bitstream_switching_flag (GstActiveStream * stream);
guint gst_mpd_client2_get_video_stream_width (GstActiveStream * stream);
guint gst_mpd_client2_get_video_stream_height (GstActiveStream * stream);
gboolean gst_mpd_client2_get_video_stream_framerate (GstActiveStream * stream, gint * fps_num, gint * fps_den);
guint gst_mpd_client2_get_audio_stream_rate (GstActiveStream * stream);
guint gst_mpd_client2_get_audio_stream_num_channels (GstActiveStream * stream);
/* Support multi language */
guint gst_mpd_client2_get_list_and_nb_of_audio_language (GstMPDClient2 *client, GList **lang);
GstClockTimeDiff gst_mpd_client2_calculate_time_difference (const GstDateTime * t1, const GstDateTime * t2);
GstDateTime *gst_mpd_client2_add_time_difference (GstDateTime * t1, GstClockTimeDiff diff);
gint64 gst_mpd_client2_parse_default_presentation_delay(GstMPDClient2 * client, const gchar * default_presentation_delay);
/* profiles */
gboolean gst_mpd_client2_has_isoff_ondemand_profile (GstMPDClient2 *client);
/* add/set node methods */
gboolean gst_mpd_client2_set_root_node (GstMPDClient2 * client,
const gchar * property_name,
...);
gchar * gst_mpd_client2_set_period_node (GstMPDClient2 * client,
gchar * period_id,
const gchar * property_name,
...);
guint gst_mpd_client2_set_adaptation_set_node (GstMPDClient2 * client,
gchar * period_id,
guint adap_set_id,
const gchar * property_name,
...);
gchar * gst_mpd_client2_set_representation_node (GstMPDClient2 * client,
gchar * period_id,
guint adap_set_id,
gchar * rep_id,
const gchar * property_name,
...);
gboolean gst_mpd_client2_set_segment_list (GstMPDClient2 * client,
gchar * period_id,
guint adap_set_id,
gchar * rep_id,
const gchar * property_name,
...);
gboolean gst_mpd_client2_set_segment_template (GstMPDClient2 * client,
gchar * period_id,
guint adap_set_id,
gchar * rep_id,
const gchar * property_name,
...);
/* create a new node */
gboolean gst_mpd_client2_add_baseurl_node (GstMPDClient2 * client,
const gchar * property_name,
...);
gboolean gst_mpd_client2_add_segment_url (GstMPDClient2 * client,
gchar * period_id,
guint adap_set_id,
gchar * rep_id,
const gchar * property_name,
...);
G_END_DECLS
#endif /* __GST_MPDCLIENT_H__ */

View file

@ -0,0 +1,120 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdcontentcomponentnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDContentComponentNode2, gst_mpd_content_component_node,
GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_content_component_node_finalize (GObject * object)
{
GstMPDContentComponentNode *self = GST_MPD_CONTENT_COMPONENT_NODE (object);
if (self->lang)
xmlFree (self->lang);
if (self->contentType)
xmlFree (self->contentType);
g_slice_free (GstXMLRatio, self->par);
g_list_free_full (self->Accessibility,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Role,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Rating,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->Viewpoint,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
G_OBJECT_CLASS (gst_mpd_content_component_node_parent_class)->finalize
(object);
}
/* Base class */
static xmlNodePtr
gst_mpd_content_component_get_xml_node (GstMPDNode * node)
{
xmlNodePtr content_component_xml_node = NULL;
GstMPDContentComponentNode *self = GST_MPD_CONTENT_COMPONENT_NODE (node);
content_component_xml_node =
xmlNewNode (NULL, (xmlChar *) "ContentComponent");
gst_xml_helper_set_prop_uint (content_component_xml_node, "id", self->id);
gst_xml_helper_set_prop_string (content_component_xml_node, "lang",
self->lang);
gst_xml_helper_set_prop_string (content_component_xml_node, "contentType",
self->contentType);
gst_xml_helper_set_prop_ratio (content_component_xml_node, "par", self->par);
g_list_foreach (self->Accessibility, gst_mpd_node_get_list_item,
content_component_xml_node);
g_list_foreach (self->Role, gst_mpd_node_get_list_item,
content_component_xml_node);
g_list_foreach (self->Rating, gst_mpd_node_get_list_item,
content_component_xml_node);
g_list_foreach (self->Viewpoint, gst_mpd_node_get_list_item,
content_component_xml_node);
return content_component_xml_node;
}
static void
gst_mpd_content_component_node_class_init (GstMPDContentComponentNodeClass *
klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_content_component_node_finalize;
m_klass->get_xml_node = gst_mpd_content_component_get_xml_node;
}
static void
gst_mpd_content_component_node_init (GstMPDContentComponentNode * self)
{
self->id = 0;
self->lang = NULL;
self->contentType = NULL;
self->par = 0;
self->Accessibility = 0;
self->Role = NULL;
self->Rating = NULL;
self->Viewpoint = NULL;
}
GstMPDContentComponentNode *
gst_mpd_content_component_node_new (void)
{
return g_object_new (GST_TYPE_MPD_CONTENT_COMPONENT_NODE, NULL);
}
void
gst_mpd_content_component_node_free (GstMPDContentComponentNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,57 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDCONTENTCOMPONENTNODE_H__
#define __GSTMPDCONTENTCOMPONENTNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_CONTENT_COMPONENT_NODE gst_mpd_content_component_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDContentComponentNode2, gst_mpd_content_component_node, GST, MPD_CONTENT_COMPONENT_NODE, GstMPDNode)
typedef GstMPDContentComponentNode2 GstMPDContentComponentNode;
typedef GstMPDContentComponentNode2Class GstMPDContentComponentNodeClass;
struct _GstMPDContentComponentNode2
{
GstObject parent_instance;
guint id;
gchar *lang; /* LangVectorType RFC 5646 */
gchar *contentType;
GstXMLRatio *par;
/* list of Accessibility DescriptorType nodes */
GList *Accessibility;
/* list of Role DescriptorType nodes */
GList *Role;
/* list of Rating DescriptorType nodes */
GList *Rating;
/* list of Viewpoint DescriptorType nodes */
GList *Viewpoint;
};
GstMPDContentComponentNode * gst_mpd_content_component_node_new (void);
void gst_mpd_content_component_node_free (GstMPDContentComponentNode* self);
G_END_DECLS
#endif /* __GSTMPDCONTENTCOMPONENTNODE_H__ */

View file

@ -0,0 +1,99 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpddescriptortypenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDDescriptorTypeNode2, gst_mpd_descriptor_type_node,
GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_descriptor_type_node_finalize (GObject * object)
{
GstMPDDescriptorTypeNode *self = GST_MPD_DESCRIPTOR_TYPE_NODE (object);
if (self->schemeIdUri)
xmlFree (self->schemeIdUri);
if (self->value)
xmlFree (self->value);
g_free (self->node_name);
G_OBJECT_CLASS (gst_mpd_descriptor_type_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_descriptor_type_get_xml_node (GstMPDNode * node)
{
xmlNodePtr descriptor_type_xml_node = NULL;
GstMPDDescriptorTypeNode *self = GST_MPD_DESCRIPTOR_TYPE_NODE (node);
descriptor_type_xml_node = xmlNewNode (NULL, (xmlChar *) self->node_name);
gst_xml_helper_set_prop_string (descriptor_type_xml_node, "schemeIdUri",
self->schemeIdUri);
gst_xml_helper_set_prop_string (descriptor_type_xml_node, "value",
self->value);
return descriptor_type_xml_node;
}
static void
gst_mpd_descriptor_type_node_class_init (GstMPDDescriptorTypeNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_descriptor_type_node_finalize;
m_klass->get_xml_node = gst_mpd_descriptor_type_get_xml_node;
}
static void
gst_mpd_descriptor_type_node_init (GstMPDDescriptorTypeNode * self)
{
if (self->schemeIdUri)
xmlFree (self->schemeIdUri);
if (self->value)
xmlFree (self->value);
}
GstMPDDescriptorTypeNode *
gst_mpd_descriptor_type_node_new (const gchar * name)
{
GstMPDDescriptorTypeNode *self =
g_object_new (GST_TYPE_MPD_DESCRIPTOR_TYPE_NODE, NULL);
self->node_name = g_strdup (name);
return self;
}
void
gst_mpd_descriptor_type_node_free (GstMPDDescriptorTypeNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,48 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDDESCRIPTORTYPENODE_H__
#define __GSTMPDDESCRIPTORTYPENODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_DESCRIPTOR_TYPE_NODE gst_mpd_descriptor_type_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDDescriptorTypeNode2, gst_mpd_descriptor_type_node, GST, MPD_DESCRIPTOR_TYPE_NODE, GstMPDNode)
typedef GstMPDDescriptorTypeNode2 GstMPDDescriptorTypeNode;
typedef GstMPDDescriptorTypeNode2Class GstMPDDescriptorTypeNodeClass;
struct _GstMPDDescriptorTypeNode2
{
GstObject parent_instance;
gchar *node_name;
gchar *schemeIdUri;
gchar *value;
};
GstMPDDescriptorTypeNode * gst_mpd_descriptor_type_node_new (const gchar* name);
void gst_mpd_descriptor_type_node_free (GstMPDDescriptorTypeNode* self);
G_END_DECLS
#endif /* __GSTMPDDESCRIPTORTYPENODE_H__ */

View file

@ -0,0 +1,214 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdhelper.h"
#include "gstmpdbaseurlnode.h"
#include <gst/pbutils/pbutils.h>
#define GST_CAT_DEFAULT gst_dash_demux2_debug
gboolean
gst_mpd_helper_get_mpd_type (xmlNode * a_node,
const gchar * property_name, GstMPDFileType * property_value)
{
xmlChar *prop_string;
gboolean exists = FALSE;
*property_value = GST_MPD_FILE_TYPE_STATIC; /* default */
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
if (prop_string) {
if (xmlStrcmp (prop_string, (xmlChar *) "OnDemand") == 0
|| xmlStrcmp (prop_string, (xmlChar *) "static") == 0) {
exists = TRUE;
*property_value = GST_MPD_FILE_TYPE_STATIC;
GST_LOG (" - %s: static", property_name);
} else if (xmlStrcmp (prop_string, (xmlChar *) "Live") == 0
|| xmlStrcmp (prop_string, (xmlChar *) "dynamic") == 0) {
exists = TRUE;
*property_value = GST_MPD_FILE_TYPE_DYNAMIC;
GST_LOG (" - %s: dynamic", property_name);
} else {
GST_WARNING ("failed to parse MPD type property %s from xml string %s",
property_name, prop_string);
}
xmlFree (prop_string);
}
return exists;
}
gboolean
gst_mpd_helper_get_SAP_type (xmlNode * a_node,
const gchar * property_name, GstMPDSAPType * property_value)
{
xmlChar *prop_string;
guint prop_SAP_type = 0;
gboolean exists = FALSE;
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
if (prop_string) {
if (sscanf ((gchar *) prop_string, "%u", &prop_SAP_type) == 1
&& prop_SAP_type <= 6) {
exists = TRUE;
*property_value = (GstMPDSAPType) prop_SAP_type;
GST_LOG (" - %s: %u", property_name, prop_SAP_type);
} else {
GST_WARNING
("failed to parse unsigned integer property %s from xml string %s",
property_name, prop_string);
}
xmlFree (prop_string);
}
return exists;
}
const gchar *
gst_mpd_helper_get_audio_codec_from_mime (GstCaps * caps)
{
GstStructure *s;
const gchar *name = "";
const gchar *codec_name = NULL;
if (!caps)
return NULL;
s = gst_caps_get_structure (caps, 0);
if (!s)
goto done;
name = gst_structure_get_name (s);
if (!g_strcmp0 (name, "audio/mpeg")) {
gint mpeg_version;
if (gst_structure_get_int (s, "mpegversion", &mpeg_version)) {
if (mpeg_version == 4)
return "mp4a";
}
} else {
GST_DEBUG ("No codecs for this caps name %s", name);
}
done:
return codec_name;
}
const gchar *
gst_mpd_helper_get_video_codec_from_mime (GstCaps * caps)
{
GstStructure *s;
const gchar *name = "";
const gchar *codec_name = NULL;
if (!caps)
return NULL;
s = gst_caps_get_structure (caps, 0);
if (!s)
goto done;
name = gst_structure_get_name (s);
if (!g_strcmp0 (name, "video/x-h264")) {
return "avc1";
} else if (!g_strcmp0 (name, "video/x-h265")) {
return "hvc1";
} else {
GST_DEBUG ("No codecs for this caps name %s", name);
}
done:
return codec_name;
}
const gchar *
gst_mpd_helper_mimetype_to_caps (const gchar * mimeType)
{
if (mimeType == NULL)
return NULL;
if (strcmp (mimeType, "video/mp2t") == 0) {
return "video/mpegts, systemstream=(bool) true";
} else if (strcmp (mimeType, "video/mp4") == 0) {
return "video/quicktime";
} else if (strcmp (mimeType, "audio/mp4") == 0) {
return "audio/x-m4a";
} else if (strcmp (mimeType, "text/vtt") == 0) {
return "application/x-subtitle-vtt";
} else
return mimeType;
}
/* Some top-level mime types directly tell us which
* codec is inside */
GstCaps *
gst_mpd_helper_mimetype_to_codec_caps (const gchar * mimeType)
{
if (mimeType == NULL)
return NULL;
if (strcmp (mimeType, "text/vtt") == 0)
return gst_caps_new_empty_simple ("application/x-subtitle-vtt");
return NULL;
}
/*
* Combine a base url with the current stream base url from the list of
* baseURLs. Takes ownership of base and returns a new base.
*/
GstUri *
gst_mpd_helper_combine_urls (GstUri * base, GList * list, gchar ** query,
guint idx)
{
GstMPDBaseURLNode *baseURL;
GstUri *ret = base;
if (list != NULL) {
baseURL = g_list_nth_data (list, idx);
if (!baseURL) {
baseURL = list->data;
}
ret = gst_uri_from_string_with_base (base, baseURL->baseURL);
gst_uri_unref (base);
if (ret && query) {
g_free (*query);
*query = gst_uri_get_query_string (ret);
if (*query) {
ret = gst_uri_make_writable (ret);
gst_uri_set_query_table (ret, NULL);
}
}
}
return ret;
}
/* comparison functions */
int
gst_mpd_helper_strncmp_ext (const char *s1, const char *s2)
{
if (s1 == NULL && s2 == NULL)
return 0;
if (s1 == NULL && s2 != NULL)
return 1;
if (s2 == NULL && s1 != NULL)
return 1;
return strncmp (s1, s2, strlen (s2));
}

View file

@ -0,0 +1,71 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MPDHELPER_H__
#define __GST_MPDHELPER_H__
#include "gstxmlhelper.h"
#include "gstmpdnode.h"
#include "gstmpdurltypenode.h"
#include "gstmpddescriptortypenode.h"
#include "gstmpdsegmenttimelinenode.h"
#include "gstmpdsegmentbasenode.h"
G_BEGIN_DECLS
typedef enum
{
GST_SAP_TYPE_0 = 0,
GST_SAP_TYPE_1,
GST_SAP_TYPE_2,
GST_SAP_TYPE_3,
GST_SAP_TYPE_4,
GST_SAP_TYPE_5,
GST_SAP_TYPE_6
} GstMPDSAPType;
typedef enum
{
GST_MPD_FILE_TYPE_STATIC = 0,
GST_MPD_FILE_TYPE_DYNAMIC
} GstMPDFileType;
#define GST_MPD_XLINK_ACTUATE_ON_LOAD_STR "onLoad"
typedef enum
{
GST_MPD_XLINK_ACTUATE_ON_REQUEST,
GST_MPD_XLINK_ACTUATE_ON_LOAD
} GstMPDXLinkActuate;
gboolean gst_mpd_helper_get_mpd_type (xmlNode * a_node, const gchar * property_name, GstMPDFileType * property_value);
gboolean gst_mpd_helper_get_SAP_type (xmlNode * a_node, const gchar * property_name, GstMPDSAPType * property_value);
const gchar * gst_mpd_helper_mimetype_to_caps (const gchar * mimeType);
GstCaps *gst_mpd_helper_mimetype_to_codec_caps (const gchar * mimeType);
const gchar * gst_mpd_helper_get_video_codec_from_mime (GstCaps * caps);
const gchar * gst_mpd_helper_get_audio_codec_from_mime (GstCaps * caps);
GstUri *gst_mpd_helper_combine_urls (GstUri * base, GList * list, gchar ** query, guint idx);
int gst_mpd_helper_strncmp_ext (const char *s1, const char *s2);
G_END_DECLS
#endif /* __GST_MPDHELPER_H__ */

View file

@ -0,0 +1,84 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdlocationnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDLocationNode2, gst_mpd_location_node, GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_location_node_finalize (GObject * object)
{
GstMPDLocationNode *self = GST_MPD_LOCATION_NODE (object);
g_free (self->location);
G_OBJECT_CLASS (gst_mpd_location_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_location_get_xml_node (GstMPDNode * node)
{
xmlNodePtr location_xml_node = NULL;
GstMPDLocationNode *self = GST_MPD_LOCATION_NODE (node);
location_xml_node = xmlNewNode (NULL, (xmlChar *) "Location");
if (self->location)
gst_xml_helper_set_content (location_xml_node, self->location);
return location_xml_node;
}
static void
gst_mpd_location_node_class_init (GstMPDLocationNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_mpd_location_node_finalize;
m_klass = GST_MPD_NODE_CLASS (klass);
m_klass->get_xml_node = gst_mpd_location_get_xml_node;
}
static void
gst_mpd_location_node_init (GstMPDLocationNode * self)
{
self->location = NULL;
}
GstMPDLocationNode *
gst_mpd_location_node_new (void)
{
return g_object_new (GST_TYPE_MPD_LOCATION_NODE, NULL);
}
void
gst_mpd_location_node_free (GstMPDLocationNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,46 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDLOCATIONNODE_H__
#define __GSTMPDLOCATIONNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_LOCATION_NODE gst_mpd_location_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDLocationNode2, gst_mpd_location_node, GST, MPD_LOCATION_NODE, GstMPDNode)
typedef GstMPDLocationNode2 GstMPDLocationNode;
typedef GstMPDLocationNode2Class GstMPDLocationNodeClass;
struct _GstMPDLocationNode2
{
GstObject parent_instance;
gchar *location;
};
GstMPDLocationNode * gst_mpd_location_node_new (void);
void gst_mpd_location_node_free (GstMPDLocationNode* self);
G_END_DECLS
#endif /* __GSTMPDLOCATIONNODE_H__ */

View file

@ -0,0 +1,94 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdmetricsnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDMetricsNode2, gst_mpd_metrics_node, GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_metrics_node_finalize (GObject * object)
{
GstMPDMetricsNode *self = GST_MPD_METRICS_NODE (object);
g_free (self->metrics);
g_list_free_full (self->MetricsRanges,
(GDestroyNotify) gst_mpd_metrics_range_node_free);
G_OBJECT_CLASS (gst_mpd_metrics_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_metrics_get_xml_node (GstMPDNode * node)
{
xmlNodePtr metrics_xml_node = NULL;
GstMPDMetricsNode *self = GST_MPD_METRICS_NODE (node);
metrics_xml_node = xmlNewNode (NULL, (xmlChar *) "Metrics");
if (self->metrics)
gst_xml_helper_set_prop_string (metrics_xml_node, "metrics", self->metrics);
g_list_foreach (self->Reportings, gst_mpd_node_get_list_item,
metrics_xml_node);
g_list_foreach (self->MetricsRanges, gst_mpd_node_get_list_item,
metrics_xml_node);
return metrics_xml_node;
}
static void
gst_mpd_metrics_node_class_init (GstMPDMetricsNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_metrics_node_finalize;
m_klass->get_xml_node = gst_mpd_metrics_get_xml_node;
}
static void
gst_mpd_metrics_node_init (GstMPDMetricsNode * self)
{
self->metrics = NULL;
self->MetricsRanges = NULL;
self->Reportings = NULL;
}
GstMPDMetricsNode *
gst_mpd_metrics_node_new (void)
{
return g_object_new (GST_TYPE_MPD_METRICS_NODE, NULL);
}
void
gst_mpd_metrics_node_free (GstMPDMetricsNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,50 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDMETRICSNODE_H__
#define __GSTMPDMETRICSNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_METRICS_NODE gst_mpd_metrics_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDMetricsNode2, gst_mpd_metrics_node, GST, MPD_METRICS_NODE, GstMPDNode)
typedef GstMPDMetricsNode2 GstMPDMetricsNode;
typedef GstMPDMetricsNode2Class GstMPDMetricsNodeClass;
struct _GstMPDMetricsNode2
{
GstObject parent_instance;
gchar *metrics;
/* list of Metrics Range nodes */
GList *MetricsRanges;
/* list of Reporting nodes */
GList *Reportings;
};
GstMPDMetricsNode * gst_mpd_metrics_node_new (void);
void gst_mpd_metrics_node_free (GstMPDMetricsNode* self);
G_END_DECLS
#endif /* __GSTMPDMETRICSNODE_H__ */

View file

@ -0,0 +1,75 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdmetricsrangenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDMetricsRangeNode2, gst_mpd_metrics_range_node,
GST_TYPE_MPD_NODE);
/* Base class */
static xmlNodePtr
gst_mpd_metrics_range_get_xml_node (GstMPDNode * node)
{
xmlNodePtr metrics_range_xml_node = NULL;
GstMPDMetricsRangeNode *self = GST_MPD_METRICS_RANGE_NODE (node);
metrics_range_xml_node = xmlNewNode (NULL, (xmlChar *) "Range");
if (self->starttime)
gst_xml_helper_set_prop_duration (metrics_range_xml_node, "starttime",
self->starttime);
if (self->duration)
gst_xml_helper_set_prop_duration (metrics_range_xml_node, "duration",
self->duration);
return metrics_range_xml_node;
}
static void
gst_mpd_metrics_range_node_class_init (GstMPDMetricsRangeNodeClass * klass)
{
GstMPDNodeClass *m_klass;
m_klass = GST_MPD_NODE_CLASS (klass);
m_klass->get_xml_node = gst_mpd_metrics_range_get_xml_node;
}
static void
gst_mpd_metrics_range_node_init (GstMPDMetricsRangeNode * self)
{
self->starttime = 0; /* [ms] */
self->duration = 0; /* [ms] */
}
GstMPDMetricsRangeNode *
gst_mpd_metrics_range_node_new (void)
{
return g_object_new (GST_TYPE_MPD_METRICS_RANGE_NODE, NULL);
}
void
gst_mpd_metrics_range_node_free (GstMPDMetricsRangeNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,47 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDMETRICSRANGENODE_H__
#define __GSTMPDMETRICSRANGENODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_METRICS_RANGE_NODE gst_mpd_metrics_range_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDMetricsRangeNode2, gst_mpd_metrics_range_node, GST, MPD_METRICS_RANGE_NODE, GstMPDNode)
typedef GstMPDMetricsRangeNode2 GstMPDMetricsRangeNode;
typedef GstMPDMetricsRangeNode2Class GstMPDMetricsRangeNodeClass;
struct _GstMPDMetricsRangeNode2
{
GstObject parent_instance;
guint64 starttime; /* [ms] */
guint64 duration; /* [ms] */
};
GstMPDMetricsRangeNode * gst_mpd_metrics_range_node_new (void);
void gst_mpd_metrics_range_node_free (GstMPDMetricsRangeNode* self);
G_END_DECLS
#endif /* __GSTMPDMETRICSRANGENODE_H__ */

View file

@ -0,0 +1,153 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdmultsegmentbasenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDMultSegmentBaseNode2, gst_mpd_mult_segment_base_node,
GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_MULT_SEGMENT_BASE_0 = 100,
PROP_MPD_MULT_SEGMENT_BASE_DURATION,
PROP_MPD_MULT_SEGMENT_BASE_START_NUMBER,
};
/* GObject VMethods */
static void
gst_mpd_mult_segment_base_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDMultSegmentBaseNode *self = GST_MPD_MULT_SEGMENT_BASE_NODE (object);
switch (prop_id) {
case PROP_MPD_MULT_SEGMENT_BASE_DURATION:
self->duration = g_value_get_uint (value);
break;
case PROP_MPD_MULT_SEGMENT_BASE_START_NUMBER:
self->startNumber = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_mult_segment_base_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDMultSegmentBaseNode *self = GST_MPD_MULT_SEGMENT_BASE_NODE (object);
switch (prop_id) {
case PROP_MPD_MULT_SEGMENT_BASE_DURATION:
g_value_set_uint (value, self->duration);
break;
case PROP_MPD_MULT_SEGMENT_BASE_START_NUMBER:
g_value_set_uint (value, self->startNumber);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_mult_segment_base_node_finalize (GObject * object)
{
GstMPDMultSegmentBaseNode *self = GST_MPD_MULT_SEGMENT_BASE_NODE (object);
gst_mpd_segment_base_node_free (self->SegmentBase);
gst_mpd_segment_timeline_node_free (self->SegmentTimeline);
gst_mpd_url_type_node_free (self->BitstreamSwitching);
G_OBJECT_CLASS (gst_mpd_mult_segment_base_node_parent_class)->finalize
(object);
}
/* Base class */
static void
gst_mpd_mult_segment_base_get_xml_node (GstMPDNode * node,
xmlNodePtr mult_segment_base_node)
{
GstMPDMultSegmentBaseNode *self = GST_MPD_MULT_SEGMENT_BASE_NODE (node);
if (self->duration)
gst_xml_helper_set_prop_uint (mult_segment_base_node, "duration",
self->duration);
if (self->startNumber)
gst_xml_helper_set_prop_uint (mult_segment_base_node, "startNumber",
self->startNumber);
if (self->SegmentBase)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->SegmentBase),
mult_segment_base_node);
if (self->SegmentTimeline)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->SegmentTimeline),
mult_segment_base_node);
if (self->BitstreamSwitching)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->BitstreamSwitching),
mult_segment_base_node);
}
static void
gst_mpd_mult_segment_base_node_class_init (GstMPDMultSegmentBaseNodeClass *
klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_mpd_mult_segment_base_node_finalize;
object_class->set_property = gst_mpd_mult_segment_base_node_set_property;
object_class->get_property = gst_mpd_mult_segment_base_node_get_property;
g_object_class_install_property (object_class,
PROP_MPD_MULT_SEGMENT_BASE_DURATION, g_param_spec_uint ("duration",
"duration", "duration of segment", 0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_MULT_SEGMENT_BASE_START_NUMBER,
g_param_spec_uint ("start-number", "start number",
"start number in the segment list", 0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_mult_segment_base_node_init (GstMPDMultSegmentBaseNode * self)
{
self->duration = 0;
self->startNumber = 0;
self->SegmentBase = NULL;
self->SegmentTimeline = NULL;
self->BitstreamSwitching = NULL;
}
void
gst_mpd_mult_segment_base_node_add_child_node (GstMPDNode * node,
xmlNodePtr parent_xml_node)
{
if (node) {
xmlNodePtr new_xml_node = gst_mpd_node_get_xml_pointer (node);
gst_mpd_mult_segment_base_get_xml_node (node, new_xml_node);
xmlAddChild (parent_xml_node, new_xml_node);
}
}

View file

@ -0,0 +1,51 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDMULTSEGMENTBASENODE_H__
#define __GSTMPDMULTSEGMENTBASENODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_MULT_SEGMENT_BASE_NODE gst_mpd_mult_segment_base_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDMultSegmentBaseNode2, gst_mpd_mult_segment_base_node, GST, MPD_MULT_SEGMENT_BASE_NODE, GstMPDNode)
typedef GstMPDMultSegmentBaseNode2 GstMPDMultSegmentBaseNode;
typedef GstMPDMultSegmentBaseNode2Class GstMPDMultSegmentBaseNodeClass;
struct _GstMPDMultSegmentBaseNode2
{
GstObject base;
guint duration; /* in seconds */
guint startNumber;
/* SegmentBaseType extension */
GstMPDSegmentBaseNode *SegmentBase;
/* SegmentTimeline node */
GstMPDSegmentTimelineNode *SegmentTimeline;
/* BitstreamSwitching node */
GstMPDURLTypeNode *BitstreamSwitching;
};
void gst_mpd_mult_segment_base_node_add_child_node (GstMPDNode* node, xmlNodePtr parent_xml_node);
G_END_DECLS
#endif /* __GSTMPDMULTSEGMENTBASENODE_H__ */

View file

@ -0,0 +1,78 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdnode.h"
G_DEFINE_TYPE (GstMPDNode2, gst_mpd_node, GST_TYPE_OBJECT);
/* GObject VMethods */
static void
gst_mpd_node_class_init (GstMPDNode2Class * klass)
{
}
static void
gst_mpd_node_init (GstMPDNode2 * self)
{
}
void
gst_mpd_node_get_list_item (gpointer data, gpointer user_data)
{
GstMPDNode *node = (GstMPDNode *) data;
xmlNodePtr parent_xml_node = (xmlNodePtr) user_data;
xmlNodePtr new_xml_node = gst_mpd_node_get_xml_pointer (node);
xmlAddChild (parent_xml_node, new_xml_node);
}
void
gst_mpd_node_add_child_node (GstMPDNode * child, xmlNodePtr parent)
{
xmlNodePtr new_xml_node = gst_mpd_node_get_xml_pointer (child);
xmlAddChild (parent, new_xml_node);
}
gboolean
gst_mpd_node_get_xml_buffer (GstMPDNode * node, gchar ** xml_content,
int *xml_size)
{
GstMPDNode2Class *klass;
klass = GST_MPD_NODE_GET_CLASS (node);
if (klass->get_xml_buffer)
return klass->get_xml_buffer (node, xml_content, xml_size);
else
return FALSE;
}
xmlNodePtr
gst_mpd_node_get_xml_pointer (GstMPDNode * node)
{
GstMPDNode2Class *klass;
if (!node)
return NULL;
klass = GST_MPD_NODE_GET_CLASS (node);
if (klass->get_xml_node)
return klass->get_xml_node (node);
else
return NULL;
}

View file

@ -0,0 +1,53 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDNODE_H__
#define __GSTMPDNODE_H__
#include <gst/gst.h>
#include "gstxmlhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_NODE gst_mpd_node_get_type ()
G_DECLARE_DERIVABLE_TYPE (GstMPDNode2, gst_mpd_node,GST, MPD_NODE, GstObject)
typedef GstMPDNode2 GstMPDNode;
typedef GstMPDNode2Class GstMPDNodeClass;
#define glib_autoptr_clear_GstMPDNode glib_autoptr_clear_GstMPDNode2
typedef gboolean (*GstMPDGetXMLBuffer) (GstMPDNode * n, gchar ** doc_content, int *doc_size);
typedef xmlNodePtr (*GstMPDGetXMLNode) (GstMPDNode * n);
struct _GstMPDNode2Class {
GstObjectClass base;
GstMPDGetXMLBuffer get_xml_buffer;
GstMPDGetXMLNode get_xml_node;
};
gboolean gst_mpd_node_get_xml_buffer (GstMPDNode * node, gchar ** xml_content, int * xml_size);
xmlNodePtr gst_mpd_node_get_xml_pointer (GstMPDNode * node);
void gst_mpd_node_get_list_item (gpointer data, gpointer user_data);
void gst_mpd_node_add_child_node (GstMPDNode* data, xmlNodePtr user_data);
G_END_DECLS
#endif /* __GSTMPDNODE_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,173 @@
/*
* DASH MPD parsing library
*
* gstmpdparser.h
*
* Copyright (C) 2012 STMicroelectronics
*
* Authors:
* Gianluca Gennari <gennarone@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MPDPARSER_H__
#define __GST_MPDPARSER_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include "gstadaptivedemux.h"
#include "gstmpdhelper.h"
#include "gstxmlhelper.h"
#include "gstmpdrootnode.h"
#include "gstmpdbaseurlnode.h"
#include "gstmpdutctimingnode.h"
#include "gstmpdmetricsnode.h"
#include "gstmpdmetricsrangenode.h"
#include "gstmpdsnode.h"
#include "gstmpdsegmenttimelinenode.h"
#include "gstmpdsegmenttemplatenode.h"
#include "gstmpdsegmenturlnode.h"
#include "gstmpdsegmentlistnode.h"
#include "gstmpdsegmentbasenode.h"
#include "gstmpdperiodnode.h"
#include "gstmpdrepresentationnode.h"
#include "gstmpdsubrepresentationnode.h"
#include "gstmpdcontentcomponentnode.h"
#include "gstmpdadaptationsetnode.h"
#include "gstmpdsubsetnode.h"
#include "gstmpdprograminformationnode.h"
#include "gstmpdlocationnode.h"
#include "gstmpdreportingnode.h"
#include "gstmpdurltypenode.h"
#include "gstmpddescriptortypenode.h"
#include "gstmpdrepresentationbasenode.h"
#include "gstmpdmultsegmentbasenode.h"
G_BEGIN_DECLS
typedef struct _GstActiveStream GstActiveStream;
typedef struct _GstStreamPeriod GstStreamPeriod;
typedef struct _GstMediaFragmentInfo GstMediaFragmentInfo;
typedef struct _GstMediaSegment GstMediaSegment;
#define GST_MPD_DURATION_NONE ((guint64)-1)
typedef enum
{
GST_STREAM_UNKNOWN,
GST_STREAM_VIDEO, /* video stream (the main one) */
GST_STREAM_AUDIO, /* audio stream (optional) */
GST_STREAM_APPLICATION /* application stream (optional): for timed text/subtitles */
} GstStreamMimeType;
/**
* GstStreamPeriod:
*
* Stream period data structure
*/
struct _GstStreamPeriod
{
GstMPDPeriodNode *period; /* Stream period */
guint number; /* Period number */
GstClockTime start; /* Period start time */
GstClockTime duration; /* Period duration */
};
/**
* GstMediaSegment:
*
* Media segment data structure
*/
struct _GstMediaSegment
{
GstMPDSegmentURLNode *SegmentURL; /* this is NULL when using a SegmentTemplate */
guint number; /* segment number */
gint repeat; /* number of extra repetitions (0 = played only once) */
guint64 scale_start; /* start time in timescale units */
guint64 scale_duration; /* duration in timescale units */
GstClockTime start; /* segment start time */
GstClockTime duration; /* segment duration */
};
struct _GstMediaFragmentInfo
{
gchar *uri;
gint64 range_start;
gint64 range_end;
gchar *index_uri;
gint64 index_range_start;
gint64 index_range_end;
gboolean discontinuity;
GstClockTime timestamp;
GstClockTime duration;
};
/**
* GstActiveStream:
*
* Active stream data structure
*/
struct _GstActiveStream
{
GstStreamMimeType mimeType; /* video/audio/application */
guint baseURL_idx; /* index of the baseURL used for last request */
gchar *baseURL; /* active baseURL used for last request */
gchar *queryURL; /* active queryURL used for last request */
guint max_bandwidth; /* max bandwidth allowed for this mimeType */
GstMPDAdaptationSetNode *cur_adapt_set; /* active adaptation set */
gint representation_idx; /* index of current representation */
GstMPDRepresentationNode *cur_representation; /* active representation */
GstMPDSegmentBaseNode *cur_segment_base; /* active segment base */
GstMPDSegmentListNode *cur_segment_list; /* active segment list */
GstMPDSegmentTemplateNode *cur_seg_template; /* active segment template */
gint segment_index; /* index of next sequence chunk */
guint segment_repeat_index; /* index of the repeat count of a segment */
GPtrArray *segments; /* array of GstMediaSegment */
GstClockTime presentationTimeOffset; /* presentation time offset of the current segment */
};
/* MPD file parsing */
gboolean gst_mpdparser_get_mpd_root_node (GstMPDRootNode ** mpd_root_node, const gchar * data, gint size);
GstMPDSegmentListNode * gst_mpdparser_get_external_segment_list (const gchar * data, gint size, GstMPDSegmentListNode * parent);
GList * gst_mpdparser_get_external_periods (const gchar * data, gint size);
GList * gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size, GstMPDPeriodNode* period);
/* navigation functions */
GstStreamMimeType gst_mpdparser_representation_get_mimetype (GstMPDAdaptationSetNode * adapt_set, GstMPDRepresentationNode * rep);
/* Memory management */
void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period);
void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment);
void gst_mpdparser_free_active_stream (GstActiveStream * active_stream);
void gst_mpdparser_media_fragment_info_clear (GstMediaFragmentInfo * fragment);
/* Active stream methods*/
void gst_mpdparser_init_active_stream_segments (GstActiveStream * stream);
gchar *gst_mpdparser_get_mediaURL (GstActiveStream * stream, GstMPDSegmentURLNode * segmentURL);
const gchar *gst_mpdparser_get_initializationURL (GstActiveStream * stream, GstMPDURLTypeNode * InitializationURL);
gchar *gst_mpdparser_build_URL_from_template (const gchar * url_template, const gchar * id, guint number, guint bandwidth, guint64 time);
G_END_DECLS
#endif /* __GST_MPDPARSER_H__ */

View file

@ -0,0 +1,214 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdperiodnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDPeriodNode2, gst_mpd_period_node, GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_PERIOD_0,
PROP_MPD_PERIOD_ID,
PROP_MPD_PERIOD_START,
PROP_MPD_PERIOD_DURATION,
PROP_MPD_PERIOD_BITSTREAM_SWITCHING,
};
/* GObject VMethods */
static void
gst_mpd_period_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDPeriodNode *self = GST_MPD_PERIOD_NODE (object);
switch (prop_id) {
case PROP_MPD_PERIOD_ID:
g_free (self->id);
self->id = g_value_dup_string (value);
break;
case PROP_MPD_PERIOD_START:
self->start = g_value_get_uint64 (value);
break;
case PROP_MPD_PERIOD_DURATION:
self->duration = g_value_get_uint64 (value);
break;
case PROP_MPD_PERIOD_BITSTREAM_SWITCHING:
self->bitstreamSwitching = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_period_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDPeriodNode *self = GST_MPD_PERIOD_NODE (object);
switch (prop_id) {
case PROP_MPD_PERIOD_ID:
g_value_set_string (value, self->id);
break;
case PROP_MPD_PERIOD_START:
g_value_set_uint64 (value, self->start);
break;
case PROP_MPD_PERIOD_DURATION:
g_value_set_uint64 (value, self->duration);
break;
case PROP_MPD_PERIOD_BITSTREAM_SWITCHING:
g_value_set_boolean (value, self->bitstreamSwitching);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_period_node_finalize (GObject * object)
{
GstMPDPeriodNode *self = GST_MPD_PERIOD_NODE (object);
if (self->id)
xmlFree (self->id);
gst_mpd_segment_base_node_free (self->SegmentBase);
gst_mpd_segment_list_node_free (self->SegmentList);
gst_mpd_segment_template_node_free (self->SegmentTemplate);
g_list_free_full (self->AdaptationSets,
(GDestroyNotify) gst_mpd_adaptation_set_node_free);
g_list_free_full (self->Subsets, (GDestroyNotify) gst_mpd_subset_node_free);
g_list_free_full (self->BaseURLs, (GDestroyNotify) gst_mpd_baseurl_node_free);
if (self->xlink_href)
xmlFree (self->xlink_href);
G_OBJECT_CLASS (gst_mpd_period_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_period_get_xml_node (GstMPDNode * node)
{
xmlNodePtr period_xml_node = NULL;
GstMPDPeriodNode *self = GST_MPD_PERIOD_NODE (node);
period_xml_node = xmlNewNode (NULL, (xmlChar *) "Period");
if (self->id)
gst_xml_helper_set_prop_string (period_xml_node, "id", self->id);
gst_xml_helper_set_prop_duration (period_xml_node, "start", self->start);
gst_xml_helper_set_prop_duration (period_xml_node, "duration",
self->duration);
gst_xml_helper_set_prop_boolean (period_xml_node, "bitstreamSwitching",
self->bitstreamSwitching);
if (self->SegmentBase)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->SegmentBase),
period_xml_node);
if (self->SegmentList)
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentList), period_xml_node);
if (self->SegmentTemplate)
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentTemplate), period_xml_node);
g_list_foreach (self->AdaptationSets,
gst_mpd_representation_base_node_get_list_item, period_xml_node);
g_list_foreach (self->Subsets, gst_mpd_node_get_list_item, period_xml_node);
g_list_foreach (self->BaseURLs, gst_mpd_node_get_list_item, period_xml_node);
return period_xml_node;
}
static void
gst_mpd_period_node_class_init (GstMPDPeriodNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_period_node_finalize;
object_class->set_property = gst_mpd_period_node_set_property;
object_class->get_property = gst_mpd_period_node_get_property;
m_klass->get_xml_node = gst_mpd_period_get_xml_node;
g_object_class_install_property (object_class, PROP_MPD_PERIOD_ID,
g_param_spec_string ("id", "id",
"unique id for period", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_PERIOD_START,
g_param_spec_uint64 ("start", "Period start",
"Period start",
0, G_MAXUINT64, 0,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_MPD_PERIOD_DURATION,
g_param_spec_uint64 ("duration", "period duration",
"Period duration",
0, G_MAXUINT64, 0,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class,
PROP_MPD_PERIOD_BITSTREAM_SWITCHING,
g_param_spec_boolean ("bitstream-switching", "Bitstream switching",
"Bitstream switching", FALSE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
}
static void
gst_mpd_period_node_init (GstMPDPeriodNode * self)
{
self->id = NULL;
self->start = 0; /* [ms] */
self->duration = 0; /* [ms] */
self->bitstreamSwitching = 0;
self->SegmentBase = NULL;
self->SegmentList = NULL;
self->SegmentTemplate = NULL;
self->AdaptationSets = NULL;
self->Subsets = NULL;
self->BaseURLs = NULL;
self->xlink_href = NULL;
self->actuate = 0;
}
GstMPDPeriodNode *
gst_mpd_period_node_new (void)
{
return g_object_new (GST_TYPE_MPD_PERIOD_NODE, NULL);
}
void
gst_mpd_period_node_free (GstMPDPeriodNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,68 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDPERIODNODE_H__
#define __GSTMPDPERIODNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdsegmentlistnode.h"
#include "gstmpdsegmenttemplatenode.h"
G_BEGIN_DECLS
struct _GstSegmentTemplateNode;
#define GST_TYPE_MPD_PERIOD_NODE gst_mpd_period_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDPeriodNode2, gst_mpd_period_node, GST, MPD_PERIOD_NODE, GstMPDNode)
typedef GstMPDPeriodNode2 GstMPDPeriodNode;
typedef GstMPDPeriodNode2Class GstMPDPeriodNodeClass;
struct _GstMPDPeriodNode2
{
GstObject parent_instance;
gchar *id;
guint64 start; /* [ms] */
guint64 duration; /* [ms] */
gboolean bitstreamSwitching;
/* SegmentBase node */
GstMPDSegmentBaseNode *SegmentBase;
/* SegmentList node */
GstMPDSegmentListNode *SegmentList;
/* SegmentTemplate node */
GstMPDSegmentTemplateNode *SegmentTemplate;
/* list of Adaptation Set nodes */
GList *AdaptationSets;
/* list of Representation nodes */
GList *Subsets;
/* list of BaseURL nodes */
GList *BaseURLs;
gchar *xlink_href;
int actuate;
};
GstMPDPeriodNode * gst_mpd_period_node_new (void);
void gst_mpd_period_node_free (GstMPDPeriodNode* self);
G_END_DECLS
#endif /* __GSTMPDPERIODNODE_H__ */

View file

@ -0,0 +1,125 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdprograminformationnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDProgramInformationNode2, gst_mpd_program_information_node,
GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_program_information_node_finalize (GObject * object)
{
GstMPDProgramInformationNode *self =
GST_MPD_PROGRAM_INFORMATION_NODE (object);
if (self->lang)
xmlFree (self->lang);
if (self->moreInformationURL)
xmlFree (self->moreInformationURL);
if (self->Title)
xmlFree (self->Title);
if (self->Source)
xmlFree (self->Source);
if (self->Copyright)
xmlFree (self->Copyright);
G_OBJECT_CLASS (gst_mpd_program_information_node_parent_class)->finalize
(object);
}
/* Base class */
static xmlNodePtr
gst_mpd_program_information_get_xml_node (GstMPDNode * node)
{
xmlNodePtr program_info_xml_node = NULL;
xmlNodePtr child_node = NULL;
GstMPDProgramInformationNode *self = GST_MPD_PROGRAM_INFORMATION_NODE (node);
program_info_xml_node = xmlNewNode (NULL, (xmlChar *) "ProgramInformation");
if (self->lang)
gst_xml_helper_set_prop_string (program_info_xml_node, "lang", self->lang);
if (self->moreInformationURL)
gst_xml_helper_set_prop_string (program_info_xml_node, "moreInformationURL",
self->moreInformationURL);
if (self->Title) {
child_node = xmlNewNode (NULL, (xmlChar *) "Title");
gst_xml_helper_set_content (child_node, self->Title);
xmlAddChild (program_info_xml_node, child_node);
}
if (self->Source) {
child_node = xmlNewNode (NULL, (xmlChar *) "Source");
gst_xml_helper_set_content (child_node, self->Source);
xmlAddChild (program_info_xml_node, child_node);
}
if (self->Copyright) {
child_node = xmlNewNode (NULL, (xmlChar *) "Copyright");
gst_xml_helper_set_content (child_node, self->Copyright);
xmlAddChild (program_info_xml_node, child_node);
}
return program_info_xml_node;
}
static void
gst_mpd_program_information_node_class_init (GstMPDProgramInformationNodeClass *
klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_program_information_node_finalize;
m_klass->get_xml_node = gst_mpd_program_information_get_xml_node;
}
static void
gst_mpd_program_information_node_init (GstMPDProgramInformationNode * self)
{
self->lang = NULL;
self->moreInformationURL = NULL;
self->Title = NULL;
self->Source = NULL;
self->Copyright = NULL;
}
GstMPDProgramInformationNode *
gst_mpd_program_information_node_new (void)
{
return g_object_new (GST_TYPE_MPD_PROGRAM_INFORMATION_NODE, NULL);
}
void
gst_mpd_program_information_node_free (GstMPDProgramInformationNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,51 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDPROGRAMINFORMATIONNODE_H__
#define __GSTMPDPROGRAMINFORMATIONNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_PROGRAM_INFORMATION_NODE gst_mpd_program_information_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDProgramInformationNode2, gst_mpd_program_information_node, GST, MPD_PROGRAM_INFORMATION_NODE, GstMPDNode)
typedef GstMPDProgramInformationNode2 GstMPDProgramInformationNode;
typedef GstMPDProgramInformationNode2Class GstMPDProgramInformationNodeClass;
struct _GstMPDProgramInformationNode2
{
GstObject parent_instance;
gchar *lang; /* LangVectorType RFC 5646 */
gchar *moreInformationURL;
/* children nodes */
gchar *Title;
gchar *Source;
gchar *Copyright;
};
GstMPDProgramInformationNode * gst_mpd_program_information_node_new (void);
void gst_mpd_program_information_node_free (GstMPDProgramInformationNode* self);
G_END_DECLS
#endif /* __GSTMPDPROGRAMINFORMATIONNODE_H__ */

View file

@ -0,0 +1,64 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdreportingnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDReportingNode2, gst_mpd_reporting_node, GST_TYPE_MPD_NODE);
/* Base class */
static xmlNodePtr
gst_mpd_reporting_get_xml_node (GstMPDNode * node)
{
xmlNodePtr reporting_xml_node = NULL;
reporting_xml_node = xmlNewNode (NULL, (xmlChar *) "Reporting");
return reporting_xml_node;
}
static void
gst_mpd_reporting_node_class_init (GstMPDReportingNodeClass * klass)
{
GstMPDNodeClass *m_klass;
m_klass = GST_MPD_NODE_CLASS (klass);
m_klass->get_xml_node = gst_mpd_reporting_get_xml_node;
}
static void
gst_mpd_reporting_node_init (GstMPDReportingNode * self)
{
}
GstMPDReportingNode *
gst_mpd_reporting_node_new (void)
{
return g_object_new (GST_TYPE_MPD_REPORTING_NODE, NULL);
}
void
gst_mpd_reporting_node_free (GstMPDReportingNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,45 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDREPORTINGNODE_H__
#define __GSTMPDREPORTINGNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_REPORTING_NODE gst_mpd_reporting_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDReportingNode2, gst_mpd_reporting_node, GST, MPD_REPORTING_NODE, GstMPDNode)
typedef GstMPDReportingNode2 GstMPDReportingNode;
typedef GstMPDReportingNode2Class GstMPDReportingNodeClass;
struct _GstMPDReportingNode2
{
GstObject parent_instance;
};
GstMPDReportingNode * gst_mpd_reporting_node_new (void);
void gst_mpd_reporting_node_free (GstMPDReportingNode* self);
G_END_DECLS
#endif /* __GSTMPDREPORTINGNODE_H__ */

View file

@ -0,0 +1,351 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdrepresentationbasenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDRepresentationBaseNode2, gst_mpd_representation_base_node,
GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_REPRESENTATION_BASE_0 = 100,
PROP_MPD_REPRESENTATION_BASE_PROFILES,
PROP_MPD_REPRESENTATION_BASE_WIDTH,
PROP_MPD_REPRESENTATION_BASE_HEIGHT,
PROP_MPD_REPRESENTATION_BASE_SAR,
PROP_MPD_REPRESENTATION_BASE_MIN_FRAME_RATE,
PROP_MPD_REPRESENTATION_BASE_MAX_FRAME_RATE,
PROP_MPD_REPRESENTATION_BASE_FRAME_RATE,
PROP_MPD_REPRESENTATION_BASE_AUDIO_SAMPLING_RATE,
PROP_MPD_REPRESENTATION_BASE_MIMETYPE,
PROP_MPD_REPRESENTATION_BASE_SEGMENT_PROFILES,
PROP_MPD_REPRESENTATION_BASE_CODECS,
PROP_MPD_REPRESENTATION_BASE_MAX_SAP_PERIOD,
PROP_MPD_REPRESENTATION_BASE_START_WITH_SAP,
PROP_MPD_REPRESENTATION_BASE_MAX_PLAYOUT_RATE,
PROP_MPD_REPRESENTATION_BASE_CODING_DEPENDENCY,
PROP_MPD_REPRESENTATION_BASE_SCAN_TYPE,
};
/* GObject VMethods */
static void
gst_mpd_representation_base_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDRepresentationBaseNode *self =
GST_MPD_REPRESENTATION_BASE_NODE (object);
switch (prop_id) {
case PROP_MPD_REPRESENTATION_BASE_PROFILES:
g_free (self->profiles);
self->profiles = g_value_dup_string (value);
break;
case PROP_MPD_REPRESENTATION_BASE_WIDTH:
self->width = g_value_get_uint (value);
break;
case PROP_MPD_REPRESENTATION_BASE_HEIGHT:
self->height = g_value_get_uint (value);
break;
case PROP_MPD_REPRESENTATION_BASE_SAR:
g_slice_free (GstXMLRatio, self->sar);
self->sar = gst_xml_helper_clone_ratio (g_value_get_pointer (value));
break;
case PROP_MPD_REPRESENTATION_BASE_MIN_FRAME_RATE:
g_slice_free (GstXMLFrameRate, self->minFrameRate);
self->minFrameRate =
gst_xml_helper_clone_frame_rate (g_value_get_pointer (value));
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_FRAME_RATE:
g_slice_free (GstXMLFrameRate, self->maxFrameRate);
self->maxFrameRate =
gst_xml_helper_clone_frame_rate (g_value_get_pointer (value));
break;
case PROP_MPD_REPRESENTATION_BASE_FRAME_RATE:
g_slice_free (GstXMLFrameRate, self->frameRate);
self->frameRate =
gst_xml_helper_clone_frame_rate (g_value_get_pointer (value));
break;
case PROP_MPD_REPRESENTATION_BASE_AUDIO_SAMPLING_RATE:
g_free (self->audioSamplingRate);
self->audioSamplingRate =
g_strdup_printf ("%u", g_value_get_uint (value));
break;
case PROP_MPD_REPRESENTATION_BASE_MIMETYPE:
g_free (self->mimeType);
self->mimeType = g_value_dup_string (value);
break;
case PROP_MPD_REPRESENTATION_BASE_SEGMENT_PROFILES:
g_free (self->segmentProfiles);
self->segmentProfiles = g_value_dup_string (value);
break;
case PROP_MPD_REPRESENTATION_BASE_CODECS:
g_free (self->codecs);
self->codecs = g_value_dup_string (value);
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_SAP_PERIOD:
self->maximumSAPPeriod = g_value_get_double (value);
break;
case PROP_MPD_REPRESENTATION_BASE_START_WITH_SAP:
self->startWithSAP = g_value_get_int (value);
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_PLAYOUT_RATE:
self->maxPlayoutRate = g_value_get_double (value);
break;
case PROP_MPD_REPRESENTATION_BASE_CODING_DEPENDENCY:
self->codingDependency = g_value_get_boolean (value);
break;
case PROP_MPD_REPRESENTATION_BASE_SCAN_TYPE:
g_free (self->scanType);
self->scanType = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_representation_base_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDRepresentationBaseNode *self =
GST_MPD_REPRESENTATION_BASE_NODE (object);
switch (prop_id) {
case PROP_MPD_REPRESENTATION_BASE_PROFILES:
g_value_set_string (value, self->profiles);
break;
case PROP_MPD_REPRESENTATION_BASE_WIDTH:
g_value_set_uint (value, self->width);
break;
case PROP_MPD_REPRESENTATION_BASE_HEIGHT:
g_value_set_uint (value, self->height);
break;
case PROP_MPD_REPRESENTATION_BASE_SAR:
g_value_set_pointer (value, self->sar);
break;
case PROP_MPD_REPRESENTATION_BASE_MIN_FRAME_RATE:
g_value_set_pointer (value, self->minFrameRate);
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_FRAME_RATE:
g_value_set_pointer (value, self->maxFrameRate);
break;
case PROP_MPD_REPRESENTATION_BASE_FRAME_RATE:
g_value_set_pointer (value, self->frameRate);
break;
case PROP_MPD_REPRESENTATION_BASE_AUDIO_SAMPLING_RATE:
g_value_set_uint (value, atoi (self->audioSamplingRate));
break;
case PROP_MPD_REPRESENTATION_BASE_MIMETYPE:
g_value_set_string (value, self->mimeType);
break;
case PROP_MPD_REPRESENTATION_BASE_SEGMENT_PROFILES:
g_value_set_string (value, self->segmentProfiles);
break;
case PROP_MPD_REPRESENTATION_BASE_CODECS:
g_value_set_string (value, self->codecs);
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_SAP_PERIOD:
g_value_set_double (value, self->maximumSAPPeriod);
break;
case PROP_MPD_REPRESENTATION_BASE_START_WITH_SAP:
g_value_set_int (value, self->startWithSAP);
break;
case PROP_MPD_REPRESENTATION_BASE_MAX_PLAYOUT_RATE:
g_value_set_double (value, self->maxPlayoutRate);
break;
case PROP_MPD_REPRESENTATION_BASE_CODING_DEPENDENCY:
g_value_set_boolean (value, self->codingDependency);
self->codingDependency = g_value_get_boolean (value);
break;
case PROP_MPD_REPRESENTATION_BASE_SCAN_TYPE:
g_value_set_string (value, self->scanType);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_representation_base_node_finalize (GObject * object)
{
GstMPDRepresentationBaseNode *self =
GST_MPD_REPRESENTATION_BASE_NODE (object);
if (self->profiles)
xmlFree (self->profiles);
g_slice_free (GstXMLRatio, self->sar);
g_slice_free (GstXMLFrameRate, self->frameRate);
g_slice_free (GstXMLFrameRate, self->minFrameRate);
g_slice_free (GstXMLFrameRate, self->maxFrameRate);
if (self->audioSamplingRate)
xmlFree (self->audioSamplingRate);
if (self->mimeType)
xmlFree (self->mimeType);
if (self->segmentProfiles)
xmlFree (self->segmentProfiles);
if (self->codecs)
xmlFree (self->codecs);
if (self->scanType)
xmlFree (self->scanType);
g_list_free_full (self->FramePacking,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->AudioChannelConfiguration,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
g_list_free_full (self->ContentProtection,
(GDestroyNotify) gst_mpd_descriptor_type_node_free);
if (self->caps)
gst_caps_unref (self->caps);
G_OBJECT_CLASS (gst_mpd_representation_base_node_parent_class)->finalize
(object);
}
/* Base class */
static void
gst_mpd_representation_base_get_xml_node (GstMPDNode * node,
xmlNodePtr representation_base_node)
{
GstMPDRepresentationBaseNode *self = GST_MPD_REPRESENTATION_BASE_NODE (node);
if (self->profiles)
gst_xml_helper_set_prop_string (representation_base_node, "profiles",
self->profiles);
if (self->width)
gst_xml_helper_set_prop_uint (representation_base_node, "width",
self->width);
if (self->height)
gst_xml_helper_set_prop_uint (representation_base_node, "height",
self->height);
gst_xml_helper_set_prop_ratio (representation_base_node, "sar", self->sar);
gst_xml_helper_set_prop_framerate (representation_base_node, "minFrameRate",
self->minFrameRate);
gst_xml_helper_set_prop_framerate (representation_base_node, "maxFrameRate",
self->maxFrameRate);
gst_xml_helper_set_prop_framerate (representation_base_node, "frameRate",
self->frameRate);
gst_xml_helper_set_prop_string (representation_base_node,
"audioSamplingRate", self->audioSamplingRate);
gst_xml_helper_set_prop_string (representation_base_node, "mimeType",
self->mimeType);
gst_xml_helper_set_prop_string (representation_base_node, "segmentProfiles",
self->segmentProfiles);
gst_xml_helper_set_prop_string (representation_base_node, "codecs",
self->codecs);
if (self->maximumSAPPeriod)
gst_xml_helper_set_prop_double (representation_base_node,
"maximumSAPPeriod", self->maximumSAPPeriod);
if (self->startWithSAP)
gst_xml_helper_set_prop_int (representation_base_node, "startWithSAP",
self->startWithSAP);
if (self->maxPlayoutRate)
gst_xml_helper_set_prop_double (representation_base_node, "maxPlayoutRate",
self->maxPlayoutRate);
if (self->codingDependency)
gst_xml_helper_set_prop_boolean (representation_base_node,
"codingDependency", self->codingDependency);
gst_xml_helper_set_prop_string (representation_base_node, "scanType",
self->scanType);
g_list_foreach (self->FramePacking,
gst_mpd_node_get_list_item, representation_base_node);
g_list_foreach (self->AudioChannelConfiguration,
gst_mpd_node_get_list_item, representation_base_node);
g_list_foreach (self->ContentProtection,
gst_mpd_node_get_list_item, representation_base_node);
}
static void
gst_mpd_representation_base_node_class_init (GstMPDRepresentationBaseNodeClass *
klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_mpd_representation_base_node_finalize;
object_class->set_property = gst_mpd_representation_base_node_set_property;
object_class->get_property = gst_mpd_representation_base_node_get_property;
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BASE_WIDTH, g_param_spec_uint ("width",
"width", "representation width", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BASE_HEIGHT, g_param_spec_uint ("height",
"height", "representation height", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BASE_MIMETYPE, g_param_spec_string ("mime-type",
"mimetype", "representation mimetype", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BASE_CODECS, g_param_spec_string ("codecs",
"codecs", "representation codec", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BASE_AUDIO_SAMPLING_RATE,
g_param_spec_uint ("audio-sampling-rate", "audio sampling rate",
"representation audio sampling rate", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_representation_base_node_init (GstMPDRepresentationBaseNode * self)
{
self->profiles = NULL;
self->width = 0;
self->height = 0;
self->sar = NULL;
self->minFrameRate = NULL;
self->maxFrameRate = NULL;
self->frameRate = NULL;
self->audioSamplingRate = NULL;
self->mimeType = NULL;
self->segmentProfiles = NULL;
self->codecs = NULL;
self->maximumSAPPeriod = 0;
self->startWithSAP = GST_SAP_TYPE_0;
self->maxPlayoutRate = 0.0;
self->codingDependency = FALSE;
self->scanType = NULL;
self->FramePacking = NULL;
self->AudioChannelConfiguration = NULL;
self->ContentProtection = NULL;
}
void
gst_mpd_representation_base_node_get_list_item (gpointer data,
gpointer user_data)
{
GstMPDNode *node = (GstMPDNode *) data;
xmlNodePtr parent_xml_node = (xmlNodePtr) user_data;
xmlNodePtr new_xml_node = gst_mpd_node_get_xml_pointer (node);
gst_mpd_representation_base_get_xml_node (node, new_xml_node);
xmlAddChild (parent_xml_node, new_xml_node);
}

View file

@ -0,0 +1,68 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDREPRESENTATIONBASENODE_H__
#define __GSTMPDREPRESENTATIONBASENODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_REPRESENTATION_BASE_NODE gst_mpd_representation_base_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDRepresentationBaseNode2, gst_mpd_representation_base_node, GST, MPD_REPRESENTATION_BASE_NODE, GstMPDNode)
typedef GstMPDRepresentationBaseNode2 GstMPDRepresentationBaseNode;
typedef GstMPDRepresentationBaseNode2Class GstMPDRepresentationBaseNodeClass;
#define glib_autoptr_clear_GstMPDRepresentationBaseNode glib_autoptr_clear_GstMPDRepresentationBaseNode2
struct _GstMPDRepresentationBaseNode2
{
GstObject base;
gchar *profiles;
guint width;
guint height;
GstXMLRatio *sar;
GstXMLFrameRate *minFrameRate;
GstXMLFrameRate *maxFrameRate;
GstXMLFrameRate *frameRate;
gchar *audioSamplingRate;
gchar *mimeType;
gchar *segmentProfiles;
gchar *codecs;
GstCaps *caps;
gdouble maximumSAPPeriod;
GstMPDSAPType startWithSAP;
gdouble maxPlayoutRate;
gboolean codingDependency;
gchar *scanType;
/* list of FramePacking DescriptorType nodes */
GList *FramePacking;
/* list of AudioChannelConfiguration DescriptorType nodes */
GList *AudioChannelConfiguration;
/* list of ContentProtection DescriptorType nodes */
GList *ContentProtection;
};
void gst_mpd_representation_base_node_get_list_item (gpointer data, gpointer user_data);
G_END_DECLS
#endif /* __GSTMPDREPRESENTATIONBASENODE_H__ */

View file

@ -0,0 +1,198 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdrepresentationnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDRepresentationNode2, gst_mpd_representation_node,
GST_TYPE_MPD_REPRESENTATION_BASE_NODE);
enum
{
PROP_MPD_REPRESENTATION_0,
PROP_MPD_REPRESENTATION_ID,
PROP_MPD_REPRESENTATION_BANDWIDTH,
PROP_MPD_REPRESENTATION_QUALITY_RANKING,
};
/* GObject VMethods */
static void
gst_mpd_representation_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDRepresentationNode *self = GST_MPD_REPRESENTATION_NODE (object);
switch (prop_id) {
case PROP_MPD_REPRESENTATION_ID:
g_free (self->id);
self->id = g_value_dup_string (value);
break;
case PROP_MPD_REPRESENTATION_BANDWIDTH:
self->bandwidth = g_value_get_uint (value);
break;
case PROP_MPD_REPRESENTATION_QUALITY_RANKING:
self->qualityRanking = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_representation_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDRepresentationNode *self = GST_MPD_REPRESENTATION_NODE (object);
switch (prop_id) {
case PROP_MPD_REPRESENTATION_ID:
g_value_set_string (value, self->id);
break;
case PROP_MPD_REPRESENTATION_BANDWIDTH:
g_value_set_uint (value, self->bandwidth);
break;
case PROP_MPD_REPRESENTATION_QUALITY_RANKING:
g_value_set_uint (value, self->qualityRanking);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_representation_node_finalize (GObject * object)
{
GstMPDRepresentationNode *self = GST_MPD_REPRESENTATION_NODE (object);
if (self->id)
xmlFree (self->id);
g_strfreev (self->dependencyId);
g_strfreev (self->mediaStreamStructureId);
g_list_free_full (self->SubRepresentations,
(GDestroyNotify) gst_mpd_sub_representation_node_free);
gst_mpd_segment_base_node_free (self->SegmentBase);
gst_mpd_segment_template_node_free (self->SegmentTemplate);
gst_mpd_segment_list_node_free (self->SegmentList);
g_list_free_full (self->BaseURLs, (GDestroyNotify) gst_mpd_baseurl_node_free);
G_OBJECT_CLASS (gst_mpd_representation_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_representation_get_xml_node (GstMPDNode * node)
{
gchar *value;
xmlNodePtr representation_xml_node = NULL;
GstMPDRepresentationNode *self = GST_MPD_REPRESENTATION_NODE (node);
representation_xml_node = xmlNewNode (NULL, (xmlChar *) "Representation");
gst_xml_helper_set_prop_string (representation_xml_node, "id", self->id);
gst_xml_helper_set_prop_uint (representation_xml_node, "bandwidth",
self->bandwidth);
if (self->qualityRanking)
gst_xml_helper_set_prop_uint (representation_xml_node, "qualityRanking",
self->qualityRanking);
if (self->dependencyId) {
value = g_strjoinv (" ", self->dependencyId);
gst_xml_helper_set_prop_string (representation_xml_node, "dependencyId",
value);
g_free (value);
}
if (self->mediaStreamStructureId) {
value = g_strjoinv (" ", self->mediaStreamStructureId);
gst_xml_helper_set_prop_string (representation_xml_node,
"mediaStreamStructureId", value);
g_free (value);
}
g_list_foreach (self->BaseURLs, gst_mpd_node_get_list_item,
representation_xml_node);
g_list_foreach (self->SubRepresentations,
gst_mpd_representation_base_node_get_list_item, representation_xml_node);
gst_mpd_node_add_child_node (GST_MPD_NODE (self->SegmentBase),
representation_xml_node);
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentTemplate), representation_xml_node);
gst_mpd_mult_segment_base_node_add_child_node (GST_MPD_NODE
(self->SegmentList), representation_xml_node);
return representation_xml_node;
}
static void
gst_mpd_representation_node_class_init (GstMPDRepresentationNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_representation_node_finalize;
object_class->set_property = gst_mpd_representation_node_set_property;
object_class->get_property = gst_mpd_representation_node_get_property;
m_klass->get_xml_node = gst_mpd_representation_get_xml_node;
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_BANDWIDTH, g_param_spec_uint ("bandwidth",
"bandwidth", "representation bandwidth", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_REPRESENTATION_QUALITY_RANKING,
g_param_spec_uint ("quality-ranking", "quality ranking",
"representation quality ranking", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_representation_node_init (GstMPDRepresentationNode * self)
{
self->id = NULL;
self->bandwidth = 0;
self->qualityRanking = 0;
self->dependencyId = NULL;
self->mediaStreamStructureId = NULL;
self->BaseURLs = NULL;
self->SubRepresentations = NULL;
self->SegmentBase = NULL;
self->SegmentTemplate = NULL;
self->SegmentList = NULL;
}
GstMPDRepresentationNode *
gst_mpd_representation_node_new (void)
{
return g_object_new (GST_TYPE_MPD_REPRESENTATION_NODE, NULL);
}
void
gst_mpd_representation_node_free (GstMPDRepresentationNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,64 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDREPRESENTATIONNODE_H__
#define __GSTMPDREPRESENTATIONNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdrepresentationbasenode.h"
#include "gstmpdsegmentlistnode.h"
#include "gstmpdsegmenttemplatenode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_REPRESENTATION_NODE gst_mpd_representation_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDRepresentationNode2, gst_mpd_representation_node, GST, MPD_REPRESENTATION_NODE, GstMPDRepresentationBaseNode)
typedef GstMPDRepresentationNode2 GstMPDRepresentationNode;
typedef GstMPDRepresentationNode2Class GstMPDRepresentationNodeClass;
struct _GstMPDRepresentationNode2
{
GstMPDRepresentationBaseNode parent_instance;
gchar *id;
guint bandwidth;
guint qualityRanking;
gchar **dependencyId; /* StringVectorType */
gchar **mediaStreamStructureId; /* StringVectorType */
/* list of BaseURL nodes */
GList *BaseURLs;
/* list of SubRepresentation nodes */
GList *SubRepresentations;
/* SegmentBase node */
GstMPDSegmentBaseNode *SegmentBase;
/* SegmentTemplate node */
GstMPDSegmentTemplateNode *SegmentTemplate;
/* SegmentList node */
GstMPDSegmentListNode *SegmentList;
};
GstMPDRepresentationNode * gst_mpd_representation_node_new (void);
void gst_mpd_representation_node_free (GstMPDRepresentationNode* self);
G_END_DECLS
#endif /* __GSTMPDREPRESENTATIONNODE_H__ */

View file

@ -0,0 +1,405 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdrootnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDRootNode2, gst_mpd_root_node, GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_ROOT_0,
PROP_MPD_ROOT_DEFAULT_NAMESPACE,
PROP_MPD_ROOT_NAMESPACE_XSI,
PROP_MPD_ROOT_NAMESPACE_EXT,
PROP_MPD_ROOT_SCHEMA_LOCATION,
PROP_MPD_ROOT_ID,
PROP_MPD_ROOT_PROFILES,
PROP_MPD_ROOT_TYPE,
PROP_MPD_ROOT_PUBLISH_TIME,
PROP_MPD_ROOT_AVAILABILTY_START_TIME,
PROP_MPD_ROOT_AVAILABILTY_END_TIME,
PROP_MPD_ROOT_MEDIA_PRESENTATION_DURATION,
PROP_MPD_ROOT_MINIMUM_UPDATE_PERIOD,
PROP_MPD_ROOT_MIN_BUFFER_TIME,
PROP_MPD_ROOT_TIMESHIFT_BUFFER_DEPTH,
PROP_MPD_ROOT_SUGGESTED_PRESENTATION_DELAY,
PROP_MPD_ROOT_MAX_SEGMENT_DURATION,
PROP_MPD_ROOT_MAX_SUBSEGMENT_DURATION,
};
/* GObject VMethods */
static void
gst_mpd_root_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDRootNode *self = GST_MPD_ROOT_NODE (object);
switch (prop_id) {
case PROP_MPD_ROOT_DEFAULT_NAMESPACE:
g_free (self->default_namespace);
self->default_namespace = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_NAMESPACE_XSI:
g_free (self->namespace_xsi);
self->namespace_xsi = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_NAMESPACE_EXT:
g_free (self->namespace_ext);
self->namespace_ext = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_SCHEMA_LOCATION:
g_free (self->schemaLocation);
self->schemaLocation = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_ID:
g_free (self->id);
self->id = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_PROFILES:
g_free (self->profiles);
self->profiles = g_value_dup_string (value);
break;
case PROP_MPD_ROOT_TYPE:
self->type = (GstMPDFileType) g_value_get_int (value);
break;
case PROP_MPD_ROOT_AVAILABILTY_START_TIME:
if (self->availabilityStartTime)
gst_date_time_unref (self->availabilityStartTime);
self->availabilityStartTime = g_value_dup_boxed (value);
break;
case PROP_MPD_ROOT_AVAILABILTY_END_TIME:
if (self->availabilityEndTime)
gst_date_time_unref (self->availabilityEndTime);
self->availabilityEndTime = g_value_dup_boxed (value);
break;
case PROP_MPD_ROOT_PUBLISH_TIME:
if (self->publishTime)
gst_date_time_unref (self->publishTime);
self->publishTime = g_value_dup_boxed (value);
break;
case PROP_MPD_ROOT_MEDIA_PRESENTATION_DURATION:
self->mediaPresentationDuration = g_value_get_uint64 (value);
break;
case PROP_MPD_ROOT_MINIMUM_UPDATE_PERIOD:
self->minimumUpdatePeriod = g_value_get_uint64 (value);
break;
case PROP_MPD_ROOT_MIN_BUFFER_TIME:
self->minBufferTime = g_value_get_uint64 (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_root_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDRootNode *self = GST_MPD_ROOT_NODE (object);
switch (prop_id) {
case PROP_MPD_ROOT_DEFAULT_NAMESPACE:
g_value_set_string (value, self->default_namespace);
break;
case PROP_MPD_ROOT_NAMESPACE_XSI:
g_value_set_string (value, self->namespace_xsi);
break;
case PROP_MPD_ROOT_NAMESPACE_EXT:
g_value_set_string (value, self->namespace_ext);
break;
case PROP_MPD_ROOT_SCHEMA_LOCATION:
g_value_set_string (value, self->schemaLocation);
break;
case PROP_MPD_ROOT_ID:
g_value_set_string (value, self->id);
break;
case PROP_MPD_ROOT_PROFILES:
g_value_set_string (value, self->profiles);
break;
case PROP_MPD_ROOT_TYPE:
g_value_set_int (value, self->type);
break;
case PROP_MPD_ROOT_AVAILABILTY_START_TIME:
g_value_set_boxed (value, self->availabilityStartTime);
break;
case PROP_MPD_ROOT_AVAILABILTY_END_TIME:
g_value_set_boxed (value, self->availabilityEndTime);
break;
case PROP_MPD_ROOT_PUBLISH_TIME:
g_value_set_boxed (value, self->publishTime);
break;
case PROP_MPD_ROOT_MEDIA_PRESENTATION_DURATION:
g_value_set_uint64 (value, self->mediaPresentationDuration);
break;
case PROP_MPD_ROOT_MINIMUM_UPDATE_PERIOD:
g_value_set_uint64 (value, self->minimumUpdatePeriod);
break;
case PROP_MPD_ROOT_MIN_BUFFER_TIME:
g_value_set_uint64 (value, self->minBufferTime);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_root_node_finalize (GObject * object)
{
GstMPDRootNode *self = GST_MPD_ROOT_NODE (object);
g_free (self->default_namespace);
g_free (self->namespace_xsi);
g_free (self->namespace_ext);
g_free (self->schemaLocation);
g_free (self->id);
g_free (self->profiles);
if (self->availabilityStartTime)
gst_date_time_unref (self->availabilityStartTime);
if (self->availabilityEndTime)
gst_date_time_unref (self->availabilityEndTime);
if (self->publishTime)
gst_date_time_unref (self->publishTime);
g_list_free_full (self->ProgramInfos,
(GDestroyNotify) gst_mpd_program_information_node_free);
g_list_free_full (self->BaseURLs, (GDestroyNotify) gst_mpd_baseurl_node_free);
g_list_free_full (self->Locations,
(GDestroyNotify) gst_mpd_location_node_free);
g_list_free_full (self->Periods, (GDestroyNotify) gst_mpd_period_node_free);
g_list_free_full (self->Metrics, (GDestroyNotify) gst_mpd_metrics_node_free);
g_list_free_full (self->UTCTimings,
(GDestroyNotify) gst_mpd_utctiming_node_free);
G_OBJECT_CLASS (gst_mpd_root_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_root_get_xml_node (GstMPDNode * node)
{
xmlNodePtr root_xml_node;
GstMPDRootNode *self = GST_MPD_ROOT_NODE (node);
root_xml_node = xmlNewNode (NULL, (xmlChar *) "MPD");
gst_xml_helper_set_prop_string (root_xml_node, "xmlns",
self->default_namespace);
gst_xml_helper_set_prop_string (root_xml_node, "profiles", self->profiles);
gst_xml_helper_set_prop_string (root_xml_node, "schemaLocation",
self->schemaLocation);
gst_xml_helper_set_prop_string (root_xml_node, "xmlns:xsi",
self->namespace_xsi);
gst_xml_helper_set_prop_string (root_xml_node, "xmlns:ext",
self->namespace_ext);
gst_xml_helper_set_prop_string (root_xml_node, "id", self->id);
if (self->type == GST_MPD_FILE_TYPE_STATIC)
gst_xml_helper_set_prop_string (root_xml_node, "type", (gchar *) "static");
else
gst_xml_helper_set_prop_string (root_xml_node, "type", (gchar *) "dynamic");
gst_xml_helper_set_prop_date_time (root_xml_node, "availabilityStartTime",
self->availabilityStartTime);
gst_xml_helper_set_prop_date_time (root_xml_node, "availabilityEndTime",
self->availabilityEndTime);
gst_xml_helper_set_prop_date_time (root_xml_node, "publishTime",
self->publishTime);
if (self->mediaPresentationDuration)
gst_xml_helper_set_prop_duration (root_xml_node,
"mediaPresentationDuration", self->mediaPresentationDuration);
if (self->minimumUpdatePeriod)
gst_xml_helper_set_prop_duration (root_xml_node, "minimumUpdatePeriod",
self->minimumUpdatePeriod);
if (self->minBufferTime)
gst_xml_helper_set_prop_duration (root_xml_node, "minBufferTime",
self->minBufferTime);
if (self->timeShiftBufferDepth)
gst_xml_helper_set_prop_duration (root_xml_node, "timeShiftBufferDepth",
self->timeShiftBufferDepth);
if (self->suggestedPresentationDelay)
gst_xml_helper_set_prop_duration (root_xml_node,
"suggestedPresentationDelay", self->suggestedPresentationDelay);
if (self->maxSegmentDuration)
gst_xml_helper_set_prop_duration (root_xml_node, "maxSegmentDuration",
self->maxSegmentDuration);
if (self->maxSubsegmentDuration)
gst_xml_helper_set_prop_duration (root_xml_node, "maxSubsegmentDuration",
self->maxSubsegmentDuration);
g_list_foreach (self->BaseURLs, gst_mpd_node_get_list_item, root_xml_node);
g_list_foreach (self->Locations, gst_mpd_node_get_list_item, root_xml_node);
g_list_foreach (self->ProgramInfos, gst_mpd_node_get_list_item,
root_xml_node);
g_list_foreach (self->Periods, gst_mpd_node_get_list_item, root_xml_node);
g_list_foreach (self->Metrics, gst_mpd_node_get_list_item, root_xml_node);
g_list_foreach (self->UTCTimings, gst_mpd_node_get_list_item, root_xml_node);
return root_xml_node;
}
static gboolean
gst_mpd_root_get_xml_buffer (GstMPDNode * node, gchar ** doc_content,
gint * doc_size)
{
xmlDocPtr doc;
xmlNodePtr root_xml_node;
xmlChar *xmlbody;
doc = xmlNewDoc ((xmlChar *) "1.0");
root_xml_node = gst_mpd_root_get_xml_node (node);
xmlDocSetRootElement (doc, root_xml_node);
xmlDocDumpMemory (doc, &xmlbody, doc_size);
*doc_content = g_strndup ((gchar *) xmlbody, *doc_size);
xmlFree (xmlbody);
xmlFreeDoc (doc);
return TRUE;
}
static void
gst_mpd_root_node_class_init (GstMPDRootNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_root_node_finalize;
object_class->set_property = gst_mpd_root_node_set_property;
object_class->get_property = gst_mpd_root_node_get_property;
m_klass->get_xml_buffer = gst_mpd_root_get_xml_buffer;
m_klass->get_xml_node = gst_mpd_root_get_xml_node;
g_object_class_install_property (object_class,
PROP_MPD_ROOT_DEFAULT_NAMESPACE, g_param_spec_string ("default-namespace",
"default namespace", "default namespace", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_NAMESPACE_XSI,
g_param_spec_string ("namespace-xsi", "namespace xsi", "namespace xsi",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_NAMESPACE_EXT,
g_param_spec_string ("namespace-ext", "namespace ext", "namespace ext",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_SCHEMA_LOCATION,
g_param_spec_string ("schema-location", "schema location",
"schema location for period", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_ID,
g_param_spec_string ("id", "id", "unique id for period", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_PROFILES,
g_param_spec_string ("profiles", "profiles", "profiles", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MPD_ROOT_TYPE,
g_param_spec_int ("type", "MPD type",
"MPD type",
GST_MPD_FILE_TYPE_STATIC, GST_MPD_FILE_TYPE_DYNAMIC,
GST_MPD_FILE_TYPE_STATIC,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_AVAILABILTY_START_TIME,
g_param_spec_boxed ("availability-start-time", "Availability start time",
"MPD availability start time", GST_TYPE_DATE_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_AVAILABILTY_END_TIME,
g_param_spec_boxed ("availability-end-time", "Availability end time",
"MPD availability end time", GST_TYPE_DATE_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_PUBLISH_TIME,
g_param_spec_boxed ("publish-time", "publish time",
"MPD publish time", GST_TYPE_DATE_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_MEDIA_PRESENTATION_DURATION,
g_param_spec_uint64 ("media-presentation-duration",
"media presentation duration", "media presentation duration", 0,
G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_MINIMUM_UPDATE_PERIOD,
g_param_spec_uint64 ("minimum-update-period",
"minimum update period", "minimum update period", 0,
G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_ROOT_MIN_BUFFER_TIME,
g_param_spec_uint64 ("min-buffer-time", "mininim buffer time",
"mininim buffer time", 0,
G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_root_node_init (GstMPDRootNode * self)
{
self->default_namespace = NULL;
self->namespace_xsi = NULL;
self->namespace_ext = NULL;
self->schemaLocation = NULL;
self->id = NULL;
self->profiles = NULL;
self->type = GST_MPD_FILE_TYPE_STATIC;
self->availabilityStartTime = NULL;
self->availabilityEndTime = NULL;
self->publishTime = NULL;
self->mediaPresentationDuration = 0; /* [ms] */
self->minimumUpdatePeriod = 0; /* [ms] */
self->minBufferTime = 2000; /* [ms] */
self->timeShiftBufferDepth = 0; /* [ms] */
self->suggestedPresentationDelay = 0; /* [ms] */
self->maxSegmentDuration = 0; /* [ms] */
self->maxSubsegmentDuration = 0; /* [ms] */
/* list of BaseURL nodes */
self->BaseURLs = NULL;
/* list of Location nodes */
self->Locations = NULL;
/* List of ProgramInformation nodes */
self->ProgramInfos = NULL;
/* list of Periods nodes */
self->Periods = NULL;
/* list of Metrics nodes */
self->Metrics = NULL;
/* list of GstUTCTimingNode nodes */
self->UTCTimings = NULL;
}
GstMPDRootNode *
gst_mpd_root_node_new (void)
{
return g_object_new (GST_TYPE_MPD_ROOT_NODE, NULL);
}
void
gst_mpd_root_node_free (GstMPDRootNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,73 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDROOTNODE_H__
#define __GSTMPDROOTNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_ROOT_NODE gst_mpd_root_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDRootNode2, gst_mpd_root_node, GST, MPD_ROOT_NODE, GstMPDNode)
typedef GstMPDRootNode2 GstMPDRootNode;
typedef GstMPDRootNode2Class GstMPDRootNodeClass;
struct _GstMPDRootNode2
{
GstObject parent_instance;
gchar *default_namespace;
gchar *namespace_xsi;
gchar *namespace_ext;
gchar *schemaLocation;
gchar *id;
gchar *profiles;
GstMPDFileType type;
GstDateTime *availabilityStartTime;
GstDateTime *availabilityEndTime;
GstDateTime *publishTime;
guint64 mediaPresentationDuration; /* [ms] */
guint64 minimumUpdatePeriod; /* [ms] */
guint64 minBufferTime; /* [ms] */
guint64 timeShiftBufferDepth; /* [ms] */
guint64 suggestedPresentationDelay; /* [ms] */
guint64 maxSegmentDuration; /* [ms] */
guint64 maxSubsegmentDuration; /* [ms] */
/* list of BaseURL nodes */
GList *BaseURLs;
/* list of Location nodes */
GList *Locations;
/* List of ProgramInformation nodes */
GList *ProgramInfos;
/* list of Periods nodes */
GList *Periods;
/* list of Metrics nodes */
GList *Metrics;
/* list of GstUTCTimingNode nodes */
GList *UTCTimings;
};
GstMPDRootNode * gst_mpd_root_node_new (void);
void gst_mpd_root_node_free (GstMPDRootNode* self);
G_END_DECLS
#endif /* __GSTMPDROOTNODE_H__ */

View file

@ -0,0 +1,112 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsegmentbasenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSegmentBaseNode2, gst_mpd_segment_base_node,
GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_segment_base_node_finalize (GObject * object)
{
GstMPDSegmentBaseNode *self = GST_MPD_SEGMENT_BASE_NODE (object);
if (self->indexRange)
g_slice_free (GstXMLRange, self->indexRange);
gst_mpd_url_type_node_free (self->Initialization);
gst_mpd_url_type_node_free (self->RepresentationIndex);
G_OBJECT_CLASS (gst_mpd_segment_base_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_segment_base_get_xml_node (GstMPDNode * node)
{
xmlNodePtr segment_base_xml_node = NULL;
GstMPDSegmentBaseNode *self = GST_MPD_SEGMENT_BASE_NODE (node);
segment_base_xml_node = xmlNewNode (NULL, (xmlChar *) "SegmentBase");
if (self->timescale)
gst_xml_helper_set_prop_uint (segment_base_xml_node, "timescale",
self->timescale);
if (self->presentationTimeOffset)
gst_xml_helper_set_prop_uint64 (segment_base_xml_node,
"presentationTimeOffset", self->presentationTimeOffset);
if (self->indexRange) {
gst_xml_helper_set_prop_range (segment_base_xml_node, "indexRange",
self->indexRange);
gst_xml_helper_set_prop_boolean (segment_base_xml_node, "indexRangeExact",
self->indexRangeExact);
}
if (self->Initialization)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->Initialization),
segment_base_xml_node);
if (self->RepresentationIndex)
gst_mpd_node_add_child_node (GST_MPD_NODE (self->RepresentationIndex),
segment_base_xml_node);
return segment_base_xml_node;
}
static void
gst_mpd_segment_base_node_class_init (GstMPDSegmentBaseNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_segment_base_node_finalize;
m_klass->get_xml_node = gst_mpd_segment_base_get_xml_node;
}
static void
gst_mpd_segment_base_node_init (GstMPDSegmentBaseNode * self)
{
self->timescale = 0;
self->presentationTimeOffset = 0;
self->indexRange = NULL;
self->indexRangeExact = FALSE;
/* Initialization node */
self->Initialization = NULL;
/* RepresentationIndex node */
self->RepresentationIndex = NULL;
}
GstMPDSegmentBaseNode *
gst_mpd_segment_base_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SEGMENT_BASE_NODE, NULL);
}
void
gst_mpd_segment_base_node_free (GstMPDSegmentBaseNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,56 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSEGMENTBASENODE_H__
#define __GSTMPDSEGMENTBASENODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
#include "gstmpdurltypenode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SEGMENT_BASE_NODE gst_mpd_segment_base_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSegmentBaseNode2, gst_mpd_segment_base_node, GST, MPD_SEGMENT_BASE_NODE, GstMPDNode)
typedef GstMPDSegmentBaseNode2 GstMPDSegmentBaseNode;
typedef GstMPDSegmentBaseNode2Class GstMPDSegmentBaseNodeClass;
#define glib_autoptr_clear_GstMPDMultSegmentBaseNode glib_autoptr_clear_GstMPDMultSegmentBaseNode2
struct _GstMPDSegmentBaseNode2
{
GstObject parent_instance;
guint timescale;
guint64 presentationTimeOffset;
GstXMLRange *indexRange;
gboolean indexRangeExact;
/* Initialization node */
GstMPDURLTypeNode *Initialization;
/* RepresentationIndex node */
GstMPDURLTypeNode *RepresentationIndex;
};
GstMPDSegmentBaseNode * gst_mpd_segment_base_node_new (void);
void gst_mpd_segment_base_node_free (GstMPDSegmentBaseNode* self);
G_END_DECLS
#endif /* __GSTMPDSEGMENTBASENODE_H__ */

View file

@ -0,0 +1,104 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsegmentlistnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSegmentListNode2, gst_mpd_segment_list_node,
GST_TYPE_MPD_MULT_SEGMENT_BASE_NODE);
/* GObject VMethods */
static void
gst_mpd_segment_list_node_finalize (GObject * object)
{
GstMPDSegmentListNode *self = GST_MPD_SEGMENT_LIST_NODE (object);
g_list_free_full (self->SegmentURL,
(GDestroyNotify) gst_mpd_segment_url_node_free);
if (self->xlink_href)
xmlFree (self->xlink_href);
G_OBJECT_CLASS (gst_mpd_segment_list_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_segment_list_get_xml_node (GstMPDNode * node)
{
xmlNodePtr segment_list_xml_node = NULL;
GstMPDSegmentListNode *self = GST_MPD_SEGMENT_LIST_NODE (node);
segment_list_xml_node = xmlNewNode (NULL, (xmlChar *) "SegmentList");
g_list_foreach (self->SegmentURL, gst_mpd_node_get_list_item,
segment_list_xml_node);
if (self->xlink_href)
gst_xml_helper_set_prop_string (segment_list_xml_node, "xlink_href",
self->xlink_href);
return segment_list_xml_node;
}
static void
gst_mpd_segment_list_node_class_init (GstMPDSegmentListNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_segment_list_node_finalize;
m_klass->get_xml_node = gst_mpd_segment_list_get_xml_node;
}
static void
gst_mpd_segment_list_node_init (GstMPDSegmentListNode * self)
{
self->SegmentURL = NULL;
self->xlink_href = NULL;
self->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST;
}
GstMPDSegmentListNode *
gst_mpd_segment_list_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SEGMENT_LIST_NODE, NULL);
}
void
gst_mpd_segment_list_node_free (GstMPDSegmentListNode * self)
{
if (self)
gst_object_unref (self);
}
void
gst_mpd_segment_list_node_add_segment (GstMPDSegmentListNode * self,
GstMPDSegmentURLNode * segment_url)
{
g_return_if_fail (self != NULL);
self->SegmentURL = g_list_append (self->SegmentURL, segment_url);
}

View file

@ -0,0 +1,55 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSEGMENTLISTNODE_H__
#define __GSTMPDSEGMENTLISTNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdmultsegmentbasenode.h"
#include "gstmpdsegmenturlnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SEGMENT_LIST_NODE gst_mpd_segment_list_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSegmentListNode2, gst_mpd_segment_list_node, GST, MPD_SEGMENT_LIST_NODE, GstMPDMultSegmentBaseNode)
typedef GstMPDSegmentListNode2 GstMPDSegmentListNode;
typedef GstMPDSegmentListNode2Class GstMPDSegmentListNodeClass;
struct _GstMPDSegmentListNode2
{
GstMPDMultSegmentBaseNode parent_instance;
/* extension */
/* list of SegmentURL nodes */
GList *SegmentURL;
gchar *xlink_href;
GstMPDXLinkActuate actuate;
};
GstMPDSegmentListNode * gst_mpd_segment_list_node_new (void);
void gst_mpd_segment_list_node_free (GstMPDSegmentListNode* self);
void gst_mpd_segment_list_node_add_segment(GstMPDSegmentListNode * self, GstMPDSegmentURLNode * segment_url);
G_END_DECLS
#endif /* __GSTMPDSEGMENTLISTNODE_H__ */

View file

@ -0,0 +1,186 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsegmenttemplatenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSegmentTemplateNode2, gst_mpd_segment_template_node,
GST_TYPE_MPD_MULT_SEGMENT_BASE_NODE);
enum
{
PROP_MPD_SEGMENT_TEMPLATE_0,
PROP_MPD_SEGMENT_TEMPLATE_MEDIA,
PROP_MPD_SEGMENT_TEMPLATE_INDEX,
PROP_MPD_SEGMENT_TEMPLATE_INITIALIZATION,
PROP_MPD_SEGMENT_TEMPLATE_BITSTREAM_SWITCHING,
};
/* GObject VMethods */
static void
gst_mpd_segment_template_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDSegmentTemplateNode *self = GST_MPD_SEGMENT_TEMPLATE_NODE (object);
switch (prop_id) {
case PROP_MPD_SEGMENT_TEMPLATE_MEDIA:
self->media = g_value_dup_string (value);
break;
case PROP_MPD_SEGMENT_TEMPLATE_INDEX:
self->index = g_value_dup_string (value);
break;
case PROP_MPD_SEGMENT_TEMPLATE_INITIALIZATION:
self->initialization = g_value_dup_string (value);
break;
case PROP_MPD_SEGMENT_TEMPLATE_BITSTREAM_SWITCHING:
self->bitstreamSwitching = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_segment_template_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDSegmentTemplateNode *self = GST_MPD_SEGMENT_TEMPLATE_NODE (object);
switch (prop_id) {
case PROP_MPD_SEGMENT_TEMPLATE_MEDIA:
g_value_set_string (value, self->media);
break;
case PROP_MPD_SEGMENT_TEMPLATE_INDEX:
g_value_set_string (value, self->index);
break;
case PROP_MPD_SEGMENT_TEMPLATE_INITIALIZATION:
g_value_set_string (value, self->initialization);
break;
case PROP_MPD_SEGMENT_TEMPLATE_BITSTREAM_SWITCHING:
g_value_set_string (value, self->bitstreamSwitching);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_segment_template_node_finalize (GObject * object)
{
GstMPDSegmentTemplateNode *self = GST_MPD_SEGMENT_TEMPLATE_NODE (object);
if (self->media)
xmlFree (self->media);
if (self->index)
xmlFree (self->index);
if (self->initialization)
xmlFree (self->initialization);
if (self->bitstreamSwitching)
xmlFree (self->bitstreamSwitching);
G_OBJECT_CLASS (gst_mpd_segment_template_node_parent_class)->finalize
(object);
}
/* Base class */
static xmlNodePtr
gst_mpd_segment_template_get_xml_node (GstMPDNode * node)
{
xmlNodePtr segment_template_xml_node = NULL;
GstMPDSegmentTemplateNode *self = GST_MPD_SEGMENT_TEMPLATE_NODE (node);
segment_template_xml_node = xmlNewNode (NULL, (xmlChar *) "SegmentTemplate");
if (self->media)
gst_xml_helper_set_prop_string (segment_template_xml_node, "media",
self->media);
if (self->index)
gst_xml_helper_set_prop_string (segment_template_xml_node, "index",
self->index);
if (self->initialization)
gst_xml_helper_set_prop_string (segment_template_xml_node, "initialization",
self->initialization);
if (self->bitstreamSwitching)
gst_xml_helper_set_prop_string (segment_template_xml_node,
"bitstreamSwitching", self->bitstreamSwitching);
return segment_template_xml_node;
}
static void
gst_mpd_segment_template_node_class_init (GstMPDSegmentTemplateNodeClass *
klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_segment_template_node_finalize;
object_class->set_property = gst_mpd_segment_template_node_set_property;
object_class->get_property = gst_mpd_segment_template_node_get_property;
m_klass->get_xml_node = gst_mpd_segment_template_get_xml_node;
g_object_class_install_property (object_class,
PROP_MPD_SEGMENT_TEMPLATE_MEDIA, g_param_spec_string ("media",
"media", "media", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_SEGMENT_TEMPLATE_INDEX, g_param_spec_string ("index",
"index", "index", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_SEGMENT_TEMPLATE_INITIALIZATION,
g_param_spec_string ("initialization", "initialization", "initialization",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class,
PROP_MPD_SEGMENT_TEMPLATE_BITSTREAM_SWITCHING,
g_param_spec_string ("bitstream-switching", "bitstream switching",
"bitstream switching", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_segment_template_node_init (GstMPDSegmentTemplateNode * self)
{
self->media = NULL;
self->index = NULL;
self->initialization = NULL;
self->bitstreamSwitching = NULL;
}
GstMPDSegmentTemplateNode *
gst_mpd_segment_template_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SEGMENT_TEMPLATE_NODE, NULL);
}
void
gst_mpd_segment_template_node_free (GstMPDSegmentTemplateNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,51 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSEGMENTTEMPLATENODE_H__
#define __GSTMPDSEGMENTTEMPLATENODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdmultsegmentbasenode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SEGMENT_TEMPLATE_NODE gst_mpd_segment_template_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSegmentTemplateNode2, gst_mpd_segment_template_node, GST, MPD_SEGMENT_TEMPLATE_NODE, GstMPDMultSegmentBaseNode)
typedef GstMPDSegmentTemplateNode2 GstMPDSegmentTemplateNode;
typedef GstMPDSegmentTemplateNode2Class GstMPDSegmentTemplateNodeClass;
struct _GstMPDSegmentTemplateNode2
{
GstMPDMultSegmentBaseNode parent_instance;
gchar *media;
gchar *index;
gchar *initialization;
gchar *bitstreamSwitching;
};
GstMPDSegmentTemplateNode * gst_mpd_segment_template_node_new (void);
void gst_mpd_segment_template_node_free (GstMPDSegmentTemplateNode* self);
G_END_DECLS
#endif /* __GSTMPDSEGMENTTEMPLATENODE_H__ */

View file

@ -0,0 +1,111 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsegmenttimelinenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSegmentTimelineNode2, gst_mpd_segment_timeline_node,
GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_segment_timeline_node_finalize (GObject * object)
{
GstMPDSegmentTimelineNode *self = GST_MPD_SEGMENT_TIMELINE_NODE (object);
g_queue_foreach (&self->S, (GFunc) gst_mpd_s_node_free, NULL);
g_queue_clear (&self->S);
G_OBJECT_CLASS (gst_mpd_segment_timeline_node_parent_class)->finalize
(object);
}
/* Base class */
static xmlNodePtr
gst_mpd_segment_timeline_get_xml_node (GstMPDNode * node)
{
xmlNodePtr segment_timeline_xml_node = NULL;
GstMPDSegmentTimelineNode *self = GST_MPD_SEGMENT_TIMELINE_NODE (node);
segment_timeline_xml_node = xmlNewNode (NULL, (xmlChar *) "SegmentTimeline");
g_queue_foreach (&self->S, (GFunc) gst_mpd_node_get_list_item,
segment_timeline_xml_node);
return segment_timeline_xml_node;
}
static void
gst_mpd_segment_timeline_node_class_init (GstMPDSegmentTimelineNodeClass *
klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_segment_timeline_node_finalize;
m_klass->get_xml_node = gst_mpd_segment_timeline_get_xml_node;
}
static void
gst_mpd_segment_timeline_node_init (GstMPDSegmentTimelineNode * self)
{
g_queue_init (&self->S);
}
GstMPDSegmentTimelineNode *
gst_mpd_segment_timeline_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SEGMENT_TIMELINE_NODE, NULL);
}
void
gst_mpd_segment_timeline_node_free (GstMPDSegmentTimelineNode * self)
{
if (self)
gst_object_unref (self);
}
GstMPDSegmentTimelineNode *
gst_mpd_segment_timeline_node_clone (GstMPDSegmentTimelineNode *
segment_timeline)
{
GstMPDSegmentTimelineNode *clone = NULL;
GList *list;
if (segment_timeline) {
clone = gst_mpd_segment_timeline_node_new ();
for (list = g_queue_peek_head_link (&segment_timeline->S); list;
list = g_list_next (list)) {
GstMPDSNode *s_node;
s_node = (GstMPDSNode *) list->data;
if (s_node) {
g_queue_push_tail (&clone->S, gst_mpd_s_node_clone (s_node));
}
}
}
return clone;
}

View file

@ -0,0 +1,49 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSEGMENTTIMELINENODE_H__
#define __GSTMPDSEGMENTTIMELINENODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SEGMENT_TIMELINE_NODE gst_mpd_segment_timeline_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSegmentTimelineNode2, gst_mpd_segment_timeline_node, GST, MPD_SEGMENT_TIMELINE_NODE, GstMPDNode)
typedef GstMPDSegmentTimelineNode2 GstMPDSegmentTimelineNode;
typedef GstMPDSegmentTimelineNode2Class GstMPDSegmentTimelineNodeClass;
struct _GstMPDSegmentTimelineNode2
{
GstObject parent_instance;
/* list of S nodes */
GQueue S;
};
GstMPDSegmentTimelineNode * gst_mpd_segment_timeline_node_new (void);
void gst_mpd_segment_timeline_node_free (GstMPDSegmentTimelineNode* self);
GstMPDSegmentTimelineNode *gst_mpd_segment_timeline_node_clone (GstMPDSegmentTimelineNode * pointer);
G_END_DECLS
#endif /* __GSTMPDSEGMENTTIMELINENODE_H__ */

View file

@ -0,0 +1,166 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsegmenturlnode.h"
#include "gstmpdparser.h"
#include "gstmpdhelper.h"
G_DEFINE_TYPE (GstMPDSegmentURLNode2, gst_mpd_segment_url_node,
GST_TYPE_MPD_NODE);
enum
{
PROP_MPD_SEGMENT_URL_0,
PROP_MPD_SEGMENT_URL_MEDIA,
};
/* GObject VMethods */
static void
gst_mpd_segment_url_node_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMPDSegmentURLNode *self = GST_MPD_SEGMENT_URL_NODE (object);
switch (prop_id) {
case PROP_MPD_SEGMENT_URL_MEDIA:
g_free (self->media);
self->media = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_segment_url_node_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMPDSegmentURLNode *self = GST_MPD_SEGMENT_URL_NODE (object);
switch (prop_id) {
case PROP_MPD_SEGMENT_URL_MEDIA:
g_value_set_string (value, self->media);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_mpd_segment_url_node_finalize (GObject * object)
{
GstMPDSegmentURLNode *self = GST_MPD_SEGMENT_URL_NODE (object);
if (self->media)
xmlFree (self->media);
g_slice_free (GstXMLRange, self->mediaRange);
if (self->index)
xmlFree (self->index);
g_slice_free (GstXMLRange, self->indexRange);
G_OBJECT_CLASS (gst_mpd_segment_url_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_segment_url_get_xml_node (GstMPDNode * node)
{
xmlNodePtr segment_url_xml_node = NULL;
GstMPDSegmentURLNode *self = GST_MPD_SEGMENT_URL_NODE (node);
segment_url_xml_node = xmlNewNode (NULL, (xmlChar *) "SegmentURL");
if (self->media)
gst_xml_helper_set_prop_string (segment_url_xml_node, "media", self->media);
if (self->mediaRange)
gst_xml_helper_set_prop_range (segment_url_xml_node, "mediaRange",
self->mediaRange);
if (self->index)
gst_xml_helper_set_prop_string (segment_url_xml_node, "index", self->index);
if (self->indexRange)
gst_xml_helper_set_prop_range (segment_url_xml_node, "indexRange",
self->indexRange);
return segment_url_xml_node;
}
static void
gst_mpd_segment_url_node_class_init (GstMPDSegmentURLNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_segment_url_node_finalize;
object_class->set_property = gst_mpd_segment_url_node_set_property;
object_class->get_property = gst_mpd_segment_url_node_get_property;
m_klass->get_xml_node = gst_mpd_segment_url_get_xml_node;
g_object_class_install_property (object_class,
PROP_MPD_SEGMENT_URL_MEDIA, g_param_spec_string ("media",
"media", "media description", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_mpd_segment_url_node_init (GstMPDSegmentURLNode * self)
{
self->media = NULL;
self->mediaRange = NULL;
self->index = NULL;
self->indexRange = NULL;
}
GstMPDSegmentURLNode *
gst_mpd_segment_url_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SEGMENT_URL_NODE, NULL);
}
void
gst_mpd_segment_url_node_free (GstMPDSegmentURLNode * self)
{
if (self)
gst_object_unref (self);
}
GstMPDSegmentURLNode *
gst_mpd_segment_url_node_clone (GstMPDSegmentURLNode * seg_url)
{
GstMPDSegmentURLNode *clone = NULL;
if (seg_url) {
clone = gst_mpd_segment_url_node_new ();
clone->media = xmlMemStrdup (seg_url->media);
clone->mediaRange = gst_xml_helper_clone_range (seg_url->mediaRange);
clone->index = xmlMemStrdup (seg_url->index);
clone->indexRange = gst_xml_helper_clone_range (seg_url->indexRange);
}
return clone;
}

View file

@ -0,0 +1,51 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSEGMENTURLNODE_H__
#define __GSTMPDSEGMENTURLNODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SEGMENT_URL_NODE gst_mpd_segment_url_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSegmentURLNode2, gst_mpd_segment_url_node, GST, MPD_SEGMENT_URL_NODE, GstMPDNode)
typedef GstMPDSegmentURLNode2 GstMPDSegmentURLNode;
typedef GstMPDSegmentURLNode2Class GstMPDSegmentURLNodeClass;
struct _GstMPDSegmentURLNode2
{
GstObject parent_instance;
gchar *media;
GstXMLRange *mediaRange;
gchar *index;
GstXMLRange *indexRange;
};
GstMPDSegmentURLNode * gst_mpd_segment_url_node_new (void);
void gst_mpd_segment_url_node_free (GstMPDSegmentURLNode* self);
GstMPDSegmentURLNode *gst_mpd_segment_url_node_clone (GstMPDSegmentURLNode * seg_url);
G_END_DECLS
#endif /* __GSTMPDSEGMENTURLNODE_H__ */

View file

@ -0,0 +1,92 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSNode2, gst_mpd_s_node, GST_TYPE_MPD_NODE);
/* Base class */
static xmlNodePtr
gst_mpd_s_get_xml_node (GstMPDNode * node)
{
xmlNodePtr s_xml_node = NULL;
GstMPDSNode *self = GST_MPD_S_NODE (node);
s_xml_node = xmlNewNode (NULL, (xmlChar *) "S");
if (self->t)
gst_xml_helper_set_prop_uint64 (s_xml_node, "t", self->t);
if (self->d)
gst_xml_helper_set_prop_uint64 (s_xml_node, "d", self->d);
if (self->r)
gst_xml_helper_set_prop_int (s_xml_node, "r", self->r);
return s_xml_node;
}
static void
gst_mpd_s_node_class_init (GstMPDSNodeClass * klass)
{
GstMPDNodeClass *m_klass;
m_klass = GST_MPD_NODE_CLASS (klass);
m_klass->get_xml_node = gst_mpd_s_get_xml_node;
}
static void
gst_mpd_s_node_init (GstMPDSNode * self)
{
self->t = 0;
self->d = 0;
self->r = 0;
}
GstMPDSNode *
gst_mpd_s_node_new (void)
{
return g_object_new (GST_TYPE_MPD_S_NODE, NULL);
}
void
gst_mpd_s_node_free (GstMPDSNode * self)
{
if (self)
gst_object_unref (self);
}
GstMPDSNode *
gst_mpd_s_node_clone (GstMPDSNode * s_node)
{
GstMPDSNode *clone = NULL;
if (s_node) {
clone = gst_mpd_s_node_new ();
clone->t = s_node->t;
clone->d = s_node->d;
clone->r = s_node->r;
}
return clone;
}

View file

@ -0,0 +1,50 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSNODE_H__
#define __GSTMPDSNODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_S_NODE gst_mpd_s_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSNode2, gst_mpd_s_node, GST, MPD_S_NODE, GstMPDNode)
typedef GstMPDSNode2 GstMPDSNode;
typedef GstMPDSNode2Class GstMPDSNodeClass;
struct _GstMPDSNode2
{
GstObject parent_instance;
guint64 t;
guint64 d;
gint r;
};
GstMPDSNode * gst_mpd_s_node_new (void);
void gst_mpd_s_node_free (GstMPDSNode* self);
GstMPDSNode *gst_mpd_s_node_clone (GstMPDSNode * pointer);
G_END_DECLS
#endif /* __GSTMPDSNODE_H__ */

View file

@ -0,0 +1,109 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsubrepresentationnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSubRepresentationNode2, gst_mpd_sub_representation_node,
GST_TYPE_MPD_REPRESENTATION_BASE_NODE);
/* GObject VMethods */
static void
gst_mpd_sub_representation_node_finalize (GObject * object)
{
GstMPDSubRepresentationNode *self = GST_MPD_SUB_REPRESENTATION_NODE (object);
if (self->dependencyLevel)
xmlFree (self->dependencyLevel);
g_strfreev (self->contentComponent);
G_OBJECT_CLASS (gst_mpd_sub_representation_node_parent_class)->finalize
(object);
}
/* Base class */
static xmlNodePtr
gst_mpd_sub_representation_get_xml_node (GstMPDNode * node)
{
gchar *value = NULL;
xmlNodePtr sub_representation_xml_node = NULL;
GstMPDSubRepresentationNode *self = GST_MPD_SUB_REPRESENTATION_NODE (node);
sub_representation_xml_node =
xmlNewNode (NULL, (xmlChar *) "SubRepresentation");
gst_xml_helper_set_prop_uint (sub_representation_xml_node, "level",
self->level);
gst_xml_helper_set_prop_uint_vector_type (sub_representation_xml_node,
"dependencyLevel", self->dependencyLevel, self->dependencyLevel_size);
gst_xml_helper_set_prop_uint (sub_representation_xml_node, "bandwidth",
self->level);
if (self->contentComponent) {
value = g_strjoinv (" ", self->contentComponent);
gst_xml_helper_set_prop_string (sub_representation_xml_node,
"contentComponent", value);
g_free (value);
}
return sub_representation_xml_node;
}
static void
gst_mpd_sub_representation_node_class_init (GstMPDSubRepresentationNodeClass *
klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_sub_representation_node_finalize;
m_klass->get_xml_node = gst_mpd_sub_representation_get_xml_node;
}
static void
gst_mpd_sub_representation_node_init (GstMPDSubRepresentationNode * self)
{
self->level = 0;
self->dependencyLevel = NULL;
self->dependencyLevel_size = 0;
self->bandwidth = 0;
self->contentComponent = NULL;
}
GstMPDSubRepresentationNode *
gst_mpd_sub_representation_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SUB_REPRESENTATION_NODE, NULL);
}
void
gst_mpd_sub_representation_node_free (GstMPDSubRepresentationNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,52 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSUBREPRESENTATIONNODE_H__
#define __GSTMPDSUBREPRESENTATIONNODE_H__
#include <gst/gst.h>
#include "gstmpdhelper.h"
#include "gstmpdrepresentationbasenode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SUB_REPRESENTATION_NODE gst_mpd_sub_representation_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSubRepresentationNode2, gst_mpd_sub_representation_node, GST, MPD_SUB_REPRESENTATION_NODE, GstMPDRepresentationBaseNode)
typedef GstMPDSubRepresentationNode2 GstMPDSubRepresentationNode;
typedef GstMPDSubRepresentationNode2Class GstMPDSubRepresentationNodeClass;
struct _GstMPDSubRepresentationNode2
{
GstMPDRepresentationBaseNode parent_instance;
/* RepresentationBase extension */
guint level;
guint *dependencyLevel; /* UIntVectorType */
guint dependencyLevel_size; /* size of "dependencyLevel" array */
guint bandwidth;
gchar **contentComponent; /* StringVectorType */
};
GstMPDSubRepresentationNode * gst_mpd_sub_representation_node_new (void);
void gst_mpd_sub_representation_node_free (GstMPDSubRepresentationNode* self);
G_END_DECLS
#endif /* __GSTMPDSUBREPRESENTATIONNODE_H__ */

View file

@ -0,0 +1,88 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdsubsetnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDSubsetNode2, gst_mpd_subset_node, GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_subset_node_finalize (GObject * object)
{
GstMPDSubsetNode *self = GST_MPD_SUBSET_NODE (object);
if (self->contains)
xmlFree (self->contains);
G_OBJECT_CLASS (gst_mpd_subset_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_subset_get_xml_node (GstMPDNode * node)
{
xmlNodePtr subset_xml_node = NULL;
GstMPDSubsetNode *self = GST_MPD_SUBSET_NODE (node);
subset_xml_node = xmlNewNode (NULL, (xmlChar *) "Subset");
if (self->contains)
gst_xml_helper_set_prop_uint_vector_type (subset_xml_node, "contains",
self->contains, self->contains_size);
return subset_xml_node;
}
static void
gst_mpd_subset_node_class_init (GstMPDSubsetNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_subset_node_finalize;
m_klass->get_xml_node = gst_mpd_subset_get_xml_node;
}
static void
gst_mpd_subset_node_init (GstMPDSubsetNode * self)
{
self->contains_size = 0;
self->contains = NULL;
}
GstMPDSubsetNode *
gst_mpd_subset_node_new (void)
{
return g_object_new (GST_TYPE_MPD_SUBSET_NODE, NULL);
}
void
gst_mpd_subset_node_free (GstMPDSubsetNode * self)
{
if (self)
gst_object_unref (self);
}

View file

@ -0,0 +1,52 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDSUBSETNODE_H__
#define __GSTMPDSUBSETNODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_SUBSET_NODE gst_mpd_subset_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDSubsetNode2, gst_mpd_subset_node, GST, MPD_SUBSET_NODE, GstMPDNode)
typedef GstMPDSubsetNode2 GstMPDSubsetNode;
typedef GstMPDSubsetNode2Class GstMPDSubsetNodeClass;
struct _GstMPDSubsetNode2
{
GstObject parent_instance;
guint *contains; /* UIntVectorType */
guint contains_size; /* size of the "contains" array */
};
struct _GstMPDSubsetNodeClass {
GstMPDNodeClass parent_class;
};
GstMPDSubsetNode * gst_mpd_subset_node_new (void);
void gst_mpd_subset_node_free (GstMPDSubsetNode* self);
G_END_DECLS
#endif /* __GSTMPDSUBSETNODE_H__ */

View file

@ -0,0 +1,110 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdurltypenode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDURLTypeNode2, gst_mpd_url_type_node, GST_TYPE_MPD_NODE);
/* GObject VMethods */
static void
gst_mpd_url_type_node_finalize (GObject * object)
{
GstMPDURLTypeNode *self = GST_MPD_URL_TYPE_NODE (object);
if (self->sourceURL)
xmlFree (self->sourceURL);
g_slice_free (GstXMLRange, self->range);
g_free (self->node_name);
G_OBJECT_CLASS (gst_mpd_url_type_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_url_type_get_xml_node (GstMPDNode * node)
{
xmlNodePtr url_type_xml_node = NULL;
GstMPDURLTypeNode *self = GST_MPD_URL_TYPE_NODE (node);
url_type_xml_node = xmlNewNode (NULL, (xmlChar *) self->node_name);
gst_xml_helper_set_prop_string (url_type_xml_node, "sourceURL",
self->sourceURL);
gst_xml_helper_set_prop_range (url_type_xml_node, "range", self->range);
return url_type_xml_node;
}
static void
gst_mpd_url_type_node_class_init (GstMPDURLTypeNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_url_type_node_finalize;
m_klass->get_xml_node = gst_mpd_url_type_get_xml_node;
}
static void
gst_mpd_url_type_node_init (GstMPDURLTypeNode * self)
{
self->node_name = NULL;
self->sourceURL = NULL;
self->range = NULL;
}
GstMPDURLTypeNode *
gst_mpd_url_type_node_new (const gchar * name)
{
GstMPDURLTypeNode *self = g_object_new (GST_TYPE_MPD_URL_TYPE_NODE, NULL);
self->node_name = g_strdup (name);
return self;
}
void
gst_mpd_url_type_node_free (GstMPDURLTypeNode * self)
{
if (self)
gst_object_unref (self);
}
GstMPDURLTypeNode *
gst_mpd_url_type_node_clone (GstMPDURLTypeNode * url)
{
GstMPDURLTypeNode *clone = NULL;
if (url) {
clone = gst_mpd_url_type_node_new (url->node_name);
if (url->sourceURL) {
clone->sourceURL = xmlMemStrdup (url->sourceURL);
}
clone->range = gst_xml_helper_clone_range (url->range);
}
return clone;
}

View file

@ -0,0 +1,50 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDURLTYPENODE_H__
#define __GSTMPDURLTYPENODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_URL_TYPE_NODE gst_mpd_url_type_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDURLTypeNode2, gst_mpd_url_type_node, GST, MPD_URL_TYPE_NODE, GstMPDNode)
typedef GstMPDURLTypeNode2 GstMPDURLTypeNode;
typedef GstMPDURLTypeNode2Class GstMPDURLTypeNodeClass;
struct _GstMPDURLTypeNode2
{
GstObject parent_instance;
gchar* node_name;
gchar *sourceURL;
GstXMLRange *range;
};
GstMPDURLTypeNode * gst_mpd_url_type_node_new (const gchar* name);
void gst_mpd_url_type_node_free (GstMPDURLTypeNode* self);
GstMPDURLTypeNode *gst_mpd_url_type_node_clone (GstMPDURLTypeNode * url);
G_END_DECLS
#endif /* __GSTMPDURLTYPENODE_H__ */

View file

@ -0,0 +1,140 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gstmpdutctimingnode.h"
#include "gstmpdparser.h"
G_DEFINE_TYPE (GstMPDUTCTimingNode2, gst_mpd_utctiming_node, GST_TYPE_MPD_NODE);
static const struct GstMPDUTCTimingMethod gst_mpd_utctiming_methods[] = {
{"urn:mpeg:dash:utc:ntp:2014", GST_MPD_UTCTIMING_TYPE_NTP},
{"urn:mpeg:dash:utc:sntp:2014", GST_MPD_UTCTIMING_TYPE_SNTP},
{"urn:mpeg:dash:utc:http-head:2014", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
{"urn:mpeg:dash:utc:http-xsdate:2014", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
{"urn:mpeg:dash:utc:http-iso:2014", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
{"urn:mpeg:dash:utc:http-ntp:2014", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
{"urn:mpeg:dash:utc:direct:2014", GST_MPD_UTCTIMING_TYPE_DIRECT},
/*
* Early working drafts used the :2012 namespace and this namespace is
* used by some DASH packagers. To work-around these packagers, we also
* accept the early draft scheme names.
*/
{"urn:mpeg:dash:utc:ntp:2012", GST_MPD_UTCTIMING_TYPE_NTP},
{"urn:mpeg:dash:utc:sntp:2012", GST_MPD_UTCTIMING_TYPE_SNTP},
{"urn:mpeg:dash:utc:http-head:2012", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
{"urn:mpeg:dash:utc:http-xsdate:2012", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
{"urn:mpeg:dash:utc:http-iso:2012", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
{"urn:mpeg:dash:utc:http-ntp:2012", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
{"urn:mpeg:dash:utc:direct:2012", GST_MPD_UTCTIMING_TYPE_DIRECT},
{NULL, 0}
};
/* GObject VMethods */
static void
gst_mpd_utctiming_node_finalize (GObject * object)
{
GstMPDUTCTimingNode *self = GST_MPD_UTCTIMING_NODE (object);
g_strfreev (self->urls);
G_OBJECT_CLASS (gst_mpd_utctiming_node_parent_class)->finalize (object);
}
/* Base class */
static xmlNodePtr
gst_mpd_utc_timing_get_xml_node (GstMPDNode * node)
{
xmlNodePtr utc_timing_xml_node = NULL;
gchar *value = NULL;
GstMPDUTCTimingNode *self = GST_MPD_UTCTIMING_NODE (node);
utc_timing_xml_node = xmlNewNode (NULL, (xmlChar *) "UTCTiming");
if (self->method) {
gst_xml_helper_set_prop_string (utc_timing_xml_node, "schemeiduri",
(gchar *) gst_mpd_utctiming_get_scheme_id_uri (self->method));
}
if (self->urls) {
value = g_strjoinv (" ", self->urls);
gst_xml_helper_set_prop_string (utc_timing_xml_node, "value", value);
g_free (value);
}
return utc_timing_xml_node;
}
static void
gst_mpd_utctiming_node_class_init (GstMPDUTCTimingNodeClass * klass)
{
GObjectClass *object_class;
GstMPDNodeClass *m_klass;
object_class = G_OBJECT_CLASS (klass);
m_klass = GST_MPD_NODE_CLASS (klass);
object_class->finalize = gst_mpd_utctiming_node_finalize;
m_klass->get_xml_node = gst_mpd_utc_timing_get_xml_node;
}
static void
gst_mpd_utctiming_node_init (GstMPDUTCTimingNode * self)
{
self->method = 0;
self->urls = NULL;
}
GstMPDUTCTimingNode *
gst_mpd_utctiming_node_new (void)
{
return g_object_new (GST_TYPE_MPD_UTCTIMING_NODE, NULL);
}
void
gst_mpd_utctiming_node_free (GstMPDUTCTimingNode * self)
{
if (self)
gst_object_unref (self);
}
const gchar *
gst_mpd_utctiming_get_scheme_id_uri (GstMPDUTCTimingType type)
{
int i;
for (i = 0; gst_mpd_utctiming_methods[i].name; ++i) {
if (type == gst_mpd_utctiming_methods[i].method)
return gst_mpd_utctiming_methods[i].name;
}
return NULL;
}
GstMPDUTCTimingType
gst_mpd_utctiming_get_method (gchar * schemeIDURI)
{
int i;
for (i = 0; gst_mpd_utctiming_methods[i].name; ++i) {
if (g_ascii_strncasecmp (gst_mpd_utctiming_methods[i].name,
schemeIDURI, strlen (gst_mpd_utctiming_methods[i].name)) == 0)
return gst_mpd_utctiming_methods[i].method;
}
return GST_MPD_UTCTIMING_TYPE_UNKNOWN;
}

View file

@ -0,0 +1,71 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GSTMPDUTCTIMINGNODE_H__
#define __GSTMPDUTCTIMINGNODE_H__
#include <gst/gst.h>
#include "gstmpdnode.h"
G_BEGIN_DECLS
#define GST_TYPE_MPD_UTCTIMING_NODE gst_mpd_utctiming_node_get_type ()
G_DECLARE_FINAL_TYPE (GstMPDUTCTimingNode2, gst_mpd_utctiming_node, GST, MPD_UTCTIMING_NODE, GstMPDNode)
typedef GstMPDUTCTimingNode2 GstMPDUTCTimingNode;
typedef GstMPDUTCTimingNode2Class GstMPDUTCTimingNodeClass;
typedef enum
{
GST_MPD_UTCTIMING_TYPE_UNKNOWN = 0x00,
GST_MPD_UTCTIMING_TYPE_NTP = 0x01,
GST_MPD_UTCTIMING_TYPE_SNTP = 0x02,
GST_MPD_UTCTIMING_TYPE_HTTP_HEAD = 0x04,
GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE = 0x08,
GST_MPD_UTCTIMING_TYPE_HTTP_ISO = 0x10,
GST_MPD_UTCTIMING_TYPE_HTTP_NTP = 0x20,
GST_MPD_UTCTIMING_TYPE_DIRECT = 0x40
} GstMPDUTCTimingType;
struct GstMPDUTCTimingMethod
{
const gchar *name;
GstMPDUTCTimingType method;
};
struct _GstMPDUTCTimingNode2
{
GstObject parent_instance;
GstMPDUTCTimingType method;
/* NULL terminated array of strings */
gchar **urls;
/* TODO add missing fields such as weight etc.*/
};
GstMPDUTCTimingNode * gst_mpd_utctiming_node_new (void);
void gst_mpd_utctiming_node_free (GstMPDUTCTimingNode* self);
const gchar* gst_mpd_utctiming_get_scheme_id_uri (GstMPDUTCTimingType type);
GstMPDUTCTimingType gst_mpd_utctiming_get_method (gchar* schemeIDURI);
G_END_DECLS
#endif /* __GSTMPDUTCTIMINGNODE_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
/* GStreamer
*
* Copyright (C) 2019 Collabora Ltd.
* Author: Stéphane Cerveau <scerveau@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_XMLHELPER_H__
#define __GST_XMLHELPER_H__
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <gst/gst.h>
#include "gstdash_debug.h"
G_BEGIN_DECLS
typedef struct _GstXMLRange GstXMLRange;
typedef struct _GstXMLRatio GstXMLRatio;
typedef struct _GstXMLFrameRate GstXMLFrameRate;
typedef struct _GstXMLConditionalUintType GstXMLConditionalUintType;
struct _GstXMLRange
{
guint64 first_byte_pos;
guint64 last_byte_pos;
};
struct _GstXMLRatio
{
guint num;
guint den;
};
struct _GstXMLFrameRate
{
guint num;
guint den;
};
struct _GstXMLConditionalUintType
{
gboolean flag;
guint value;
};
GstXMLRange *gst_xml_helper_clone_range (GstXMLRange * range);
GstXMLRatio *gst_xml_helper_clone_ratio (GstXMLRatio * ratio);
GstXMLFrameRate *gst_xml_helper_clone_frame_rate (GstXMLFrameRate * frameRate);
/* XML property get method */
gboolean gst_xml_helper_get_prop_validated_string (xmlNode * a_node,
const gchar * property_name, gchar ** property_value,
gboolean (*validator) (const char *));
gboolean gst_xml_helper_get_prop_string (xmlNode * a_node,
const gchar * property_name, gchar ** property_value);
gboolean gst_xml_helper_get_prop_string_stripped (xmlNode * a_node,
const gchar * property_name, gchar ** property_value);
gboolean gst_xml_helper_get_ns_prop_string (xmlNode * a_node,
const gchar * ns_name, const gchar * property_name,
gchar ** property_value);
gboolean gst_xml_helper_get_prop_string_vector_type (xmlNode * a_node,
const gchar * property_name, gchar *** property_value);
gboolean gst_xml_helper_get_prop_signed_integer (xmlNode * a_node,
const gchar * property_name, gint default_val, gint * property_value);
gboolean gst_xml_helper_get_prop_unsigned_integer (xmlNode * a_node,
const gchar * property_name, guint default_val, guint * property_value);
gboolean gst_xml_helper_get_prop_unsigned_integer_64 (xmlNode *
a_node, const gchar * property_name, guint64 default_val,
guint64 * property_value);
gboolean gst_xml_helper_get_prop_uint_vector_type (xmlNode * a_node,
const gchar * property_name, guint ** property_value, guint * value_size);
gboolean gst_xml_helper_get_prop_double (xmlNode * a_node,
const gchar * property_name, gdouble * property_value);
gboolean gst_xml_helper_get_prop_boolean (xmlNode * a_node,
const gchar * property_name, gboolean default_val,
gboolean * property_value);
gboolean gst_xml_helper_get_prop_range (xmlNode * a_node,
const gchar * property_name, GstXMLRange ** property_value);
gboolean gst_xml_helper_get_prop_ratio (xmlNode * a_node,
const gchar * property_name, GstXMLRatio ** property_value);
gboolean gst_xml_helper_get_prop_framerate (xmlNode * a_node,
const gchar * property_name, GstXMLFrameRate ** property_value);
gboolean gst_xml_helper_get_prop_cond_uint (xmlNode * a_node,
const gchar * property_name, GstXMLConditionalUintType ** property_value);
gboolean gst_xml_helper_get_prop_dateTime (xmlNode * a_node,
const gchar * property_name, GstDateTime ** property_value);
gboolean gst_xml_helper_get_prop_duration (xmlNode * a_node,
const gchar * property_name, guint64 default_value,
guint64 * property_value);
gboolean gst_xml_helper_get_prop_string_no_whitespace (xmlNode * a_node,
const gchar * property_name, gchar ** property_value);
/* XML node get method */
gboolean gst_xml_helper_get_node_content (xmlNode * a_node,
gchar ** content);
gchar *gst_xml_helper_get_node_namespace (xmlNode * a_node,
const gchar * prefix);
gboolean gst_xml_helper_get_node_as_string (xmlNode * a_node,
gchar ** content);
/* XML property set method */
void gst_xml_helper_set_prop_string (xmlNodePtr node, const gchar * name, gchar* value);
void gst_xml_helper_set_prop_boolean (xmlNodePtr node, const gchar * name, gboolean value);
void gst_xml_helper_set_prop_int (xmlNodePtr root, const gchar * name, gint value);
void gst_xml_helper_set_prop_uint (xmlNodePtr root, const gchar * name, guint value);
void gst_xml_helper_set_prop_int64 (xmlNodePtr node, const gchar * name, gint64 value);
void gst_xml_helper_set_prop_uint64 (xmlNodePtr node, const gchar * name, guint64 value);
void gst_xml_helper_set_prop_uint_vector_type (xmlNode * a_node, const gchar * name, guint * value, guint value_size);
void gst_xml_helper_set_prop_double (xmlNodePtr node, const gchar * name, gdouble value);
void gst_xml_helper_set_prop_date_time (xmlNodePtr node, const gchar * name, GstDateTime* value);
void gst_xml_helper_set_prop_duration (xmlNode * node, const gchar * name, guint64 value);
void gst_xml_helper_set_prop_ratio (xmlNodePtr node, const gchar * name, GstXMLRatio* value);
void gst_xml_helper_set_prop_framerate (xmlNodePtr node, const gchar * name, GstXMLFrameRate* value);
void gst_xml_helper_set_prop_range (xmlNodePtr node, const gchar * name, GstXMLRange* value);
void gst_xml_helper_set_prop_cond_uint (xmlNode * a_node, const gchar * property_name, GstXMLConditionalUintType * cond);
void gst_xml_helper_set_content (xmlNodePtr node, gchar * content);
G_END_DECLS
#endif /* __GST_XMLHELPER_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
/* GStreamer
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* downloadhelper.h:
*
* 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.
*
* Youshould have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <glib.h>
#include "gstadaptivedemuxutils.h"
#include "downloadrequest.h"
#ifndef __DOWNLOADHELPER_H__
#define __DOWNLOADHELPER_H__
G_BEGIN_DECLS typedef struct DownloadHelper DownloadHelper;
typedef enum DownloadFlags DownloadFlags;
#define HTTP_STATUS_IS_SUCCESSFUL(s) ((s) >= 200 && (s) < 300)
enum DownloadFlags
{
DOWNLOAD_FLAG_NONE = 0,
DOWNLOAD_FLAG_COMPRESS = (1 << 0),
DOWNLOAD_FLAG_FORCE_REFRESH = (1 << 1),
DOWNLOAD_FLAG_HEADERS_ONLY = (1 << 2),
DOWNLOAD_FLAG_BLOCKING = (1 << 3),
};
DownloadHelper *downloadhelper_new (GstAdaptiveDemuxClock *clock);
gboolean downloadhelper_start (DownloadHelper * dh);
void downloadhelper_stop (DownloadHelper * dh);
void downloadhelper_free (DownloadHelper * dh);
void downloadhelper_set_referer (DownloadHelper * dh, const gchar *referer);
void downloadhelper_set_user_agent (DownloadHelper * dh, const gchar *user_agent);
void downloadhelper_set_cookies (DownloadHelper * dh, gchar **cookies);
gboolean downloadhelper_submit_request (DownloadHelper * dh,
const gchar * referer, DownloadFlags flags, DownloadRequest * request,
GError ** err);
void downloadhelper_cancel_request (DownloadHelper * dh, DownloadRequest *request);
DownloadRequest *downloadhelper_fetch_uri (DownloadHelper * dh, const gchar * uri,
const gchar * referer, DownloadFlags flags, GError ** err);
DownloadRequest *downloadhelper_fetch_uri_range (DownloadHelper * dh,
const gchar * uri, const gchar * referer, DownloadFlags flags,
gint64 range_start, gint64 range_end, GError ** err);
G_END_DECLS
#endif

View file

@ -0,0 +1,396 @@
/* GStreamer
* Copyright (C) 2011 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* gstrequest.c:
*
* 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/base/gsttypefindhelper.h>
#include <gst/base/gstadapter.h>
#include "downloadrequest.h"
typedef struct _DownloadRequestPrivate DownloadRequestPrivate;
struct _DownloadRequestPrivate
{
DownloadRequest request;
GstBuffer *buffer;
GstCaps *caps;
GRecMutex lock;
DownloadRequestEventCallback completion_cb;
DownloadRequestEventCallback cancellation_cb;
DownloadRequestEventCallback error_cb;
DownloadRequestEventCallback progress_cb;
void *cb_data;
};
#define DOWNLOAD_REQUEST_PRIVATE(frag) ((DownloadRequestPrivate *)(frag))
static void download_request_free (DownloadRequest * request);
DownloadRequest *
download_request_new (void)
{
DownloadRequest *request =
(DownloadRequest *) g_slice_new0 (DownloadRequestPrivate);
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_atomic_int_set (&request->ref_count, 1);
g_rec_mutex_init (&priv->lock);
priv->buffer = NULL;
request->state = DOWNLOAD_REQUEST_STATE_UNSENT;
request->status_code = 0;
request->download_request_time = GST_CLOCK_TIME_NONE;
request->download_start_time = GST_CLOCK_TIME_NONE;
request->download_end_time = GST_CLOCK_TIME_NONE;
request->headers = NULL;
return (DownloadRequest *) (request);
}
DownloadRequest *
download_request_new_uri (const gchar * uri)
{
DownloadRequest *request = download_request_new ();
request->uri = g_strdup (uri);
request->range_start = 0;
request->range_end = -1;
return request;
}
DownloadRequest *
download_request_new_uri_range (const gchar * uri, gint64 range_start,
gint64 range_end)
{
DownloadRequest *request = download_request_new ();
request->uri = g_strdup (uri);
request->range_start = range_start;
request->range_end = range_end;
return request;
}
static void
download_request_free (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_free (request->uri);
g_free (request->redirect_uri);
if (request->headers) {
gst_structure_free (request->headers);
request->headers = NULL;
}
if (priv->buffer != NULL) {
gst_buffer_unref (priv->buffer);
priv->buffer = NULL;
}
if (priv->caps != NULL) {
gst_caps_unref (priv->caps);
priv->caps = NULL;
}
g_rec_mutex_clear (&priv->lock);
g_slice_free1 (sizeof (DownloadRequestPrivate), priv);
}
void
download_request_set_callbacks (DownloadRequest * request,
DownloadRequestEventCallback on_completion,
DownloadRequestEventCallback on_error,
DownloadRequestEventCallback on_cancellation,
DownloadRequestEventCallback on_progress, void *cb_data)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_rec_mutex_lock (&priv->lock);
priv->completion_cb = on_completion;
priv->error_cb = on_error;
priv->cancellation_cb = on_cancellation;
priv->progress_cb = on_progress;
priv->cb_data = cb_data;
request->send_progress = (on_progress != NULL);
g_rec_mutex_unlock (&priv->lock);
}
/* Called with request lock held */
void
download_request_despatch_progresss (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
if (priv->progress_cb)
priv->progress_cb (request, request->state, priv->cb_data);
}
/* Called with request lock held */
void
download_request_despatch_completion (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
switch (request->state) {
case DOWNLOAD_REQUEST_STATE_UNSENT:
if (priv->cancellation_cb)
priv->cancellation_cb (request, request->state, priv->cb_data);
break;
case DOWNLOAD_REQUEST_STATE_COMPLETE:
if (priv->completion_cb)
priv->completion_cb (request, request->state, priv->cb_data);
break;
case DOWNLOAD_REQUEST_STATE_ERROR:
if (priv->error_cb)
priv->error_cb (request, request->state, priv->cb_data);
break;
default:
g_assert_not_reached ();
}
}
DownloadRequest *
download_request_ref (DownloadRequest * request)
{
g_return_val_if_fail (request != NULL, NULL);
g_atomic_int_inc (&request->ref_count);
return request;
}
void
download_request_unref (DownloadRequest * request)
{
g_return_if_fail (request != NULL);
if (g_atomic_int_dec_and_test (&request->ref_count)) {
download_request_free (request);
}
}
void
download_request_lock (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_rec_mutex_lock (&priv->lock);
}
void
download_request_unlock (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_rec_mutex_unlock (&priv->lock);
}
GstBuffer *
download_request_take_buffer (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
GstBuffer *buffer = NULL;
g_return_val_if_fail (request != NULL, NULL);
g_rec_mutex_lock (&priv->lock);
if (request->state != DOWNLOAD_REQUEST_STATE_LOADING
&& request->state != DOWNLOAD_REQUEST_STATE_COMPLETE) {
g_rec_mutex_unlock (&priv->lock);
return NULL;
}
buffer = priv->buffer;
priv->buffer = NULL;
g_rec_mutex_unlock (&priv->lock);
return buffer;
}
void
download_request_set_uri (DownloadRequest * request, const gchar * uri,
gint64 range_start, gint64 range_end)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_rec_mutex_lock (&priv->lock);
g_assert (request->in_use == FALSE);
if (request->uri != uri) {
g_free (request->uri);
request->uri = g_strdup (uri);
}
g_free (request->redirect_uri);
request->redirect_uri = NULL;
request->redirect_permanent = FALSE;
request->range_start = range_start;
request->range_end = range_end;
g_rec_mutex_unlock (&priv->lock);
}
void
download_request_reset (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_rec_mutex_lock (&priv->lock);
g_assert (request->in_use == FALSE);
request->state = DOWNLOAD_REQUEST_STATE_UNSENT;
if (request->headers) {
gst_structure_free (request->headers);
request->headers = NULL;
}
if (priv->buffer != NULL) {
gst_buffer_unref (priv->buffer);
priv->buffer = NULL;
}
if (priv->caps != NULL) {
gst_caps_unref (priv->caps);
priv->caps = NULL;
}
g_rec_mutex_unlock (&priv->lock);
}
/* Called when the request is submitted, to clear any settings from a previous
* download */
void
download_request_begin_download (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_return_if_fail (request != NULL);
g_rec_mutex_lock (&priv->lock);
if (priv->buffer != NULL) {
gst_buffer_unref (priv->buffer);
priv->buffer = NULL;
}
if (request->headers) {
gst_structure_free (request->headers);
request->headers = NULL;
}
if (priv->caps != NULL) {
gst_caps_unref (priv->caps);
priv->caps = NULL;
}
request->content_length = 0;
request->content_received = 0;
request->download_request_time = GST_CLOCK_TIME_NONE;
request->download_start_time = GST_CLOCK_TIME_NONE;
request->download_end_time = GST_CLOCK_TIME_NONE;
g_rec_mutex_unlock (&priv->lock);
}
void
download_request_set_caps (DownloadRequest * request, GstCaps * caps)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_return_if_fail (request != NULL);
g_rec_mutex_lock (&priv->lock);
gst_caps_replace (&priv->caps, caps);
g_rec_mutex_unlock (&priv->lock);
}
GstCaps *
download_request_get_caps (DownloadRequest * request)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
GstCaps *caps;
g_return_val_if_fail (request != NULL, NULL);
if (request->state != DOWNLOAD_REQUEST_STATE_LOADING
&& request->state != DOWNLOAD_REQUEST_STATE_COMPLETE)
return NULL;
g_rec_mutex_lock (&priv->lock);
if (priv->caps == NULL) {
guint64 offset, offset_end;
/* FIXME: This is currently necessary as typefinding only
* works with 0 offsets... need to find a better way to
* do that */
offset = GST_BUFFER_OFFSET (priv->buffer);
offset_end = GST_BUFFER_OFFSET_END (priv->buffer);
GST_BUFFER_OFFSET (priv->buffer) = GST_BUFFER_OFFSET_NONE;
GST_BUFFER_OFFSET_END (priv->buffer) = GST_BUFFER_OFFSET_NONE;
priv->caps = gst_type_find_helper_for_buffer (NULL, priv->buffer, NULL);
GST_BUFFER_OFFSET (priv->buffer) = offset;
GST_BUFFER_OFFSET_END (priv->buffer) = offset_end;
}
caps = gst_caps_ref (priv->caps);
g_rec_mutex_unlock (&priv->lock);
return caps;
}
gboolean
download_request_add_buffer (DownloadRequest * request, GstBuffer * buffer)
{
DownloadRequestPrivate *priv = DOWNLOAD_REQUEST_PRIVATE (request);
g_return_val_if_fail (request != NULL, FALSE);
g_return_val_if_fail (buffer != NULL, FALSE);
if (request->state == DOWNLOAD_REQUEST_STATE_COMPLETE) {
GST_WARNING ("Download request is completed, could not add more buffers");
gst_buffer_unref (buffer);
return FALSE;
}
GST_DEBUG ("Adding new buffer %" GST_PTR_FORMAT " to the request data",
buffer);
request->content_received += gst_buffer_get_size (buffer);
/* We steal the buffers you pass in */
if (priv->buffer == NULL)
priv->buffer = buffer;
else
priv->buffer = gst_buffer_append (priv->buffer, buffer);
return TRUE;
}

View file

@ -0,0 +1,110 @@
/* GStreamer
* Copyright (C) 2011 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* downloadrequest.h:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __DOWNLOAD_REQUEST_H__
#define __DOWNLOAD_REQUEST_H__
#include <gst/gst.h>
G_BEGIN_DECLS
#define DOWNLOAD_REQUEST(obj) ((DownloadRequest *)(obj))
typedef struct _DownloadRequest DownloadRequest;
typedef enum _DownloadRequestState DownloadRequestState;
typedef void (*DownloadRequestEventCallback) (DownloadRequest *request, DownloadRequestState state, void *cb_data);
enum _DownloadRequestState {
DOWNLOAD_REQUEST_STATE_UNSENT,
DOWNLOAD_REQUEST_STATE_OPEN, /* Request sent, but no response yet */
DOWNLOAD_REQUEST_STATE_HEADERS_RECEIVED, /* Response headers received, awaiting body */
DOWNLOAD_REQUEST_STATE_LOADING, /* Content loading in progress */
DOWNLOAD_REQUEST_STATE_COMPLETE, /* Request processing finished successfully - check status_code for completion 200-399 codes */
DOWNLOAD_REQUEST_STATE_ERROR, /* Request generated an http error - check status_code */
};
struct _DownloadRequest
{
gint ref_count;
gboolean in_use; /* TRUE if this request is being serviced */
gboolean send_progress; /* TRUE if this request wants progress events */
DownloadRequestState state;
guint status_code; /* HTTP status code */
/* Request parameters */
gchar * uri; /* URI of the request */
gint64 range_start;
gint64 range_end;
/* possibly populated during a download */
gchar * redirect_uri; /* Redirect target if any */
gboolean redirect_permanent; /* If the redirect is permanent */
GstStructure *headers; /* HTTP request/response headers */
guint64 content_length; /* Response content length, if known (or 0) */
guint64 content_received; /* Response content received so far */
guint64 download_request_time; /* Epoch time when the download started */
guint64 download_start_time; /* Epoch time when the first data for the download arrived */
guint64 download_end_time; /* Epoch time when the download finished */
};
void download_request_set_uri (DownloadRequest *request, const gchar *uri,
gint64 range_start, gint64 range_end);
/* Reset the request state back to UNSENT and clear any stored info. The request must not be in use */
void download_request_reset (DownloadRequest *request);
void download_request_begin_download (DownloadRequest *request);
void download_request_set_caps (DownloadRequest * request, GstCaps * caps);
GstCaps * download_request_get_caps (DownloadRequest * request);
gboolean download_request_add_buffer (DownloadRequest *request, GstBuffer *buffer);
GstBuffer * download_request_take_buffer (DownloadRequest *request);
DownloadRequest * download_request_new (void);
DownloadRequest * download_request_new_uri (const gchar * uri);
DownloadRequest * download_request_new_uri_range (const gchar * uri, gint64 range_start, gint64 range_end);
void download_request_set_callbacks (DownloadRequest *request,
DownloadRequestEventCallback on_completion,
DownloadRequestEventCallback on_error,
DownloadRequestEventCallback on_cancellation,
DownloadRequestEventCallback on_progress,
void *cb_data);
DownloadRequest *download_request_ref (DownloadRequest *request);
void download_request_unref (DownloadRequest *request);
void download_request_lock (DownloadRequest *request);
void download_request_unlock (DownloadRequest *request);
void download_request_despatch_progresss (DownloadRequest *request);
void download_request_despatch_completion (DownloadRequest *request);
G_END_DECLS
#endif /* __DOWNLOAD_REQUEST_H__ */

View file

@ -0,0 +1,286 @@
/* GStreamer
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* 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 "gstadaptivedemux.h"
#include "gstadaptivedemux-private.h"
GST_DEBUG_CATEGORY_EXTERN (adaptivedemux2_debug);
#define GST_CAT_DEFAULT adaptivedemux2_debug
GstAdaptiveDemuxPeriod *
gst_adaptive_demux_period_new (GstAdaptiveDemux * demux)
{
GstAdaptiveDemuxPeriod *period;
period = g_new0 (GstAdaptiveDemuxPeriod, 1);
g_atomic_int_set (&period->ref_count, 1);
period->demux = demux;
period->period_num = demux->priv->n_periods++;
g_queue_push_tail (demux->priv->periods, period);
return period;
}
static void
_demux_period_free (GstAdaptiveDemuxPeriod * period)
{
/* Disable and remove all streams and tracks. */
g_list_free_full (period->streams, (GDestroyNotify) gst_object_unref);
/* Theoretically all tracks should have gone by now */
GST_DEBUG ("Disabling and removing all tracks");
g_list_free_full (period->tracks,
(GDestroyNotify) gst_adaptive_demux_track_unref);
g_free (period);
}
GstAdaptiveDemuxPeriod *
gst_adaptive_demux_period_ref (GstAdaptiveDemuxPeriod * period)
{
g_return_val_if_fail (period != NULL, NULL);
GST_TRACE ("%p %d -> %d", period, period->ref_count, period->ref_count + 1);
g_atomic_int_inc (&period->ref_count);
return period;
}
void
gst_adaptive_demux_period_unref (GstAdaptiveDemuxPeriod * period)
{
g_return_if_fail (period != NULL);
GST_TRACE ("%p %d -> %d", period, period->ref_count, period->ref_count - 1);
if (g_atomic_int_dec_and_test (&period->ref_count)) {
_demux_period_free (period);
}
}
static GstAdaptiveDemuxTrack *
default_track_for_stream_type_locked (GstAdaptiveDemuxPeriod * period,
GstStreamType stream_type)
{
GList *tmp;
GstAdaptiveDemuxTrack *res = NULL, *select = NULL;
for (tmp = period->tracks; tmp; tmp = tmp->next) {
GstAdaptiveDemuxTrack *cand = tmp->data;
if (cand->type == stream_type) {
/* If selected, we're done */
if (cand->selected)
return cand;
if (!select && cand->flags & GST_STREAM_FLAG_SELECT)
res = select = cand;
if (res == NULL)
res = cand;
}
}
return res;
}
/* called with TRACKS_LOCK taken */
void
gst_adaptive_demux_period_select_default_tracks (GstAdaptiveDemux * demux,
GstAdaptiveDemuxPeriod * period)
{
GstAdaptiveDemuxTrack *track;
gboolean changed = FALSE;
GST_DEBUG_OBJECT (demux, "Picking a default selection");
/* Do initial selection (pick default for each type) */
if ((track =
default_track_for_stream_type_locked (period,
GST_STREAM_TYPE_VIDEO))) {
GST_DEBUG_OBJECT (demux, "Selecting default video track %s",
track->stream_id);
if (!track->selected) {
changed = TRUE;
track->selected = TRUE;
gst_pad_set_active (track->sinkpad, TRUE);
}
}
if ((track =
default_track_for_stream_type_locked (period,
GST_STREAM_TYPE_AUDIO))) {
GST_DEBUG_OBJECT (demux, "Selecting default audio track %s",
track->stream_id);
if (!track->selected) {
changed = TRUE;
track->selected = TRUE;
gst_pad_set_active (track->sinkpad, TRUE);
}
}
if ((track =
default_track_for_stream_type_locked (period,
GST_STREAM_TYPE_TEXT))) {
GST_DEBUG_OBJECT (demux, "Selecting default text track %s",
track->stream_id);
if (!track->selected) {
changed = TRUE;
track->selected = TRUE;
gst_pad_set_active (track->sinkpad, TRUE);
}
}
if (changed)
g_atomic_int_set (&demux->priv->requested_selection_seqnum,
gst_util_seqnum_next ());
}
static GstAdaptiveDemuxTrack *
gst_adaptive_demux_period_find_matching_track (GstAdaptiveDemuxPeriod * period,
GstAdaptiveDemuxTrack * track)
{
GList *iter;
for (iter = period->tracks; iter; iter = iter->next) {
GstAdaptiveDemuxTrack *cand = iter->data;
if (!cand->selected && cand->type == track->type) {
/* FIXME : Improve this a *lot* */
if (!g_strcmp0 (cand->stream_id, track->stream_id))
return cand;
}
}
return NULL;
}
void
gst_adaptive_demux_period_transfer_selection (GstAdaptiveDemux * demux,
GstAdaptiveDemuxPeriod * next_period,
GstAdaptiveDemuxPeriod * current_period)
{
GList *iter;
for (iter = current_period->tracks; iter; iter = iter->next) {
GstAdaptiveDemuxTrack *track = iter->data;
if (track->selected) {
GstAdaptiveDemuxTrack *new_track =
gst_adaptive_demux_period_find_matching_track (next_period, track);
if (new_track) {
GST_DEBUG_OBJECT (demux, "Selecting replacement track %s",
new_track->stream_id);
new_track->selected = TRUE;
gst_pad_set_active (new_track->sinkpad, TRUE);
} else {
GST_WARNING_OBJECT (demux, "Could not find replacement track for %s",
track->stream_id);
/* FIXME : Pick a default for that type ? Just continue as-is ? */
}
}
}
}
/* called with TRACKS_LOCK taken */
gboolean
gst_adaptive_demux_period_add_track (GstAdaptiveDemuxPeriod * period,
GstAdaptiveDemuxTrack * track)
{
GST_LOG ("period %d track:%p", period->period_num, track);
/* Actually create and add the elements to the demuxer */
if (!gst_adaptive_demux_track_add_elements (track, period->period_num)) {
GST_ERROR ("Failed to add track");
return FALSE;
}
period->tracks =
g_list_append (period->tracks, gst_adaptive_demux_track_ref (track));
period->tracks_changed = TRUE;
return TRUE;
}
/* must be called with manifest_lock taken */
GstFlowReturn
gst_adaptive_demux_period_combine_stream_flows (GstAdaptiveDemuxPeriod * period)
{
gboolean all_notlinked = TRUE;
gboolean all_eos = TRUE;
GList *iter;
for (iter = period->streams; iter; iter = g_list_next (iter)) {
GstAdaptiveDemux2Stream *stream = iter->data;
if (stream->last_ret != GST_FLOW_NOT_LINKED) {
all_notlinked = FALSE;
if (stream->last_ret != GST_FLOW_EOS)
all_eos = FALSE;
}
if (stream->last_ret <= GST_FLOW_NOT_NEGOTIATED
|| stream->last_ret == GST_FLOW_FLUSHING) {
return stream->last_ret;
}
}
if (all_notlinked)
return GST_FLOW_NOT_LINKED;
if (all_eos)
return GST_FLOW_EOS;
return GST_FLOW_OK;
}
void
gst_adaptive_demux_period_stop_tasks (GstAdaptiveDemuxPeriod * period)
{
GList *iter;
for (iter = period->streams; iter; iter = g_list_next (iter)) {
GstAdaptiveDemux2Stream *stream = iter->data;
gst_adaptive_demux2_stream_stop (stream);
stream->download_error_count = 0;
stream->need_header = TRUE;
}
}
gboolean
gst_adaptive_demux_period_has_pending_tracks (GstAdaptiveDemuxPeriod * period)
{
GList *iter;
for (iter = period->streams; iter; iter = g_list_next (iter)) {
GstAdaptiveDemux2Stream *stream = iter->data;
if (stream->pending_tracks)
return TRUE;
}
return FALSE;
}

View file

@ -0,0 +1,238 @@
/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _GST_ADAPTIVE_DEMUX_PRIVATE_H_
#define _GST_ADAPTIVE_DEMUX_PRIVATE_H_
#include <gst/base/gstadapter.h>
#include <gst/base/gstflowcombiner.h>
#define NUM_LOOKBACK_FRAGMENTS 3
#define MAX_DOWNLOAD_ERROR_COUNT 3
/* Internal, so not using GST_FLOW_CUSTOM_SUCCESS_N */
#define GST_ADAPTIVE_DEMUX_FLOW_SWITCH (GST_FLOW_CUSTOM_SUCCESS_2 + 1)
#define TRACKS_GET_LOCK(d) (&GST_ADAPTIVE_DEMUX_CAST(d)->priv->tracks_lock)
#define TRACKS_LOCK(d) g_mutex_lock (TRACKS_GET_LOCK (d))
#define TRACKS_UNLOCK(d) g_mutex_unlock (TRACKS_GET_LOCK (d))
#define BUFFERING_GET_LOCK(d) (&GST_ADAPTIVE_DEMUX_CAST(d)->priv->buffering_lock)
#define BUFFERING_LOCK(d) g_mutex_lock (BUFFERING_GET_LOCK (d))
#define BUFFERING_UNLOCK(d) g_mutex_unlock (BUFFERING_GET_LOCK (d))
#define GST_MANIFEST_GET_LOCK(d) (&(GST_ADAPTIVE_DEMUX_CAST(d)->priv->manifest_lock))
#define GST_MANIFEST_LOCK(d) G_STMT_START { \
GST_TRACE("Locking manifest from thread %p", g_thread_self()); \
g_rec_mutex_lock (GST_MANIFEST_GET_LOCK (d)); \
GST_TRACE("Locked manifest from thread %p", g_thread_self()); \
} G_STMT_END
#define GST_MANIFEST_UNLOCK(d) G_STMT_START { \
GST_TRACE("Unlocking manifest from thread %p", g_thread_self()); \
g_rec_mutex_unlock (GST_MANIFEST_GET_LOCK (d)); \
} G_STMT_END
#define GST_ADAPTIVE_DEMUX_GET_SCHEDULER(d) (GST_ADAPTIVE_DEMUX_CAST(d)->priv->scheduler_task)
#define GST_ADAPTIVE_SCHEDULER_LOCK(d) gst_adaptive_demux_scheduler_lock(demux)
#define GST_ADAPTIVE_SCHEDULER_UNLOCK(d) G_STMT_START { \
GST_TRACE("Unlocking scheduler from thread %p", g_thread_self()); \
gst_adaptive_demux_loop_unlock_and_unpause (GST_ADAPTIVE_DEMUX_GET_SCHEDULER (d)); \
} G_STMT_END
#define GST_ADAPTIVE_DEMUX_SEGMENT_GET_LOCK(d) (&GST_ADAPTIVE_DEMUX_CAST(d)->priv->segment_lock)
#define GST_ADAPTIVE_DEMUX_SEGMENT_LOCK(d) g_mutex_lock (GST_ADAPTIVE_DEMUX_SEGMENT_GET_LOCK (d))
#define GST_ADAPTIVE_DEMUX_SEGMENT_UNLOCK(d) g_mutex_unlock (GST_ADAPTIVE_DEMUX_SEGMENT_GET_LOCK (d))
struct _GstAdaptiveDemuxPrivate
{
GstAdapter *input_adapter; /* protected by manifest_lock */
gint have_manifest; /* MT safe */
/* Adaptive scheduling and parsing task */
GstAdaptiveDemuxLoop *scheduler_task;
GMutex scheduler_lock;
/* Callback / timer id for the next manifest update */
guint manifest_updates_cb;
/* Count of failed manifest updates */
gint update_failed_count;
guint32 segment_seqnum; /* protected by manifest_lock */
/* main lock used to protect adaptive demux and all its streams.
* It serializes the adaptive demux public API.
*/
GRecMutex manifest_lock;
/* Duration, updated after manifest updates */
GstClockTime duration;
/* Set to TRUE if any stream is waiting on the manifest update */
gboolean stream_waiting_for_manifest;
GMutex api_lock;
/* Protects demux and stream segment information
* Needed because seeks can update segment information
* without needing to stop tasks when they just want to
* update the segment boundaries */
GMutex segment_lock;
GstClockTime qos_earliest_time;
/* Protects all tracks and period content */
GMutex tracks_lock;
/* Used to notify addition to a waiting (i.e. previously empty) track */
GCond tracks_add;
/* TRUE if we are buffering */
gboolean is_buffering;
/* TRUE if percent changed and message should be posted */
gboolean percent_changed;
gint percent;
/* Serialises buffering message posting to avoid out-of-order
* posting */
GMutex buffering_lock;
/* Atomic */
guint32 requested_selection_seqnum;
/* Lock protecting all the following fields */
GRecMutex output_lock;
/* Output task */
GstTask *output_task;
/* List of enabled OutputSlot */
GList *outputs;
/* flow combiner of output slots */
GstFlowCombiner *flowcombiner;
/* protected by output_lock */
gboolean flushing;
/* Current output selection seqnum */
guint32 current_selection_seqnum;
/* Current output position (in running time) */
GstClockTimeDiff global_output_position;
/* End of fields protected by output_lock */
gint n_audio_streams, n_video_streams, n_subtitle_streams;
/* Counter used for uniquely identifying periods */
gint n_periods;
/* Array of periods.
*
* Head is the period being outputted, or to be outputted first
* Tail is where new streams get added */
GQueue *periods;
};
static inline gboolean gst_adaptive_demux_scheduler_lock(GstAdaptiveDemux *d)
{
GST_TRACE("Locking scheduler from thread %p", g_thread_self());
if (!gst_adaptive_demux_loop_pause_and_lock (GST_ADAPTIVE_DEMUX_GET_SCHEDULER (d)))
return FALSE;
GST_TRACE("Locked scheduler from thread %p", g_thread_self());
return TRUE;
}
void demux_update_buffering_locked (GstAdaptiveDemux * demux);
void demux_post_buffering_locked (GstAdaptiveDemux * demux);
GstFlowReturn gst_adaptive_demux_update_manifest (GstAdaptiveDemux *demux);
void gst_adaptive_demux2_stream_wants_manifest_update (GstAdaptiveDemux * demux);
void gst_adaptive_demux2_stream_parse_error (GstAdaptiveDemux2Stream *stream, GError * err);
GstClockTime gst_adaptive_demux2_stream_get_fragment_waiting_time (GstAdaptiveDemux *
demux, GstAdaptiveDemux2Stream * stream);
GstClockTime gst_adaptive_demux2_stream_get_presentation_offset (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream);
GstClockTime gst_adaptive_demux_get_period_start_time (GstAdaptiveDemux * demux);
gboolean gst_adaptive_demux_is_live (GstAdaptiveDemux * demux);
void gst_adaptive_demux2_stream_on_manifest_update (GstAdaptiveDemux2Stream * stream);
void gst_adaptive_demux2_stream_on_output_space_available (GstAdaptiveDemux2Stream *stream);
gboolean gst_adaptive_demux2_stream_has_next_fragment (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream);
GstFlowReturn gst_adaptive_demux2_stream_update_fragment_info (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream);
GstFlowReturn gst_adaptive_demux2_stream_seek (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream, gboolean forward, GstSeekFlags flags,
GstClockTimeDiff ts, GstClockTimeDiff * final_ts);
gboolean gst_adaptive_demux_get_live_seek_range (GstAdaptiveDemux * demux,
gint64 * range_start, gint64 * range_stop);
gboolean gst_adaptive_demux2_stream_in_live_seek_range (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream);
gboolean gst_adaptive_demux2_stream_is_selected_locked (GstAdaptiveDemux2Stream *stream);
gboolean gst_adaptive_demux_has_next_period (GstAdaptiveDemux * demux);
void gst_adaptive_demux_advance_period (GstAdaptiveDemux * demux);
void gst_adaptive_demux2_stream_stop (GstAdaptiveDemux2Stream * stream);
typedef struct
{
GstMiniObject *item;
gsize size;
/* running time of item : GST_CLOCK_STIME_NONE for non-timed data */
GstClockTimeDiff runningtime;
/* GST_CLOCK_STIME_NONE for non-timed data */
GstClockTimeDiff runningtime_end;
} TrackQueueItem;
GstAdaptiveDemux2Stream *find_stream_for_track_locked (GstAdaptiveDemux *
demux, GstAdaptiveDemuxTrack * track);
GstMiniObject * track_dequeue_data_locked (GstAdaptiveDemux * demux, GstAdaptiveDemuxTrack * track, gboolean check_sticky_events);
void gst_adaptive_demux_track_flush (GstAdaptiveDemuxTrack * track);
void gst_adaptive_demux_track_drain_to (GstAdaptiveDemuxTrack * track, GstClockTime drain_running_time);
void gst_adaptive_demux_track_update_next_position (GstAdaptiveDemuxTrack * track);
/* Period functions */
GstAdaptiveDemuxPeriod * gst_adaptive_demux_period_new (GstAdaptiveDemux * demux);
GstAdaptiveDemuxPeriod * gst_adaptive_demux_period_ref (GstAdaptiveDemuxPeriod * period);
void gst_adaptive_demux_period_unref (GstAdaptiveDemuxPeriod * period);
gboolean gst_adaptive_demux_period_add_track (GstAdaptiveDemuxPeriod * period,
GstAdaptiveDemuxTrack * track);
gboolean gst_adaptive_demux_track_add_elements (GstAdaptiveDemuxTrack * track,
guint period_num);
void gst_adaptive_demux_period_select_default_tracks (GstAdaptiveDemux * demux,
GstAdaptiveDemuxPeriod * period);
void gst_adaptive_demux_period_transfer_selection (GstAdaptiveDemux * demux,
GstAdaptiveDemuxPeriod * next_period,
GstAdaptiveDemuxPeriod * current_period);
void gst_adaptive_demux_period_stop_tasks (GstAdaptiveDemuxPeriod * period);
GstFlowReturn gst_adaptive_demux_period_combine_stream_flows (GstAdaptiveDemuxPeriod * period);
gboolean gst_adaptive_demux_period_has_pending_tracks (GstAdaptiveDemuxPeriod * period);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,891 @@
/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* 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 "gstadaptivedemux.h"
#include "gstadaptivedemux-private.h"
GST_DEBUG_CATEGORY_EXTERN (adaptivedemux2_debug);
#define GST_CAT_DEFAULT adaptivedemux2_debug
/* TRACKS_LOCK held
* Flushes all data in the track and resets it */
void
gst_adaptive_demux_track_flush (GstAdaptiveDemuxTrack * track)
{
GST_DEBUG_OBJECT (track->demux, "Flushing track '%s' with %u queued items",
track->stream_id, gst_queue_array_get_length (track->queue));
gst_queue_array_clear (track->queue);
gst_event_store_flush (&track->sticky_events);
gst_segment_init (&track->input_segment, GST_FORMAT_TIME);
track->input_time = 0;
track->input_segment_seqnum = GST_SEQNUM_INVALID;
gst_segment_init (&track->output_segment, GST_FORMAT_TIME);
track->gap_position = track->gap_duration = GST_CLOCK_TIME_NONE;
track->output_time = 0;
track->next_position = GST_CLOCK_STIME_NONE;
track->level_bytes = 0;
track->level_time = 0;
track->eos = FALSE;
track->update_next_segment = FALSE;
track->output_discont = FALSE;
}
static gboolean
_track_sink_query_function (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstAdaptiveDemuxTrack *track = gst_pad_get_element_private (pad);
GstAdaptiveDemux *demux = track->demux;
gboolean ret = FALSE;
GST_DEBUG_OBJECT (pad, "query %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ACCEPT_CAPS:
/* Should we intersect by track caps as a safety check ? */
GST_DEBUG_OBJECT (demux, "We accept any caps on %s:%s",
GST_DEBUG_PAD_NAME (pad));
gst_query_set_accept_caps_result (query, TRUE);
ret = TRUE;
break;
default:
break;
}
return ret;
}
/* Dequeue an item from the track queue for processing
* TRACKS_LOCK hold */
static gboolean
track_dequeue_item_locked (GstAdaptiveDemux * demux,
GstAdaptiveDemuxTrack * track, TrackQueueItem * out_item)
{
TrackQueueItem *item = gst_queue_array_peek_head_struct (track->queue);
if (item == NULL)
return FALSE;
*out_item = *item;
gst_queue_array_pop_head (track->queue);
GST_LOG_OBJECT (demux,
"track %s item running_time %" GST_STIME_FORMAT " end %" GST_STIME_FORMAT,
track->stream_id, GST_STIME_ARGS (out_item->runningtime),
GST_STIME_ARGS (out_item->runningtime_end));
return TRUE;
}
static inline GstClockTimeDiff my_segment_to_running_time (GstSegment * segment,
GstClockTime val);
/* Dequeue or generate a buffer/event from the track queue and update the buffering levels
* TRACKS_LOCK hold */
GstMiniObject *
track_dequeue_data_locked (GstAdaptiveDemux * demux,
GstAdaptiveDemuxTrack * track, gboolean check_sticky_events)
{
GstMiniObject *res = NULL;
gboolean is_pending_sticky = FALSE;
GstEvent *event;
GstClockTimeDiff running_time;
GstClockTimeDiff running_time_end;
gsize item_size = 0;
if (check_sticky_events) {
/* If there are any sticky events to send, do that before anything else */
event = gst_event_store_get_next_pending (&track->sticky_events);
if (event != NULL) {
res = (GstMiniObject *) event;
running_time = running_time_end = GST_CLOCK_STIME_NONE;
GST_DEBUG_OBJECT (demux,
"track %s dequeued pending sticky event %" GST_PTR_FORMAT,
track->stream_id, event);
is_pending_sticky = TRUE;
goto handle_event;
}
}
do {
TrackQueueItem item;
/* If we're filling a gap, generate a gap event */
if (track->gap_position != GST_CLOCK_TIME_NONE) {
GstClockTime pos = track->gap_position;
GstClockTime duration = track->gap_duration;
if (duration > 100 * GST_MSECOND) {
duration = 100 * GST_MSECOND;
track->gap_position += duration;
track->gap_duration -= duration;
} else {
/* Duration dropped below 100 ms, this is the last
* gap of the sequence */
track->gap_position = GST_CLOCK_TIME_NONE;
track->gap_duration = GST_CLOCK_TIME_NONE;
}
res = (GstMiniObject *) gst_event_new_gap (pos, duration);
running_time = my_segment_to_running_time (&track->output_segment, pos);
running_time_end =
my_segment_to_running_time (&track->output_segment, pos + duration);
item_size = 0;
break;
}
/* Otherwise, try and pop something from the item queue */
if (!track_dequeue_item_locked (demux, track, &item))
return NULL;
res = item.item;
running_time = item.runningtime;
running_time_end = item.runningtime_end;
item_size = item.size;
/* Special case for a gap event, to drain them out little-by-little.
* See if it can be output directly, otherwise set up to fill a gap and loop again */
if (GST_IS_EVENT (res) && GST_EVENT_TYPE (res) == GST_EVENT_GAP
&& GST_CLOCK_STIME_IS_VALID (running_time)) {
GstClockTime pos, duration;
GstClockTime cstart, cstop;
gst_event_parse_gap (GST_EVENT_CAST (res), &pos, &duration);
/* Handle a track with no duration as 0 duration. This can only
* happen if an element in parsebin emits such a gap event */
if (duration == GST_CLOCK_TIME_NONE)
duration = 0;
/* We *can* end up with a gap outside of the segment range due to segment
* base updating when (re)activating a track. In that case, just let the gap
* event flow out normally.
* Otherwise, this gap crosses into the segment, clip it to the ends and set up to fill the gap */
if (!gst_segment_clip (&track->output_segment, GST_FORMAT_TIME, pos,
pos + duration, &cstart, &cstop))
break;
pos = cstart;
duration = cstop - cstart;
GST_DEBUG_OBJECT (demux,
"track %s Starting gap for runningtime %" GST_STIME_FORMAT
" - clipped position %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT,
track->stream_id, GST_STIME_ARGS (running_time), GST_TIME_ARGS (pos),
GST_TIME_ARGS (duration));
track->gap_position = pos;
track->gap_duration = duration;
gst_mini_object_unref (res);
res = NULL;
continue;
}
} while (res == NULL);
handle_event:
if (GST_IS_EVENT (res)) {
event = (GstEvent *) res;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:
gst_event_copy_segment (event, &track->output_segment);
if (track->update_next_segment) {
GstClockTimeDiff global_output_position =
demux->priv->global_output_position;
GST_DEBUG ("track %s: Override segment for running time %"
GST_STIME_FORMAT " : %" GST_PTR_FORMAT, track->stream_id,
GST_STIME_ARGS (global_output_position), event);
gst_event_unref (event);
gst_segment_set_running_time (&track->output_segment, GST_FORMAT_TIME,
global_output_position);
event = gst_event_new_segment (&track->output_segment);
gst_event_set_seqnum (event, track->demux->priv->segment_seqnum);
res = (GstMiniObject *) event;
running_time = global_output_position;
track->update_next_segment = FALSE;
/* Replace the stored sticky event with this one */
is_pending_sticky = FALSE;
}
break;
default:
break;
}
/* Store any sticky event in the cache, unless this is already an event
* from the pending sticky_events store */
if (!is_pending_sticky && GST_EVENT_IS_STICKY (event)) {
GST_DEBUG_OBJECT (demux,
"track %s Storing sticky event %" GST_PTR_FORMAT,
track->stream_id, event);
gst_event_store_insert_event (&track->sticky_events, event, FALSE);
}
}
/* Update track buffering levels */
if (running_time != GST_CLOCK_STIME_NONE) {
GstClockTimeDiff output_time;
track->output_time = running_time;
if (running_time_end != GST_CLOCK_TIME_NONE)
track->output_time = running_time_end;
output_time = MAX (track->output_time, demux->priv->global_output_position);
if (track->input_time >= output_time)
track->level_time = track->input_time - output_time;
else
track->level_time = 0;
GST_LOG_OBJECT (demux,
"track %s input_time:%" GST_STIME_FORMAT " output_time:%"
GST_STIME_FORMAT " level:%" GST_TIME_FORMAT,
track->stream_id, GST_STIME_ARGS (track->input_time),
GST_STIME_ARGS (output_time), GST_TIME_ARGS (track->level_time));
} else {
GST_LOG_OBJECT (demux, "track %s popping untimed item %" GST_PTR_FORMAT,
track->stream_id, res);
}
track->level_bytes -= item_size;
/* FIXME: This logic should be in adaptive demux, not the track */
if (track->level_time < track->waiting_del_level) {
/* Wake up download loop */
GstAdaptiveDemux2Stream *stream =
find_stream_for_track_locked (demux, track);
g_assert (stream != NULL);
gst_adaptive_demux2_stream_on_output_space_available (stream);
}
return res;
}
void
gst_adaptive_demux_track_drain_to (GstAdaptiveDemuxTrack * track,
GstClockTime drain_running_time)
{
GstAdaptiveDemux *demux = track->demux;
GST_DEBUG_OBJECT (demux,
"Track '%s' draining to running time %" GST_STIME_FORMAT,
track->stream_id, GST_STIME_ARGS (drain_running_time));
while (track->next_position == GST_CLOCK_STIME_NONE ||
track->next_position < drain_running_time) {
TrackQueueItem *item;
GstMiniObject *next_mo = NULL;
/* If we're in a gap, and the end time is after the target running time,
* exit */
if (track->gap_position != GST_CLOCK_TIME_NONE) {
GstClockTimeDiff running_time_end;
GstClockTimeDiff gap_end = track->gap_position;
/* In reverse playback, the start of the gap is the highest
* running time, so only add duration for forward play */
if (track->output_segment.rate > 0)
gap_end += track->gap_duration;
running_time_end =
my_segment_to_running_time (&track->output_segment, gap_end);
if (running_time_end >= drain_running_time) {
GST_DEBUG_OBJECT (demux,
"Track '%s' drained to GAP with running time %" GST_STIME_FORMAT,
track->stream_id, GST_STIME_ARGS (running_time_end));
return;
}
/* Otherwise this gap is complete, so skip it */
track->gap_position = GST_CLOCK_STIME_NONE;
}
/* Otherwise check what's enqueued */
item = gst_queue_array_peek_head_struct (track->queue);
/* track is empty, we're done */
if (item == NULL) {
GST_DEBUG_OBJECT (demux, "Track '%s' completely drained",
track->stream_id);
return;
}
/* If the item has a running time, and it's after the drain_running_time
* we're done. */
if (item->runningtime != GST_CLOCK_STIME_NONE
&& item->runningtime >= drain_running_time) {
GST_DEBUG_OBJECT (demux, "Track '%s' drained to item %" GST_PTR_FORMAT
" with running time %" GST_STIME_FORMAT,
track->stream_id, item->item, GST_STIME_ARGS (item->runningtime));
return;
}
GST_DEBUG_OBJECT (demux, "Track '%s' discarding %" GST_PTR_FORMAT
" with running time %" GST_STIME_FORMAT,
track->stream_id, item->item, GST_STIME_ARGS (item->runningtime));
/* Dequeue the item and discard. Sticky events
* will be collected by the dequeue function, gaps will be started.
* If it's a buffer, mark the track as discont to get the flag set
* on the next output buffer */
next_mo = track_dequeue_data_locked (demux, track, FALSE);
if (GST_IS_BUFFER (next_mo)) {
track->output_discont = TRUE;
}
gst_mini_object_unref (next_mo);
gst_adaptive_demux_track_update_next_position (track);
}
GST_DEBUG_OBJECT (demux,
"Track '%s' drained to running time %" GST_STIME_FORMAT, track->stream_id,
GST_STIME_ARGS (track->next_position));
}
static inline GstClockTimeDiff
my_segment_to_running_time (GstSegment * segment, GstClockTime val)
{
GstClockTimeDiff res = GST_CLOCK_STIME_NONE;
if (GST_CLOCK_TIME_IS_VALID (val)) {
gboolean sign =
gst_segment_to_running_time_full (segment, GST_FORMAT_TIME, val, &val);
if (sign > 0)
res = val;
else if (sign < 0)
res = -val;
}
return res;
}
/* Queues an item on a track queue and updates the buffering levels
* TRACKS_LOCK hold */
static void
track_queue_data_locked (GstAdaptiveDemux * demux,
GstAdaptiveDemuxTrack * track, GstMiniObject * object, gsize size,
GstClockTime timestamp, GstClockTime duration)
{
TrackQueueItem item;
item.item = object;
item.size = size;
item.runningtime = GST_CLOCK_STIME_NONE;
item.runningtime_end = GST_CLOCK_STIME_NONE;
if (timestamp != GST_CLOCK_TIME_NONE) {
GstClockTimeDiff output_time;
/* Set the running time of the item */
item.runningtime =
my_segment_to_running_time (&track->input_segment, timestamp);
/* Update segment position (include duration if valid) */
track->input_segment.position = timestamp;
if (GST_CLOCK_TIME_IS_VALID (duration)) {
track->input_segment.position += duration;
item.runningtime_end =
my_segment_to_running_time (&track->input_segment,
track->input_segment.position);
}
/* Update track input time and level */
track->input_time =
my_segment_to_running_time (&track->input_segment,
track->input_segment.position);
output_time = MAX (track->output_time, demux->priv->global_output_position);
if (track->input_time >= output_time)
track->level_time = track->input_time - output_time;
else
track->level_time = 0;
GST_LOG_OBJECT (demux,
"track %s input_time:%" GST_STIME_FORMAT " output_time:%"
GST_STIME_FORMAT " level:%" GST_TIME_FORMAT,
track->stream_id, GST_STIME_ARGS (track->input_time),
GST_STIME_ARGS (track->output_time), GST_TIME_ARGS (track->level_time));
}
track->level_bytes += size;
gst_queue_array_push_tail_struct (track->queue, &item);
/* If we were waiting for this track to add something, notify output thread */
/* FIXME: This should be in adaptive demux */
if (track->waiting_add) {
g_cond_signal (&demux->priv->tracks_add);
}
}
static GstFlowReturn
_track_sink_chain_function (GstPad * pad, GstObject * parent,
GstBuffer * buffer)
{
GstAdaptiveDemuxTrack *track = gst_pad_get_element_private (pad);
GstAdaptiveDemux *demux = track->demux;
GstClockTime ts;
GST_DEBUG_OBJECT (pad, "buffer %" GST_PTR_FORMAT, buffer);
TRACKS_LOCK (demux);
ts = GST_BUFFER_DTS_OR_PTS (buffer);
/* Buffers coming out of parsebin *should* always be timestamped (it's the
* goal of parsebin after all). The tracks will use that (converted to
* running-time) in order to track position and buffering levels.
*
* Unfortunately there are valid cases were the parsers won't be able to
* timestamp all frames (due to the underlying formats or muxing). For those
* cases, we use the last incoming timestamp (via the track input GstSegment
* position):
*
* * If buffers were previously received, that segment position will
* correspond to the last timestamped-buffer PTS/DTS
*
* * If *no* buffers were previously received, the segment position *should*
* correspond to the valid initial position (in buffer timestamps). If not
* set, we need to bail out.
*/
if (!GST_CLOCK_TIME_IS_VALID (ts)) {
if (GST_CLOCK_TIME_IS_VALID (track->input_segment.position)) {
GST_WARNING_OBJECT (pad,
"buffer doesn't have any pts or dts, using segment position (%"
GST_TIME_FORMAT ")", GST_TIME_ARGS (track->input_segment.position));
ts = track->input_segment.position;
} else {
GST_ERROR_OBJECT (pad, "initial buffer doesn't have any pts or dts !");
gst_buffer_unref (buffer);
TRACKS_UNLOCK (demux);
return GST_FLOW_ERROR;
}
}
if (GST_CLOCK_TIME_IS_VALID (track->input_segment.position) &&
ts > track->input_segment.position &&
ts > track->input_segment.start &&
ts - track->input_segment.position > 100 * GST_MSECOND) {
GstClockTime duration = ts - track->input_segment.position;
GstEvent *gap = gst_event_new_gap (track->input_segment.position, duration);
/* Insert gap event to ensure coherent interleave */
GST_DEBUG_OBJECT (pad,
"Inserting gap for %" GST_TIME_FORMAT " vs %" GST_TIME_FORMAT,
GST_TIME_ARGS (ts), GST_TIME_ARGS (track->input_segment.position));
track_queue_data_locked (demux, track, (GstMiniObject *) gap, 0,
track->input_segment.position, duration);
}
track_queue_data_locked (demux, track, (GstMiniObject *) buffer,
gst_buffer_get_size (buffer), ts, GST_BUFFER_DURATION (buffer));
/* Recalculate buffering */
demux_update_buffering_locked (demux);
demux_post_buffering_locked (demux);
/* UNLOCK */
TRACKS_UNLOCK (demux);
return GST_FLOW_OK;
}
static gboolean
_track_sink_event_function (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstAdaptiveDemuxTrack *track = gst_pad_get_element_private (pad);
GstAdaptiveDemux *demux = track->demux;
GstClockTime timestamp = GST_CLOCK_TIME_NONE;
GstClockTime duration = GST_CLOCK_TIME_NONE;
gboolean drop = FALSE;
GST_DEBUG_OBJECT (pad, "event %" GST_PTR_FORMAT, event);
TRACKS_LOCK (demux);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_STREAM_COLLECTION:
{
/* Replace upstream collection with demux collection */
GST_DEBUG_OBJECT (pad, "Dropping stream-collection, we send our own");
drop = TRUE;
break;
}
case GST_EVENT_STREAM_START:
{
GST_DEBUG_OBJECT (pad, "Dropping stream-start, we send our own");
if (track->eos) {
gint i, len;
/* Find and drop latest EOS if present */
len = gst_queue_array_get_length (track->queue);
for (i = len - 1; i >= 0; i--) {
TrackQueueItem *item =
gst_queue_array_peek_nth_struct (track->queue, i);
if (GST_IS_EVENT (item->item)
&& GST_EVENT_TYPE (item->item) == GST_EVENT_EOS) {
TrackQueueItem sub;
GST_DEBUG_OBJECT (pad, "Removing previously received EOS (pos:%d)",
i);
if (gst_queue_array_drop_struct (track->queue, i, &sub))
gst_mini_object_unref (sub.item);
break;
}
}
track->eos = FALSE;
}
drop = TRUE;
break;
}
case GST_EVENT_EOS:
{
if (track->pending_srcpad != NULL) {
GST_DEBUG_OBJECT (pad,
"Dropping EOS because we have a pending pad switch");
drop = TRUE;
} else {
track->eos = TRUE;
}
break;
}
case GST_EVENT_FLUSH_STOP:
case GST_EVENT_FLUSH_START:
{
/* Drop flush events */
drop = TRUE;
break;
}
default:
break;
}
if (drop || !GST_EVENT_IS_SERIALIZED (event)) {
GST_DEBUG_OBJECT (pad, "dropping event %s", GST_EVENT_TYPE_NAME (event));
gst_event_unref (event);
TRACKS_UNLOCK (demux);
/* Silently "accept" them */
return TRUE;
}
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:
{
guint64 seg_seqnum = gst_event_get_seqnum (event);
if (track->input_segment_seqnum == seg_seqnum) {
GST_DEBUG_OBJECT (pad, "Ignoring duplicate segment");
gst_event_unref (event);
TRACKS_UNLOCK (demux);
return TRUE;
}
track->input_segment_seqnum = seg_seqnum;
gst_event_copy_segment (event, &track->input_segment);
if (track->input_segment.rate >= 0)
track->input_segment.position = track->input_segment.start;
else
track->input_segment.position = track->input_segment.stop;
GST_DEBUG_OBJECT (pad, "track %s stored segment %" GST_SEGMENT_FORMAT,
track->stream_id, &track->input_segment);
timestamp = track->input_segment.position;
break;
}
case GST_EVENT_GAP:
{
gst_event_parse_gap (event, &timestamp, &duration);
if (!GST_CLOCK_TIME_IS_VALID (timestamp)) {
GST_DEBUG_OBJECT (pad, "Dropping gap event with invalid timestamp");
goto drop_ok;
}
break;
}
default:
break;
}
track_queue_data_locked (demux, track, (GstMiniObject *) event, 0,
timestamp, duration);
/* Recalculate buffering */
demux_update_buffering_locked (demux);
demux_post_buffering_locked (demux);
TRACKS_UNLOCK (demux);
return TRUE;
/* errors */
drop_ok:
{
gst_event_unref (event);
TRACKS_UNLOCK (demux);
return TRUE;
}
}
static void
track_sinkpad_unlinked_cb (GstPad * sinkpad, GstPad * parsebin_srcpad,
GstAdaptiveDemuxTrack * track)
{
GST_DEBUG_OBJECT (sinkpad, "Got unlinked from %s:%s",
GST_DEBUG_PAD_NAME (parsebin_srcpad));
if (track->pending_srcpad) {
GST_DEBUG_OBJECT (sinkpad, "linking to pending pad %s:%s",
GST_DEBUG_PAD_NAME (track->pending_srcpad));
if (gst_pad_link (track->pending_srcpad, sinkpad) != GST_PAD_LINK_OK) {
GST_ERROR_OBJECT (sinkpad, "could not link pending pad !");
}
gst_object_unref (track->pending_srcpad);
track->pending_srcpad = NULL;
}
}
/* TRACKS_LOCK held
* Call this to update the track next_position with timed data */
void
gst_adaptive_demux_track_update_next_position (GstAdaptiveDemuxTrack * track)
{
guint i, len;
/* If filling a gap, the next position is the gap position */
if (track->gap_position != GST_CLOCK_TIME_NONE) {
track->next_position =
my_segment_to_running_time (&track->output_segment,
track->gap_position);
return;
}
len = gst_queue_array_get_length (track->queue);
for (i = 0; i < len; i++) {
TrackQueueItem *item = gst_queue_array_peek_nth_struct (track->queue, i);
if (item->runningtime != GST_CLOCK_STIME_NONE) {
GST_DEBUG_OBJECT (track->demux,
"Track '%s' next position %" GST_STIME_FORMAT, track->stream_id,
GST_STIME_ARGS (item->runningtime));
track->next_position = item->runningtime;
return;
}
}
track->next_position = GST_CLOCK_STIME_NONE;
GST_DEBUG_OBJECT (track->demux,
"Track '%s' doesn't have any pending timed data", track->stream_id);
}
static void
_demux_track_free (GstAdaptiveDemuxTrack * track)
{
GST_DEBUG_OBJECT (track->demux, "freeing track %p '%s'", track,
track->stream_id);
g_free (track->stream_id);
g_free (track->upstream_stream_id);
if (track->pending_srcpad)
gst_object_unref (track->pending_srcpad);
if (track->generic_caps)
gst_caps_unref (track->generic_caps);
gst_object_unref (track->stream_object);
if (track->tags)
gst_tag_list_unref (track->tags);
gst_queue_array_free (track->queue);
gst_event_store_deinit (&track->sticky_events);
if (track->element != NULL) {
gst_element_set_state (track->element, GST_STATE_NULL);
gst_bin_remove (GST_BIN_CAST (track->demux), track->element);
}
g_free (track);
}
GstAdaptiveDemuxTrack *
gst_adaptive_demux_track_ref (GstAdaptiveDemuxTrack * track)
{
g_return_val_if_fail (track != NULL, NULL);
GST_TRACE ("%p %d -> %d", track, track->ref_count, track->ref_count + 1);
g_atomic_int_inc (&track->ref_count);
return track;
}
void
gst_adaptive_demux_track_unref (GstAdaptiveDemuxTrack * track)
{
g_return_if_fail (track != NULL);
GST_TRACE ("%p %d -> %d", track, track->ref_count, track->ref_count - 1);
if (g_atomic_int_dec_and_test (&track->ref_count)) {
_demux_track_free (track);
}
}
static void
_track_queue_item_clear (TrackQueueItem * item)
{
if (item->item) {
gst_mini_object_unref ((GstMiniObject *) item->item);
item->item = NULL;
}
}
/* Internal function which actually adds the elements to the demuxer */
gboolean
gst_adaptive_demux_track_add_elements (GstAdaptiveDemuxTrack * track,
guint period_num)
{
GstAdaptiveDemux *demux = track->demux;
gchar *internal_name;
guint i, len;
internal_name =
g_strdup_printf ("track-period%d-%s", period_num, track->stream_id);
len = strlen (internal_name);
for (i = 0; i < len; i++)
if (internal_name[i] == ' ')
internal_name[i] = '_';
track->element = gst_bin_new (internal_name);
g_free (internal_name);
internal_name =
g_strdup_printf ("track-period%d-sink-%s", period_num, track->stream_id);
len = strlen (internal_name);
for (i = 0; i < len; i++)
if (internal_name[i] == ' ')
internal_name[i] = '_';
track->sinkpad = gst_pad_new (internal_name, GST_PAD_SINK);
g_signal_connect (track->sinkpad, "unlinked",
(GCallback) track_sinkpad_unlinked_cb, track);
g_free (internal_name);
gst_element_add_pad (GST_ELEMENT_CAST (track->element), track->sinkpad);
gst_pad_set_element_private (track->sinkpad, track);
gst_pad_set_chain_function (track->sinkpad, _track_sink_chain_function);
gst_pad_set_event_function (track->sinkpad, _track_sink_event_function);
gst_pad_set_query_function (track->sinkpad, _track_sink_query_function);
if (!gst_bin_add (GST_BIN_CAST (demux), track->element)) {
track->element = NULL;
return FALSE;
}
gst_element_sync_state_with_parent (track->element);
return TRUE;
}
/**
* gst_adaptive_demux_track_new:
* @demux: a #GstAdaptiveDemux
* @type: a #GstStreamType
* @flags: a #GstStreamFlags
* @stream_id: (transfer none): The stream id for the new track
* @caps: (transfer full): The caps for the track
* @tags: (allow-none) (transfer full): The tags for the track
*
* Create and register a new #GstAdaptiveDemuxTrack
*
* Returns: (transfer none) The new track
*/
GstAdaptiveDemuxTrack *
gst_adaptive_demux_track_new (GstAdaptiveDemux * demux,
GstStreamType type,
GstStreamFlags flags, gchar * stream_id, GstCaps * caps, GstTagList * tags)
{
GstAdaptiveDemuxTrack *track;
g_return_val_if_fail (stream_id != NULL, NULL);
g_return_val_if_fail (type && type != GST_STREAM_TYPE_UNKNOWN, NULL);
GST_DEBUG_OBJECT (demux, "type:%s stream_id:%s caps:%" GST_PTR_FORMAT,
gst_stream_type_get_name (type), stream_id, caps);
track = g_new0 (GstAdaptiveDemuxTrack, 1);
g_atomic_int_set (&track->ref_count, 1);
track->demux = demux;
track->type = type;
track->flags = flags;
track->stream_id = g_strdup (stream_id);
track->generic_caps = caps;
track->stream_object = gst_stream_new (stream_id, caps, type, flags);
if (tags) {
track->tags = gst_tag_list_ref (tags);
gst_stream_set_tags (track->stream_object, tags);
}
track->selected = FALSE;
track->active = FALSE;
track->draining = FALSE;
track->queue = gst_queue_array_new_for_struct (sizeof (TrackQueueItem), 50);
gst_queue_array_set_clear_func (track->queue,
(GDestroyNotify) _track_queue_item_clear);
gst_event_store_init (&track->sticky_events);
track->waiting_add = TRUE;
track->waiting_del_level = 0;
/* We have no fragment duration yet, so the buffering threshold is just the
* low watermark in time for now */
GST_OBJECT_LOCK (demux);
track->buffering_threshold = demux->buffering_low_watermark_time;
GST_OBJECT_UNLOCK (demux);
gst_segment_init (&track->input_segment, GST_FORMAT_TIME);
track->input_time = 0;
track->input_segment_seqnum = GST_SEQNUM_INVALID;
gst_segment_init (&track->output_segment, GST_FORMAT_TIME);
track->gap_position = track->gap_duration = GST_CLOCK_TIME_NONE;
track->output_time = 0;
track->next_position = GST_CLOCK_STIME_NONE;
track->update_next_segment = FALSE;
track->level_bytes = 0;
track->level_time = 0;
return track;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,786 @@
/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _GST_ADAPTIVE_DEMUX_H_
#define _GST_ADAPTIVE_DEMUX_H_
#include <gst/gst.h>
#include <gst/base/gstqueuearray.h>
#include <gst/app/gstappsrc.h>
#include "downloadhelper.h"
#include "downloadrequest.h"
#include "gstadaptivedemuxutils.h"
G_BEGIN_DECLS
#define GST_TYPE_ADAPTIVE_DEMUX \
(gst_adaptive_demux_ng_get_type())
#define GST_ADAPTIVE_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ADAPTIVE_DEMUX,GstAdaptiveDemux))
#define GST_ADAPTIVE_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ADAPTIVE_DEMUX,GstAdaptiveDemuxClass))
#define GST_ADAPTIVE_DEMUX_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_ADAPTIVE_DEMUX,GstAdaptiveDemuxClass))
#define GST_IS_ADAPTIVE_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ADAPTIVE_DEMUX))
#define GST_IS_ADAPTIVE_DEMUX_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ADAPTIVE_DEMUX))
#define GST_ADAPTIVE_DEMUX_CAST(obj) ((GstAdaptiveDemux *)obj)
#define GST_TYPE_ADAPTIVE_DEMUX2_STREAM \
(gst_adaptive_demux2_stream_get_type())
#define GST_ADAPTIVE_DEMUX2_STREAM(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ADAPTIVE_DEMUX2_STREAM,GstAdaptiveDemux2Stream))
#define GST_ADAPTIVE_DEMUX2_STREAM_CAST(obj) ((GstAdaptiveDemux2Stream *)obj)
typedef struct _GstAdaptiveDemux2Stream GstAdaptiveDemux2Stream;
typedef GstObjectClass GstAdaptiveDemux2StreamClass;
/**
* GST_ADAPTIVE_DEMUX_SINK_NAME:
*
* The name of the templates for the sink pad.
*/
#define GST_ADAPTIVE_DEMUX_SINK_NAME "sink"
/**
* GST_ADAPTIVE_DEMUX_SINK_PAD:
* @obj: a #GstAdaptiveDemux
*
* Gives the pointer to the sink #GstPad object of the element.
*/
#define GST_ADAPTIVE_DEMUX_SINK_PAD(obj) (((GstAdaptiveDemux *) (obj))->sinkpad)
#define GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS(obj) ((((GstAdaptiveDemux*)(obj))->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) == GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)
#define GST_ADAPTIVE_DEMUX2_STREAM_NEED_HEADER(obj) (((GstAdaptiveDemux2Stream *) (obj))->need_header)
/**
* GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME:
*
* Name of the ELEMENT type messages posted by dashdemux with statistics.
*
* Since: 1.6
*/
#define GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME "adaptive-streaming-statistics"
#define GST_ELEMENT_ERROR_FROM_ERROR(el, msg, err) G_STMT_START { \
gchar *__dbg = g_strdup_printf ("%s: %s", msg, err->message); \
GST_WARNING_OBJECT (el, "error: %s", __dbg); \
gst_element_message_full (GST_ELEMENT(el), GST_MESSAGE_ERROR, \
err->domain, err->code, \
NULL, __dbg, __FILE__, GST_FUNCTION, __LINE__); \
g_clear_error (&err); \
} G_STMT_END
/* DEPRECATED */
#define GST_ADAPTIVE_DEMUX_FLOW_END_OF_FRAGMENT GST_FLOW_CUSTOM_SUCCESS_1
/* Current fragment download should be aborted and restarted. The parent class
* will call ::update_fragment_info() again to get the updated information.
*/
#define GST_ADAPTIVE_DEMUX_FLOW_RESTART_FRAGMENT GST_FLOW_CUSTOM_SUCCESS_2
typedef enum _GstAdaptiveDemux2StreamState GstAdaptiveDemux2StreamState;
typedef struct _GstAdaptiveDemux2StreamFragment GstAdaptiveDemux2StreamFragment;
typedef struct _GstAdaptiveDemuxTrack GstAdaptiveDemuxTrack;
typedef struct _GstAdaptiveDemuxPeriod GstAdaptiveDemuxPeriod;
typedef struct _GstAdaptiveDemux GstAdaptiveDemux;
typedef struct _GstAdaptiveDemuxClass GstAdaptiveDemuxClass;
typedef struct _GstAdaptiveDemuxPrivate GstAdaptiveDemuxPrivate;
struct _GstAdaptiveDemux2StreamFragment
{
/* The period-local stream time for the given fragment. */
GstClockTimeDiff stream_time;
GstClockTime duration;
gchar *uri;
gint64 range_start;
gint64 range_end;
/* when chunked downloading is used, may be be updated need_another_chunk() */
gint chunk_size;
/* when headers are needed */
gchar *header_uri;
gint64 header_range_start;
gint64 header_range_end;
/* when index is needed */
gchar *index_uri;
gint64 index_range_start;
gint64 index_range_end;
gboolean finished;
};
struct _GstAdaptiveDemuxTrack
{
gint ref_count;
/* Demux */
GstAdaptiveDemux *demux;
/* Stream type */
GstStreamType type;
/* Stream flags */
GstStreamFlags flags;
/* Unique identifier */
gchar *stream_id;
/* Unique identifier of the internal stream produced
* by parsebin for the Stream this track comes from */
gchar *upstream_stream_id;
/* Generic *elementary stream* caps */
GstCaps *generic_caps;
/* Generic metadata */
GstTagList *tags;
/* The stream object */
GstStream *stream_object;
/* If TRUE, this track should be filled */
gboolean selected;
/* If TRUE, this track is currently being outputted */
gboolean active;
/* If TRUE, it is no longer selected but still being outputted. */
gboolean draining;
/* FIXME : Replace by actual track element */
GstElement *element;
/* The level at which 100% buffering is achieved */
GstClockTime buffering_threshold;
/* The sinkpad receives parsed elementary stream */
GstPad *sinkpad;
/* The pending parsebin source pad (used in case streams from parsebin get updated) (ref taken) */
GstPad *pending_srcpad;
/* Data storage */
GstQueueArray *queue;
/* Sticky event storage for this track */
GstEventStore sticky_events;
/* ============== */
/* Input tracking */
/* The track received EOS */
gboolean eos;
/* Level to wait until download can commence */
GstClockTime waiting_del_level;
/* Input segment and time (in running time) */
GstSegment input_segment;
GstClockTimeDiff input_time;
guint64 input_segment_seqnum;
/* ================= */
/* Contents tracking */
/* Current level of queue in bytes and time */
guint64 level_bytes;
GstClockTime level_time;
/* =============== */
/* Output tracking */
/* Is the output thread waiting for data on this track ? */
gboolean waiting_add;
/* If TRUE, the next pending GstSegment running time should be updated to the
* time stored in update_next_segment_run_ts */
gboolean update_next_segment;
/* Output segment and time (in running time) */
GstSegment output_segment;
GstClockTimeDiff output_time;
/* Track position and duration for emitting gap
* events */
GstClockTime gap_position;
GstClockTime gap_duration;
/* Next running time position pending in queue */
GstClockTimeDiff next_position;
/* If the next output buffer should be marked discont */
gboolean output_discont;
};
enum _GstAdaptiveDemux2StreamState {
GST_ADAPTIVE_DEMUX2_STREAM_STATE_STOPPED, /* Stream was stopped */
GST_ADAPTIVE_DEMUX2_STREAM_STATE_RESTART, /* Stream stopped but needs restart logic */
GST_ADAPTIVE_DEMUX2_STREAM_STATE_START_FRAGMENT,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_WAITING_LIVE,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_WAITING_OUTPUT_SPACE,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_WAITING_MANIFEST_UPDATE,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_DOWNLOADING,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_EOS,
GST_ADAPTIVE_DEMUX2_STREAM_STATE_ERRORED
};
struct _GstAdaptiveDemux2Stream
{
GstObject object;
/* FIXME : transition to gstobject->parent */
GstAdaptiveDemux *demux;
/* The period to which the stream belongs, set when adding the stream to the
* demuxer */
GstAdaptiveDemuxPeriod *period;
/* The tracks this stream targets */
GList *tracks;
/* The internal parsebin, forward data to track */
GstElement *parsebin;
GstPad *parsebin_sink;
gulong pad_added_id, pad_removed_id;
GstSegment parse_segment;
/* TRUE if the current stream GstSegment should be sent downstream */
gboolean send_segment;
/* TRUE if the stream GstSegment requires recalculation (from demuxer
segment) */
gboolean compute_segment;
/* first_and_live applies to compute_segment */
gboolean first_and_live;
/* When restarting, what is the target position (in demux segment) to
* begin at */
GstClockTime start_position;
/* Track the current position (in demux segment) of the current fragment */
GstClockTime current_position;
GstCaps *pending_caps;
GstTagList *pending_tags;
GList *pending_events;
GstFlowReturn last_ret;
GError *last_error;
gboolean discont;
/* download tooling */
gboolean need_header;
gboolean need_index;
gboolean downloading_header;
gboolean downloading_index;
/* persistent, reused download request for fragment data */
DownloadRequest *download_request;
GstAdaptiveDemux2StreamState state;
guint pending_cb_id;
gboolean download_active;
guint last_status_code;
gboolean pending_tracks; /* if we need to discover tracks dynamically for this stream */
gboolean download_finished;
gboolean cancelled;
gboolean replaced; /* replaced in a bitrate switch (used with cancelled) */
gboolean starting_fragment;
gboolean first_fragment_buffer;
gint64 download_start_time;
gint64 download_total_bytes;
gint64 download_end_offset;
guint64 current_download_rate;
/* bitrate of the previous fragment (pre-queue2) */
guint64 last_bitrate;
/* Total last download time, from request to completion */
GstClockTime last_download_time;
/* Average for the last fragments */
guint64 moving_bitrate;
guint moving_index;
guint64 *fragment_bitrates;
GstAdaptiveDemux2StreamFragment fragment;
guint download_error_count;
/* Last collection provided by parsebin */
GstStreamCollection *stream_collection;
/* OR'd set of stream types in this stream */
GstStreamType stream_type;
};
/**
* GstAdaptiveDemuxPeriod:
*
* The opaque #GstAdaptiveDemuxPeriod data structure. */
struct _GstAdaptiveDemuxPeriod
{
gint ref_count;
GstAdaptiveDemux *demux;
/* TRUE if the streams of this period were prepared and can be started */
gboolean prepared;
/* TRUE if the period no longer receives any data (i.e. it is closed) */
gboolean closed;
/* An increasing unique identifier for the period.
*
* Note: unrelated to dash period id (which can be identical across
* periods) */
guint period_num;
/* The list of GstAdaptiveDemux2Stream (ref hold) */
GList *streams;
/* Current collection */
GstStreamCollection *collection;
/* List of available GstAdaptiveDemuxTrack (ref hold) */
GList *tracks;
/* Whether tracks were changed and need re-matching against outputs */
gboolean tracks_changed;
};
/**
* GstAdaptiveDemux:
*
* The opaque #GstAdaptiveDemux data structure.
*/
struct _GstAdaptiveDemux
{
/*< private >*/
GstBin bin;
gint running;
/*< protected >*/
GstPad *sinkpad;
DownloadHelper *download_helper;
/* Protected by TRACKS_LOCK */
/* Period used for output */
GstAdaptiveDemuxPeriod *output_period;
/* Period used for input */
GstAdaptiveDemuxPeriod *input_period;
GstSegment segment;
gdouble instant_rate_multiplier; /* 1.0 by default, or from instant-rate seek */
gchar *manifest_uri;
gchar *manifest_base_uri;
/* Properties */
gfloat bandwidth_target_ratio; /* ratio of the available bitrate to use */
guint connection_speed; /* Available / bandwidth to use set by the application */
guint min_bitrate; /* Minimum bitrate to choose */
guint max_bitrate; /* Maximum bitrate to choose */
guint current_download_rate; /* Current estimate of download bitrate */
/* Buffering levels */
GstClockTime max_buffering_time;
GstClockTime buffering_high_watermark_time;
GstClockTime buffering_low_watermark_time;
gdouble buffering_high_watermark_fragments;
gdouble buffering_low_watermark_fragments;
/* video/audio buffer level as minimum of the appropriate streams */
GstClockTime current_level_time_video;
GstClockTime current_level_time_audio;
gboolean have_group_id;
guint group_id;
guint next_stream_id;
/* Realtime clock */
GstAdaptiveDemuxClock *realtime_clock;
/* < private > */
GstAdaptiveDemuxPrivate *priv;
};
/**
* GstAdaptiveDemuxClass:
*
*/
struct _GstAdaptiveDemuxClass
{
/*< private >*/
GstBinClass bin_class;
/*< public >*/
/**
* process_manifest: Parse the manifest
* @demux: #GstAdaptiveDemux
* @manifest: the manifest to be parsed
*
* Parse the manifest and add the created streams using
* gst_adaptive_demux2_stream_new()
*
* Returns: %TRUE if successful
*/
gboolean (*process_manifest) (GstAdaptiveDemux * demux, GstBuffer * manifest);
/**
* get_manifest_update_interval:
* @demux: #GstAdaptiveDemux
*
* Used during live streaming, the subclass should return the interval
* between successive manifest updates
*
* Returns: the update interval in microseconds
*/
gint64 (*get_manifest_update_interval) (GstAdaptiveDemux * demux);
/**
* update_manifest:
* @demux: #GstAdaptiveDemux
*
* During live streaming, this will be called for the subclass to update its
* manifest with the new version. By default it fetches the manifest URI
* and passes it to GstAdaptiveDemux::update_manifest_data().
*
* Returns: #GST_FLOW_OK is all succeeded, #GST_FLOW_EOS if the stream ended
* or #GST_FLOW_ERROR if an error happened
*/
GstFlowReturn (*update_manifest) (GstAdaptiveDemux * demux);
/**
* update_manifest_data:
* @demux: #GstAdaptiveDemux
* @buf: Downloaded manifest data
*
* During live streaming, this will be called for the subclass to update its
* manifest with the new version
*
* Returns: #GST_FLOW_OK is all succeeded, #GST_FLOW_EOS if the stream ended
* or #GST_FLOW_ERROR if an error happened
*/
GstFlowReturn (*update_manifest_data) (GstAdaptiveDemux * demux, GstBuffer * buf);
gboolean (*is_live) (GstAdaptiveDemux * demux);
GstClockTime (*get_duration) (GstAdaptiveDemux * demux);
/**
* reset:
* @demux: #GstAdaptiveDemux
*
* Reset the internal state of the subclass, getting ready to restart with
* a new stream afterwards
*/
void (*reset) (GstAdaptiveDemux * demux);
/**
* seek:
* @demux: #GstAdaptiveDemux
* @seek: a seek #GstEvent
*
* The demuxer should seek on all its streams to the specified position
* in the seek event
*
* Returns: %TRUE if successful
*/
gboolean (*seek) (GstAdaptiveDemux * demux, GstEvent * seek);
/**
* has_next_period:
* @demux: #GstAdaptiveDemux
*
* Checks if there is a next period following the current one.
* DASH can have multiple medias chained in its manifest, when one finishes
* this function is called to verify if there is a new period to be played
* in sequence.
*
* Returns: %TRUE if there is another period
*/
gboolean (*has_next_period) (GstAdaptiveDemux * demux);
/**
* advance_period:
* @demux: #GstAdaptiveDemux
*
* Advances the manifest to the next period. New streams should be created
* using gst_adaptive_demux2_stream_new().
*/
void (*advance_period) (GstAdaptiveDemux * demux);
GstFlowReturn (*stream_seek) (GstAdaptiveDemux2Stream * stream,
gboolean forward,
GstSeekFlags flags,
GstClockTimeDiff target_ts,
GstClockTimeDiff * final_ts);
gboolean (*stream_has_next_fragment) (GstAdaptiveDemux2Stream * stream);
GstFlowReturn (*stream_advance_fragment) (GstAdaptiveDemux2Stream * stream);
/**
* stream_can_start:
* @demux: The #GstAdaptiveDemux
* @stream: a #GstAdaptiveDemux2Stream
*
* Called before starting a @stream. sub-classes can return %FALSE if more
* information is required before it can be started. Sub-classes will have to
* call gst_adaptive_demux2_stream_start() when the stream should be started.
*/
gboolean (*stream_can_start) (GstAdaptiveDemux *demux,
GstAdaptiveDemux2Stream *stream);
/**
* stream_update_tracks:
* @demux: The #GstAdaptiveDemux
* @stream: A #GstAdaptiveDemux2Stream
*
* Called whenever the base class collected a @collection on a @stream which has
* pending tracks to be created. Subclasses should override this if they
* create streams without tracks.
*
* * create the various tracks by analyzing the @stream stream_collection
* * Set the track upstream_stream_id to the corresponding stream_id from the collection
*/
void (*stream_update_tracks) (GstAdaptiveDemux *demux,
GstAdaptiveDemux2Stream *stream);
/**
* need_another_chunk:
* @stream: #GstAdaptiveDemux2Stream
*
* If chunked downloading is used (chunk_size != 0) this is called once a
* chunk is finished to decide whether more has to be downloaded or not.
* May update chunk_size to a different value
*/
gboolean (*need_another_chunk) (GstAdaptiveDemux2Stream * stream);
/**
* stream_update_fragment_info:
* @stream: #GstAdaptiveDemux2Stream
*
* Requests the stream to set the information about the current fragment to its
* current fragment struct
*
* Returns: #GST_FLOW_OK in success, #GST_FLOW_ERROR on error and #GST_FLOW_EOS
* if there is no fragment.
*/
GstFlowReturn (*stream_update_fragment_info) (GstAdaptiveDemux2Stream * stream);
/**
* stream_select_bitrate:
* @stream: #GstAdaptiveDemux2Stream
* @bitrate: the bitrate to select (in bytes per second)
*
* The stream should try to select the bitrate that is the greater, but not
* greater than the requested bitrate. If it needs a codec change it should
* create the new stream using gst_adaptive_demux2_stream_new(). If it only
* needs a caps change it should set the new caps using
* gst_adaptive_demux2_stream_set_caps().
*
* Returns: %TRUE if the stream changed bitrate, %FALSE otherwise
*/
gboolean (*stream_select_bitrate) (GstAdaptiveDemux2Stream * stream, guint64 bitrate);
/**
* stream_get_fragment_waiting_time:
* @stream: #GstAdaptiveDemux2Stream
*
* For live streams, requests how much time should be waited before starting
* to download the fragment. This is useful to avoid downloading a fragment that
* isn't available yet.
*
* Returns: The waiting time in as a #GstClockTime
*/
GstClockTime (*stream_get_fragment_waiting_time) (GstAdaptiveDemux2Stream * stream);
/**
* start_fragment:
* @demux: #GstAdaptiveDemux
* @stream: #GstAdaptiveDemux2Stream
*
* Notifies the subclass that the given stream is starting the download
* of a new fragment. Can be used to reset/init internal state that is
* needed before each fragment, like decryption engines.
*
* Returns: %TRUE if successful.
*/
gboolean (*start_fragment) (GstAdaptiveDemux * demux, GstAdaptiveDemux2Stream * stream);
/**
* finish_fragment:
* @demux: #GstAdaptiveDemux
* @stream: #GstAdaptiveDemux2Stream
*
* Notifies the subclass that a fragment download was finished.
* It can be used to cleanup internal state after a fragment and
* also push any pending data before moving to the next fragment.
*/
GstFlowReturn (*finish_fragment) (GstAdaptiveDemux * demux, GstAdaptiveDemux2Stream * stream);
/**
* data_received:
* @demux: #GstAdaptiveDemux
* @stream: #GstAdaptiveDemux2Stream
* @buffer: #GstBuffer
*
* Notifies the subclass that a fragment chunk was downloaded. The subclass
* can look at the data and modify/push data as desired.
*
* Returns: #GST_FLOW_OK if successful, #GST_FLOW_ERROR in case of error.
*/
GstFlowReturn (*data_received) (GstAdaptiveDemux * demux, GstAdaptiveDemux2Stream * stream, GstBuffer * buffer);
/**
* get_live_seek_range:
* @demux: #GstAdaptiveDemux
* @start: pointer to put the start position allowed to seek to
* @stop: pointer to put the stop position allowed to seek to
*
* Gets the allowed seek start and stop positions for the current live stream
*
* Return: %TRUE if successful
*/
gboolean (*get_live_seek_range) (GstAdaptiveDemux * demux, gint64 * start, gint64 * stop);
/**
* get_presentation_offset:
* @demux: #GstAdaptiveDemux
* @stream: #GstAdaptiveDemux2Stream
*
* Gets the delay to apply to @stream.
*
* Return: a #GstClockTime representing the (positive) time offset to apply to
* @stream.
*/
GstClockTime (*get_presentation_offset) (GstAdaptiveDemux *demux, GstAdaptiveDemux2Stream *stream);
/**
* get_period_start_time:
* @demux: #GstAdaptiveDemux
*
* Gets the start time of the current period. Timestamps are resetting to 0
* after each period but we have to maintain a continuous stream and running
* time so need to know the start time of the current period.
*
* Return: a #GstClockTime representing the start time of the currently
* selected period.
*/
GstClockTime (*get_period_start_time) (GstAdaptiveDemux *demux);
/**
* requires_periodical_playlist_update:
* @demux: #GstAdaptiveDemux
*
* Some adaptive streaming protocols allow the client to download
* the playlist once and build up the fragment list based on the
* current fragment metadata. For those protocols the demuxer
* doesn't need to periodically refresh the playlist. This vfunc
* is relevant only for live playback scenarios.
*
* Return: %TRUE if the playlist needs to be refreshed periodically by the demuxer.
*/
gboolean (*requires_periodical_playlist_update) (GstAdaptiveDemux * demux);
};
GType gst_adaptive_demux_ng_get_type (void);
GType gst_adaptive_demux2_stream_get_type (void);
gboolean gst_adaptive_demux2_add_stream (GstAdaptiveDemux *demux,
GstAdaptiveDemux2Stream *stream);
gboolean gst_adaptive_demux2_stream_add_track (GstAdaptiveDemux2Stream *stream,
GstAdaptiveDemuxTrack *track);
GstAdaptiveDemuxTrack *gst_adaptive_demux_track_new (GstAdaptiveDemux *demux,
GstStreamType type,
GstStreamFlags flags,
gchar *stream_id,
GstCaps *caps,
GstTagList *tags);
GstAdaptiveDemuxTrack *gst_adaptive_demux_track_ref (GstAdaptiveDemuxTrack *track);
void gst_adaptive_demux_track_unref (GstAdaptiveDemuxTrack *track);
void gst_adaptive_demux2_stream_set_caps (GstAdaptiveDemux2Stream * stream,
GstCaps * caps);
void gst_adaptive_demux2_stream_set_tags (GstAdaptiveDemux2Stream * stream,
GstTagList * tags);
void gst_adaptive_demux2_stream_fragment_clear (GstAdaptiveDemux2StreamFragment * f);
GstFlowReturn gst_adaptive_demux2_stream_push_buffer (GstAdaptiveDemux2Stream * stream,
GstBuffer * buffer);
GstFlowReturn gst_adaptive_demux2_stream_advance_fragment (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream,
GstClockTime duration);
gboolean gst_adaptive_demux_start_new_period (GstAdaptiveDemux * demux);
void
gst_adaptive_demux2_stream_start (GstAdaptiveDemux2Stream * stream);
void gst_adaptive_demux2_stream_queue_event (GstAdaptiveDemux2Stream * stream,
GstEvent * event);
gboolean gst_adaptive_demux2_stream_is_selected (GstAdaptiveDemux2Stream *stream);
gboolean gst_adaptive_demux2_stream_is_running (GstAdaptiveDemux2Stream * stream);
GstClockTime gst_adaptive_demux2_get_monotonic_time (GstAdaptiveDemux * demux);
GDateTime *gst_adaptive_demux2_get_client_now_utc (GstAdaptiveDemux * demux);
gboolean gst_adaptive_demux2_is_running (GstAdaptiveDemux * demux);
GstClockTime gst_adaptive_demux2_get_qos_earliest_time (GstAdaptiveDemux *demux);
GstCaps * gst_codec_utils_caps_from_iso_rfc6831 (gchar * codec);
gdouble gst_adaptive_demux_play_rate (GstAdaptiveDemux *demux);
G_END_DECLS
#endif

View file

@ -0,0 +1,679 @@
/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* 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 <stdio.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "gstadaptivedemuxutils.h"
GST_DEBUG_CATEGORY_EXTERN (adaptivedemux2_debug);
#define GST_CAT_DEFAULT adaptivedemux2_debug
struct _GstAdaptiveDemuxClock
{
gint ref_count;
GstClock *gst_clock;
GstClockTimeDiff clock_offset; /* offset between realtime_clock and UTC */
};
struct _GstAdaptiveDemuxLoop
{
gint ref_count;
GCond cond;
GMutex lock;
GRecMutex context_lock;
GThread *thread;
GMainLoop *loop;
GMainContext *context;
gboolean stopped;
gboolean paused;
};
GstAdaptiveDemuxClock *
gst_adaptive_demux_clock_new (void)
{
GstAdaptiveDemuxClock *clock = g_slice_new (GstAdaptiveDemuxClock);
GstClockType clock_type = GST_CLOCK_TYPE_OTHER;
GObjectClass *gobject_class;
g_atomic_int_set (&clock->ref_count, 1);
clock->gst_clock = gst_system_clock_obtain ();
g_assert (clock->gst_clock != NULL);
gobject_class = G_OBJECT_GET_CLASS (clock->gst_clock);
if (g_object_class_find_property (gobject_class, "clock-type")) {
g_object_get (clock->gst_clock, "clock-type", &clock_type, NULL);
} else {
GST_WARNING ("System clock does not have clock-type property");
}
if (clock_type == GST_CLOCK_TYPE_REALTIME) {
clock->clock_offset = 0;
} else {
GDateTime *utc_now;
utc_now = g_date_time_new_now_utc ();
gst_adaptive_demux_clock_set_utc_time (clock, utc_now);
g_date_time_unref (utc_now);
}
return clock;
}
GstAdaptiveDemuxClock *
gst_adaptive_demux_clock_ref (GstAdaptiveDemuxClock * clock)
{
g_return_val_if_fail (clock != NULL, NULL);
g_atomic_int_inc (&clock->ref_count);
return clock;
}
void
gst_adaptive_demux_clock_unref (GstAdaptiveDemuxClock * clock)
{
g_return_if_fail (clock != NULL);
if (g_atomic_int_dec_and_test (&clock->ref_count)) {
gst_object_unref (clock->gst_clock);
g_slice_free (GstAdaptiveDemuxClock, clock);
}
}
GstClockTime
gst_adaptive_demux_clock_get_time (GstAdaptiveDemuxClock * clock)
{
g_return_val_if_fail (clock != NULL, GST_CLOCK_TIME_NONE);
return gst_clock_get_time (clock->gst_clock);
}
GDateTime *
gst_adaptive_demux_clock_get_now_utc (GstAdaptiveDemuxClock * clock)
{
GstClockTime rtc_now;
GDateTime *unix_datetime;
GDateTime *result_datetime;
gint64 utc_now_in_us;
rtc_now = gst_clock_get_time (clock->gst_clock);
utc_now_in_us = clock->clock_offset + GST_TIME_AS_USECONDS (rtc_now);
unix_datetime =
g_date_time_new_from_unix_utc (utc_now_in_us / G_TIME_SPAN_SECOND);
result_datetime =
g_date_time_add (unix_datetime, utc_now_in_us % G_TIME_SPAN_SECOND);
g_date_time_unref (unix_datetime);
return result_datetime;
}
void
gst_adaptive_demux_clock_set_utc_time (GstAdaptiveDemuxClock * clock,
GDateTime * utc_now)
{
GstClockTime rtc_now = gst_clock_get_time (clock->gst_clock);
GstClockTimeDiff clock_offset;
clock_offset =
g_date_time_to_unix (utc_now) * G_TIME_SPAN_SECOND +
g_date_time_get_microsecond (utc_now) - GST_TIME_AS_USECONDS (rtc_now);
GST_INFO ("Changing UTC clock offset to %" GST_STIME_FORMAT
" was %" GST_STIME_FORMAT, GST_STIME_ARGS (clock_offset),
GST_STIME_ARGS (clock->clock_offset));
clock->clock_offset = clock_offset;
}
GstAdaptiveDemuxLoop *
gst_adaptive_demux_loop_new (void)
{
GstAdaptiveDemuxLoop *loop = g_slice_new0 (GstAdaptiveDemuxLoop);
g_atomic_int_set (&loop->ref_count, 1);
g_mutex_init (&loop->lock);
g_rec_mutex_init (&loop->context_lock);
g_cond_init (&loop->cond);
loop->stopped = TRUE;
loop->paused = FALSE;
return loop;
}
GstAdaptiveDemuxLoop *
gst_adaptive_demux_loop_ref (GstAdaptiveDemuxLoop * loop)
{
g_return_val_if_fail (loop != NULL, NULL);
g_atomic_int_inc (&loop->ref_count);
return loop;
}
void
gst_adaptive_demux_loop_unref (GstAdaptiveDemuxLoop * loop)
{
g_return_if_fail (loop != NULL);
if (g_atomic_int_dec_and_test (&loop->ref_count)) {
gst_adaptive_demux_loop_stop (loop, TRUE);
g_mutex_clear (&loop->lock);
g_rec_mutex_clear (&loop->context_lock);
g_cond_clear (&loop->cond);
g_slice_free (GstAdaptiveDemuxLoop, loop);
}
}
static gpointer
_gst_adaptive_demux_loop_thread (GstAdaptiveDemuxLoop * loop)
{
g_mutex_lock (&loop->lock);
loop->loop = g_main_loop_new (loop->context, FALSE);
while (!loop->stopped) {
g_mutex_unlock (&loop->lock);
g_rec_mutex_lock (&loop->context_lock);
g_main_context_push_thread_default (loop->context);
g_main_loop_run (loop->loop);
g_main_context_pop_thread_default (loop->context);
g_rec_mutex_unlock (&loop->context_lock);
g_mutex_lock (&loop->lock);
while (loop->paused)
g_cond_wait (&loop->cond, &loop->lock);
}
g_main_loop_unref (loop->loop);
loop->loop = NULL;
g_cond_broadcast (&loop->cond);
g_mutex_unlock (&loop->lock);
g_main_context_unref (loop->context);
loop->context = NULL;
gst_adaptive_demux_loop_unref (loop);
return NULL;
}
void
gst_adaptive_demux_loop_start (GstAdaptiveDemuxLoop * loop)
{
g_mutex_lock (&loop->lock);
if (loop->thread != NULL && !loop->stopped)
goto done; /* Already running */
loop->stopped = FALSE;
loop->context = g_main_context_new ();
loop->thread =
g_thread_new ("AdaptiveDemux",
(GThreadFunc) _gst_adaptive_demux_loop_thread,
gst_adaptive_demux_loop_ref (loop));
done:
g_mutex_unlock (&loop->lock);
}
static gboolean
do_quit_cb (GstAdaptiveDemuxLoop * loop)
{
g_main_loop_quit (loop->loop);
return G_SOURCE_REMOVE;
}
void
gst_adaptive_demux_loop_stop (GstAdaptiveDemuxLoop * loop, gboolean wait)
{
g_mutex_lock (&loop->lock);
loop->stopped = TRUE;
if (loop->loop != NULL) {
GSource *s = g_idle_source_new ();
g_source_set_callback (s, (GSourceFunc) do_quit_cb,
gst_adaptive_demux_loop_ref (loop),
(GDestroyNotify) gst_adaptive_demux_loop_unref);
g_source_attach (s, loop->context);
g_source_unref (s);
if (wait) {
while (loop->loop != NULL)
g_cond_wait (&loop->cond, &loop->lock);
}
}
g_mutex_unlock (&loop->lock);
}
gboolean
gst_adaptive_demux_loop_pause_and_lock (GstAdaptiveDemuxLoop * loop)
{
/* Try and acquire the context lock directly. This will succeed
* if called when the loop is not running, and we can avoid
* adding an unnecessary extra idle source to quit the loop. */
if (!g_rec_mutex_trylock (&loop->context_lock)) {
g_mutex_lock (&loop->lock);
if (loop->stopped) {
g_mutex_unlock (&loop->lock);
return FALSE;
}
loop->paused = TRUE;
{
GSource *s = g_idle_source_new ();
g_source_set_callback (s, (GSourceFunc) do_quit_cb,
gst_adaptive_demux_loop_ref (loop),
(GDestroyNotify) gst_adaptive_demux_loop_unref);
g_source_attach (s, loop->context);
g_source_unref (s);
}
g_mutex_unlock (&loop->lock);
g_rec_mutex_lock (&loop->context_lock);
}
g_main_context_push_thread_default (loop->context);
return TRUE;
}
gboolean
gst_adaptive_demux_loop_unlock_and_unpause (GstAdaptiveDemuxLoop * loop)
{
g_main_context_pop_thread_default (loop->context);
g_rec_mutex_unlock (&loop->context_lock);
g_mutex_lock (&loop->lock);
loop->paused = FALSE;
if (loop->stopped) {
g_mutex_unlock (&loop->lock);
return FALSE;
}
/* Wake up the loop to run again */
g_cond_broadcast (&loop->cond);
g_mutex_unlock (&loop->lock);
return TRUE;
}
guint
gst_adaptive_demux_loop_call (GstAdaptiveDemuxLoop * loop, GSourceFunc func,
gpointer data, GDestroyNotify notify)
{
guint ret = 0;
g_mutex_lock (&loop->lock);
if (loop->context) {
GSource *s = g_idle_source_new ();
g_source_set_callback (s, func, data, notify);
ret = g_source_attach (s, loop->context);
g_source_unref (s);
} else if (notify != NULL) {
notify (data);
}
g_mutex_unlock (&loop->lock);
return ret;
}
guint
gst_adaptive_demux_loop_call_delayed (GstAdaptiveDemuxLoop * loop,
GstClockTime delay, GSourceFunc func, gpointer data, GDestroyNotify notify)
{
guint ret = 0;
g_mutex_lock (&loop->lock);
if (loop->context) {
GSource *s = g_timeout_source_new (GST_TIME_AS_MSECONDS (delay));
g_source_set_callback (s, func, data, notify);
ret = g_source_attach (s, loop->context);
g_source_unref (s);
} else if (notify != NULL) {
notify (data);
}
g_mutex_unlock (&loop->lock);
return ret;
}
void
gst_adaptive_demux_loop_cancel_call (GstAdaptiveDemuxLoop * loop, guint cb_id)
{
GSource *s;
g_mutex_lock (&loop->lock);
s = g_main_context_find_source_by_id (loop->context, cb_id);
if (s)
g_source_destroy (s);
g_mutex_unlock (&loop->lock);
}
struct Rfc5322TimeZone
{
const gchar *name;
gfloat tzoffset;
};
/*
Parse an RFC5322 (section 3.3) date-time from the Date: field in the
HTTP response.
See https://tools.ietf.org/html/rfc5322#section-3.3
*/
GstDateTime *
gst_adaptive_demux_util_parse_http_head_date (const gchar * http_date)
{
static const gchar *months[] = { NULL, "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec", NULL
};
static const struct Rfc5322TimeZone timezones[] = {
{"Z", 0},
{"UT", 0},
{"GMT", 0},
{"BST", 1},
{"EST", -5},
{"EDT", -4},
{"CST", -6},
{"CDT", -5},
{"MST", -7},
{"MDT", -6},
{"PST", -8},
{"PDT", -7},
{NULL, 0}
};
gint ret;
const gchar *pos;
gint year = -1, month = -1, day = -1, hour = -1, minute = -1, second = -1;
gchar zone[6];
gchar monthstr[4];
gfloat tzoffset = 0;
gboolean parsed_tz = FALSE;
g_return_val_if_fail (http_date != NULL, NULL);
/* skip optional text version of day of the week */
pos = strchr (http_date, ',');
if (pos)
pos++;
else
pos = http_date;
ret =
sscanf (pos, "%02d %3s %04d %02d:%02d:%02d %5s", &day, monthstr, &year,
&hour, &minute, &second, zone);
if (ret == 7) {
gchar *z = zone;
gint i;
for (i = 1; months[i]; ++i) {
if (g_ascii_strncasecmp (months[i], monthstr, strlen (months[i])) == 0) {
month = i;
break;
}
}
for (i = 0; timezones[i].name && !parsed_tz; ++i) {
if (g_ascii_strncasecmp (timezones[i].name, z,
strlen (timezones[i].name)) == 0) {
tzoffset = timezones[i].tzoffset;
parsed_tz = TRUE;
}
}
if (!parsed_tz) {
gint hh, mm;
gboolean neg = FALSE;
/* check if it is in the form +-HHMM */
if (*z == '+' || *z == '-') {
if (*z == '+')
++z;
else if (*z == '-') {
++z;
neg = TRUE;
}
ret = sscanf (z, "%02d%02d", &hh, &mm);
if (ret == 2) {
tzoffset = hh;
tzoffset += mm / 60.0;
if (neg)
tzoffset = -tzoffset;
parsed_tz = TRUE;
}
}
}
/* Accept year in both 2 digit or 4 digit format */
if (year < 100)
year += 2000;
}
if (month < 1 || !parsed_tz)
return NULL;
return gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
}
typedef struct
{
gboolean delivered;
GstEvent *event;
} PadEvent;
void
gst_event_store_init (GstEventStore * store)
{
store->events = g_array_sized_new (FALSE, TRUE, sizeof (PadEvent), 16);
store->events_pending = FALSE;
}
void
gst_event_store_flush (GstEventStore * store)
{
guint i, len;
GArray *events = store->events;
len = events->len;
for (i = 0; i < len; i++) {
PadEvent *ev = &g_array_index (events, PadEvent, i);
GstEvent *event = ev->event;
ev->event = NULL;
gst_event_unref (event);
}
g_array_set_size (events, 0);
store->events_pending = FALSE;
}
void
gst_event_store_deinit (GstEventStore * store)
{
gst_event_store_flush (store);
g_array_free (store->events, TRUE);
}
void
gst_event_store_insert_event (GstEventStore * store, GstEvent * event,
gboolean delivered)
{
guint i, len;
GstEventType type;
GArray *events;
GQuark name_id = 0;
gboolean insert = TRUE;
type = GST_EVENT_TYPE (event);
if (type & GST_EVENT_TYPE_STICKY_MULTI)
name_id = gst_structure_get_name_id (gst_event_get_structure (event));
events = store->events;
len = events->len;
for (i = 0; i < len; i++) {
PadEvent *ev = &g_array_index (events, PadEvent, i);
if (ev->event == NULL)
continue;
if (type == GST_EVENT_TYPE (ev->event)) {
/* matching types, check matching name if needed */
if (name_id && !gst_event_has_name_id (ev->event, name_id))
continue;
/* overwrite if different */
if (gst_event_replace (&ev->event, event)) {
ev->delivered = delivered;
/* If the event was not delivered, mark that we have a pending
* undelivered event */
if (!delivered)
store->events_pending = TRUE;
}
insert = FALSE;
break;
}
if (type < GST_EVENT_TYPE (ev->event) || (type != GST_EVENT_TYPE (ev->event)
&& GST_EVENT_TYPE (ev->event) == GST_EVENT_EOS)) {
/* STREAM_START, CAPS and SEGMENT must be delivered in this order. By
* storing the sticky ordered we can check that this is respected. */
if (G_UNLIKELY (GST_EVENT_TYPE (ev->event) <= GST_EVENT_SEGMENT
|| GST_EVENT_TYPE (ev->event) == GST_EVENT_EOS))
g_warning (G_STRLOC
":%s:<store %p> Sticky event misordering, got '%s' before '%s'",
G_STRFUNC, store,
gst_event_type_get_name (GST_EVENT_TYPE (ev->event)),
gst_event_type_get_name (type));
break;
}
}
if (insert) {
PadEvent ev;
ev.event = gst_event_ref (event);
ev.delivered = delivered;
g_array_insert_val (events, i, ev);
/* If the event was not delivered, mark that we have a pending
* undelivered event */
if (!delivered)
store->events_pending = TRUE;
GST_LOG ("store %p stored sticky event %s", store,
GST_EVENT_TYPE_NAME (event));
}
}
/* Find the first non-pending event and return a ref to it, owned by the caller */
GstEvent *
gst_event_store_get_next_pending (GstEventStore * store)
{
GArray *events;
guint i, len;
if (!store->events_pending)
return NULL;
events = store->events;
len = events->len;
for (i = 0; i < len; i++) {
PadEvent *ev = &g_array_index (events, PadEvent, i);
if (ev->event == NULL || ev->delivered)
continue;
/* Found an undelivered event, return it. The caller will mark it
* as delivered once it has done so successfully by calling
* gst_event_store_mark_delivered() */
return gst_event_ref (ev->event);
}
store->events_pending = FALSE;
return NULL;
}
void
gst_event_store_mark_delivered (GstEventStore * store, GstEvent * event)
{
gboolean events_pending = FALSE;
GArray *events;
guint i, len;
events = store->events;
len = events->len;
for (i = 0; i < len; i++) {
PadEvent *ev = &g_array_index (events, PadEvent, i);
if (ev->event == NULL)
continue;
/* Check if there are any pending events other than
* the passed one, so we can update the events_pending
* flag at the end */
if (ev->event != event && !ev->delivered) {
events_pending = TRUE;
continue;
}
ev->delivered = TRUE;
}
store->events_pending = events_pending;
}
void
gst_event_store_mark_all_undelivered (GstEventStore * store)
{
gboolean events_pending = FALSE;
GArray *events;
guint i, len;
events = store->events;
len = events->len;
for (i = 0; i < len; i++) {
PadEvent *ev = &g_array_index (events, PadEvent, i);
if (ev->event == NULL)
continue;
ev->delivered = FALSE;
events_pending = TRUE;
}
/* Only set the flag if there was at least
* one sticky event in the store */
store->events_pending = events_pending;
}

View file

@ -0,0 +1,75 @@
/* GStreamer
*
* Copyright (C) 2014 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _GST_ADAPTIVE_DEMUX_UTILS_H_
#define _GST_ADAPTIVE_DEMUX_UTILS_H_
#include <gst/gst.h>
typedef struct _GstAdaptiveDemuxClock GstAdaptiveDemuxClock;
typedef struct _GstAdaptiveDemuxLoop GstAdaptiveDemuxLoop;
GstAdaptiveDemuxClock *gst_adaptive_demux_clock_new (void);
GstAdaptiveDemuxClock *gst_adaptive_demux_clock_ref (GstAdaptiveDemuxClock *
clock);
void gst_adaptive_demux_clock_unref (GstAdaptiveDemuxClock * clock);
GstClockTime gst_adaptive_demux_clock_get_time (GstAdaptiveDemuxClock * clock);
GDateTime *gst_adaptive_demux_clock_get_now_utc (GstAdaptiveDemuxClock * clock);
void gst_adaptive_demux_clock_set_utc_time (GstAdaptiveDemuxClock * clock, GDateTime *utc_now);
GstAdaptiveDemuxLoop *gst_adaptive_demux_loop_new (void);
GstAdaptiveDemuxLoop *gst_adaptive_demux_loop_ref (GstAdaptiveDemuxLoop * loop);
void gst_adaptive_demux_loop_unref (GstAdaptiveDemuxLoop * loop);
void gst_adaptive_demux_loop_start (GstAdaptiveDemuxLoop *loop);
void gst_adaptive_demux_loop_stop (GstAdaptiveDemuxLoop * loop, gboolean wait);
guint gst_adaptive_demux_loop_call (GstAdaptiveDemuxLoop *loop, GSourceFunc func,
gpointer data, GDestroyNotify notify);
guint gst_adaptive_demux_loop_call_delayed (GstAdaptiveDemuxLoop *loop, GstClockTime delay,
GSourceFunc func, gpointer data, GDestroyNotify notify);
void gst_adaptive_demux_loop_cancel_call (GstAdaptiveDemuxLoop *loop, guint cb_id);
gboolean gst_adaptive_demux_loop_pause_and_lock (GstAdaptiveDemuxLoop * loop);
gboolean gst_adaptive_demux_loop_unlock_and_unpause (GstAdaptiveDemuxLoop * loop);
GstDateTime *gst_adaptive_demux_util_parse_http_head_date (const gchar *http_date);
typedef struct _GstEventStore GstEventStore;
struct _GstEventStore {
GArray *events;
gboolean events_pending;
};
void gst_event_store_init(GstEventStore *store);
void gst_event_store_deinit(GstEventStore *store);
void gst_event_store_flush(GstEventStore *store);
void gst_event_store_insert_event (GstEventStore *store, GstEvent * event, gboolean delivered);
GstEvent *gst_event_store_get_next_pending (GstEventStore *store);
void gst_event_store_mark_delivered (GstEventStore *store, GstEvent *event);
void gst_event_store_mark_all_undelivered (GstEventStore *store);
#endif

View file

@ -0,0 +1,979 @@
/*
* ISO File Format parsing library
*
* gstisoff.h
*
* Copyright (C) 2015 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstisoff.h"
#include <gst/base/gstbytereader.h>
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_isoff_debug);
#define GST_CAT_DEFAULT gst_isoff_debug
static gboolean initialized = FALSE;
#define INITIALIZE_DEBUG_CATEGORY \
if (!initialized) { \
GST_DEBUG_CATEGORY_INIT (gst_isoff_debug, "isoff", 0, \
"ISO File Format parsing library"); \
initialized = TRUE; \
}
static const guint8 tfrf_uuid[] = {
0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95,
0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f
};
static const guint8 tfxd_uuid[] = {
0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
};
/* gst_isoff_parse_box_header:
* @reader:
* @type: type that was found at the current position
* @extended_type: (allow-none): extended type if type=='uuid'
* @header_size: (allow-none): size of the box header (type, extended type and size)
* @size: size of the complete box including type, extended type and size
*
* Advances the byte reader to the start of the box content. To skip
* over the complete box, skip size - header_size bytes.
*
* Returns: TRUE if a box header could be parsed, FALSE if more data is needed
*/
gboolean
gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type,
guint8 extended_type[16], guint * header_size, guint64 * size)
{
guint header_start_offset;
guint32 size_field;
INITIALIZE_DEBUG_CATEGORY;
header_start_offset = gst_byte_reader_get_pos (reader);
if (gst_byte_reader_get_remaining (reader) < 8)
goto not_enough_data;
size_field = gst_byte_reader_get_uint32_be_unchecked (reader);
*type = gst_byte_reader_get_uint32_le_unchecked (reader);
if (size_field == 1) {
if (gst_byte_reader_get_remaining (reader) < 8)
goto not_enough_data;
*size = gst_byte_reader_get_uint64_be_unchecked (reader);
} else {
*size = size_field;
}
if (*type == GST_ISOFF_FOURCC_UUID) {
if (gst_byte_reader_get_remaining (reader) < 16)
goto not_enough_data;
if (extended_type)
memcpy (extended_type, gst_byte_reader_get_data_unchecked (reader, 16),
16);
}
if (header_size)
*header_size = gst_byte_reader_get_pos (reader) - header_start_offset;
return TRUE;
not_enough_data:
gst_byte_reader_set_pos (reader, header_start_offset);
return FALSE;
}
static void
gst_isoff_trun_box_clear (GstTrunBox * trun)
{
if (trun->samples)
g_array_free (trun->samples, TRUE);
}
static void
gst_isoff_tfrf_box_free (GstTfrfBox * tfrf)
{
if (tfrf->entries)
g_array_free (tfrf->entries, TRUE);
g_free (tfrf);
}
static void
gst_isoff_traf_box_clear (GstTrafBox * traf)
{
if (traf->trun)
g_array_free (traf->trun, TRUE);
if (traf->tfrf)
gst_isoff_tfrf_box_free (traf->tfrf);
g_free (traf->tfxd);
traf->trun = NULL;
traf->tfrf = NULL;
traf->tfxd = NULL;
}
static gboolean
gst_isoff_mfhd_box_parse (GstMfhdBox * mfhd, GstByteReader * reader)
{
guint8 version;
guint32 flags;
if (gst_byte_reader_get_remaining (reader) != 8)
return FALSE;
version = gst_byte_reader_get_uint8_unchecked (reader);
if (version != 0)
return FALSE;
flags = gst_byte_reader_get_uint24_be_unchecked (reader);
if (flags != 0)
return FALSE;
mfhd->sequence_number = gst_byte_reader_get_uint32_be_unchecked (reader);
return TRUE;
}
static gboolean
gst_isoff_tfhd_box_parse (GstTfhdBox * tfhd, GstByteReader * reader)
{
memset (tfhd, 0, sizeof (*tfhd));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
tfhd->version = gst_byte_reader_get_uint8_unchecked (reader);
if (tfhd->version != 0)
return FALSE;
tfhd->flags = gst_byte_reader_get_uint24_be_unchecked (reader);
if (!gst_byte_reader_get_uint32_be (reader, &tfhd->track_id))
return FALSE;
if ((tfhd->flags & GST_TFHD_FLAGS_BASE_DATA_OFFSET_PRESENT) &&
!gst_byte_reader_get_uint64_be (reader, &tfhd->base_data_offset))
return FALSE;
if ((tfhd->flags & GST_TFHD_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &tfhd->sample_description_index))
return FALSE;
if ((tfhd->flags & GST_TFHD_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &tfhd->default_sample_duration))
return FALSE;
if ((tfhd->flags & GST_TFHD_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &tfhd->default_sample_size))
return FALSE;
if ((tfhd->flags & GST_TFHD_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &tfhd->default_sample_flags))
return FALSE;
return TRUE;
}
static gboolean
gst_isoff_trun_box_parse (GstTrunBox * trun, GstByteReader * reader)
{
gint i;
memset (trun, 0, sizeof (*trun));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
trun->version = gst_byte_reader_get_uint8_unchecked (reader);
if (trun->version != 0 && trun->version != 1)
return FALSE;
trun->flags = gst_byte_reader_get_uint24_be_unchecked (reader);
if (!gst_byte_reader_get_uint32_be (reader, &trun->sample_count))
return FALSE;
trun->samples =
g_array_sized_new (FALSE, FALSE, sizeof (GstTrunSample),
trun->sample_count);
if ((trun->flags & GST_TRUN_FLAGS_DATA_OFFSET_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, (guint32 *) & trun->data_offset))
return FALSE;
if ((trun->flags & GST_TRUN_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &trun->first_sample_flags))
return FALSE;
for (i = 0; i < trun->sample_count; i++) {
GstTrunSample sample = { 0, };
if ((trun->flags & GST_TRUN_FLAGS_SAMPLE_DURATION_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &sample.sample_duration))
goto error;
if ((trun->flags & GST_TRUN_FLAGS_SAMPLE_SIZE_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &sample.sample_size))
goto error;
if ((trun->flags & GST_TRUN_FLAGS_SAMPLE_FLAGS_PRESENT) &&
!gst_byte_reader_get_uint32_be (reader, &sample.sample_flags))
goto error;
if ((trun->flags & GST_TRUN_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT)
&& !gst_byte_reader_get_uint32_be (reader,
&sample.sample_composition_time_offset.u))
goto error;
g_array_append_val (trun->samples, sample);
}
return TRUE;
error:
gst_isoff_trun_box_clear (trun);
return FALSE;
}
static gboolean
gst_isoff_tfdt_box_parse (GstTfdtBox * tfdt, GstByteReader * reader)
{
gint8 version;
memset (tfdt, 0, sizeof (*tfdt));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
version = gst_byte_reader_get_uint8_unchecked (reader);
if (!gst_byte_reader_skip (reader, 3))
return FALSE;
if (version == 1) {
if (!gst_byte_reader_get_uint64_be (reader, &tfdt->decode_time))
return FALSE;
} else {
guint32 dec_time = 0;
if (!gst_byte_reader_get_uint32_be (reader, &dec_time))
return FALSE;
tfdt->decode_time = dec_time;
}
return TRUE;
}
static gboolean
gst_isoff_tfxd_box_parse (GstTfxdBox * tfxd, GstByteReader * reader)
{
guint8 version;
guint32 flags = 0;
guint64 absolute_time = 0;
guint64 absolute_duration = 0;
memset (tfxd, 0, sizeof (*tfxd));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
if (!gst_byte_reader_get_uint8 (reader, &version)) {
GST_ERROR ("Error getting box's version field");
return FALSE;
}
if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
GST_ERROR ("Error getting box's flags field");
return FALSE;
}
tfxd->version = version;
tfxd->flags = flags;
if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8))
return FALSE;
if (version & 0x01) {
gst_byte_reader_get_uint64_be (reader, &absolute_time);
gst_byte_reader_get_uint64_be (reader, &absolute_duration);
} else {
guint32 time = 0;
guint32 duration = 0;
gst_byte_reader_get_uint32_be (reader, &time);
gst_byte_reader_get_uint32_be (reader, &duration);
absolute_time = time;
absolute_duration = duration;
}
tfxd->time = absolute_time;
tfxd->duration = absolute_duration;
return TRUE;
}
static gboolean
gst_isoff_tfrf_box_parse (GstTfrfBox * tfrf, GstByteReader * reader)
{
guint8 version;
guint32 flags = 0;
guint8 fragment_count = 0;
guint8 index = 0;
memset (tfrf, 0, sizeof (*tfrf));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
if (!gst_byte_reader_get_uint8 (reader, &version)) {
GST_ERROR ("Error getting box's version field");
return FALSE;
}
if (!gst_byte_reader_get_uint24_be (reader, &flags)) {
GST_ERROR ("Error getting box's flags field");
return FALSE;
}
tfrf->version = version;
tfrf->flags = flags;
if (!gst_byte_reader_get_uint8 (reader, &fragment_count))
return FALSE;
tfrf->entries_count = fragment_count;
tfrf->entries =
g_array_sized_new (FALSE, FALSE, sizeof (GstTfrfBoxEntry),
tfrf->entries_count);
for (index = 0; index < fragment_count; index++) {
GstTfrfBoxEntry entry = { 0, };
guint64 absolute_time = 0;
guint64 absolute_duration = 0;
if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8))
return FALSE;
if (version & 0x01) {
if (!gst_byte_reader_get_uint64_be (reader, &absolute_time) ||
!gst_byte_reader_get_uint64_be (reader, &absolute_duration)) {
return FALSE;
}
} else {
guint32 time = 0;
guint32 duration = 0;
if (!gst_byte_reader_get_uint32_be (reader, &time) ||
!gst_byte_reader_get_uint32_be (reader, &duration)) {
return FALSE;
}
absolute_time = time;
absolute_duration = duration;
}
entry.time = absolute_time;
entry.duration = absolute_duration;
g_array_append_val (tfrf->entries, entry);
}
return TRUE;
}
static gboolean
gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader)
{
gboolean had_tfhd = FALSE;
memset (traf, 0, sizeof (*traf));
traf->trun = g_array_new (FALSE, FALSE, sizeof (GstTrunBox));
g_array_set_clear_func (traf->trun,
(GDestroyNotify) gst_isoff_trun_box_clear);
traf->tfdt.decode_time = GST_CLOCK_TIME_NONE;
while (gst_byte_reader_get_remaining (reader) > 0) {
guint32 fourcc;
guint header_size;
guint64 size;
GstByteReader sub_reader;
guint8 extended_type[16] = { 0, };
if (!gst_isoff_parse_box_header (reader, &fourcc, extended_type,
&header_size, &size))
goto error;
if (gst_byte_reader_get_remaining (reader) < size - header_size)
goto error;
switch (fourcc) {
case GST_ISOFF_FOURCC_TFHD:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_tfhd_box_parse (&traf->tfhd, &sub_reader))
goto error;
had_tfhd = TRUE;
break;
}
case GST_ISOFF_FOURCC_TFDT:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_tfdt_box_parse (&traf->tfdt, &sub_reader))
goto error;
break;
}
case GST_ISOFF_FOURCC_TRUN:{
GstTrunBox trun;
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_trun_box_parse (&trun, &sub_reader))
goto error;
g_array_append_val (traf->trun, trun);
break;
}
case GST_ISOFF_FOURCC_UUID:{
/* smooth-streaming specific */
if (memcmp (extended_type, tfrf_uuid, 16) == 0) {
if (traf->tfrf)
gst_isoff_tfrf_box_free (traf->tfrf);
traf->tfrf = g_new0 (GstTfrfBox, 1);
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_tfrf_box_parse (traf->tfrf, &sub_reader))
goto error;
} else if (memcmp (extended_type, tfxd_uuid, 16) == 0) {
if (traf->tfxd)
g_free (traf->tfxd);
traf->tfxd = g_new0 (GstTfxdBox, 1);
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_tfxd_box_parse (traf->tfxd, &sub_reader))
goto error;
} else {
gst_byte_reader_skip (reader, size - header_size);
}
break;
}
default:
gst_byte_reader_skip (reader, size - header_size);
break;
}
}
if (!had_tfhd)
goto error;
return TRUE;
error:
gst_isoff_traf_box_clear (traf);
return FALSE;
}
GstMoofBox *
gst_isoff_moof_box_parse (GstByteReader * reader)
{
GstMoofBox *moof;
gboolean had_mfhd = FALSE;
GstByteReader sub_reader;
INITIALIZE_DEBUG_CATEGORY;
moof = g_new0 (GstMoofBox, 1);
moof->traf = g_array_new (FALSE, FALSE, sizeof (GstTrafBox));
g_array_set_clear_func (moof->traf,
(GDestroyNotify) gst_isoff_traf_box_clear);
while (gst_byte_reader_get_remaining (reader) > 0) {
guint32 fourcc;
guint header_size;
guint64 size;
if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
&size))
goto error;
if (gst_byte_reader_get_remaining (reader) < size - header_size)
goto error;
switch (fourcc) {
case GST_ISOFF_FOURCC_MFHD:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_mfhd_box_parse (&moof->mfhd, &sub_reader))
goto error;
had_mfhd = TRUE;
break;
}
case GST_ISOFF_FOURCC_TRAF:{
GstTrafBox traf;
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_traf_box_parse (&traf, &sub_reader))
goto error;
g_array_append_val (moof->traf, traf);
break;
}
default:
gst_byte_reader_skip (reader, size - header_size);
break;
}
}
if (!had_mfhd)
goto error;
return moof;
error:
gst_isoff_moof_box_free (moof);
return NULL;
}
void
gst_isoff_moof_box_free (GstMoofBox * moof)
{
g_array_free (moof->traf, TRUE);
g_free (moof);
}
static gboolean
gst_isoff_mdhd_box_parse (GstMdhdBox * mdhd, GstByteReader * reader)
{
guint8 version;
memset (mdhd, 0, sizeof (*mdhd));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
version = gst_byte_reader_get_uint8_unchecked (reader);
if (!gst_byte_reader_skip (reader, 3))
return FALSE;
/* skip {creation, modification}_time, we don't have interest */
if (version == 1) {
if (!gst_byte_reader_skip (reader, 16))
return FALSE;
} else {
if (!gst_byte_reader_skip (reader, 8))
return FALSE;
}
if (!gst_byte_reader_get_uint32_be (reader, &mdhd->timescale))
return FALSE;
return TRUE;
}
static gboolean
gst_isoff_hdlr_box_parse (GstHdlrBox * hdlr, GstByteReader * reader)
{
memset (hdlr, 0, sizeof (*hdlr));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
/* version & flag */
if (!gst_byte_reader_skip (reader, 4))
return FALSE;
/* pre_defined = 0 */
if (!gst_byte_reader_skip (reader, 4))
return FALSE;
if (!gst_byte_reader_get_uint32_le (reader, &hdlr->handler_type))
return FALSE;
return TRUE;
}
static gboolean
gst_isoff_mdia_box_parse (GstMdiaBox * mdia, GstByteReader * reader)
{
gboolean had_mdhd = FALSE, had_hdlr = FALSE;
while (gst_byte_reader_get_remaining (reader) > 0) {
guint32 fourcc;
guint header_size;
guint64 size;
GstByteReader sub_reader;
if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
&size))
return FALSE;
if (gst_byte_reader_get_remaining (reader) < size - header_size)
return FALSE;
switch (fourcc) {
case GST_ISOFF_FOURCC_MDHD:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_mdhd_box_parse (&mdia->mdhd, &sub_reader))
return FALSE;
had_mdhd = TRUE;
break;
}
case GST_ISOFF_FOURCC_HDLR:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_hdlr_box_parse (&mdia->hdlr, &sub_reader))
return FALSE;
had_hdlr = TRUE;
break;
}
default:
gst_byte_reader_skip (reader, size - header_size);
break;
}
}
if (!had_mdhd || !had_hdlr)
return FALSE;
return TRUE;
}
static gboolean
gst_isoff_tkhd_box_parse (GstTkhdBox * tkhd, GstByteReader * reader)
{
guint8 version;
memset (tkhd, 0, sizeof (*tkhd));
if (gst_byte_reader_get_remaining (reader) < 4)
return FALSE;
if (!gst_byte_reader_get_uint8 (reader, &version))
return FALSE;
if (!gst_byte_reader_skip (reader, 3))
return FALSE;
/* skip {creation, modification}_time, we don't have interest */
if (version == 1) {
if (!gst_byte_reader_skip (reader, 16))
return FALSE;
} else {
if (!gst_byte_reader_skip (reader, 8))
return FALSE;
}
if (!gst_byte_reader_get_uint32_be (reader, &tkhd->track_id))
return FALSE;
return TRUE;
}
static gboolean
gst_isoff_trak_box_parse (GstTrakBox * trak, GstByteReader * reader)
{
gboolean had_mdia = FALSE, had_tkhd = FALSE;
while (gst_byte_reader_get_remaining (reader) > 0) {
guint32 fourcc;
guint header_size;
guint64 size;
GstByteReader sub_reader;
if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
&size))
return FALSE;
if (gst_byte_reader_get_remaining (reader) < size - header_size)
return FALSE;
switch (fourcc) {
case GST_ISOFF_FOURCC_MDIA:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_mdia_box_parse (&trak->mdia, &sub_reader))
return FALSE;
had_mdia = TRUE;
break;
}
case GST_ISOFF_FOURCC_TKHD:{
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_tkhd_box_parse (&trak->tkhd, &sub_reader))
return FALSE;
had_tkhd = TRUE;
break;
}
default:
gst_byte_reader_skip (reader, size - header_size);
break;
}
}
if (!had_tkhd || !had_mdia)
return FALSE;
return TRUE;
}
GstMoovBox *
gst_isoff_moov_box_parse (GstByteReader * reader)
{
GstMoovBox *moov;
gboolean had_trak = FALSE;
moov = g_new0 (GstMoovBox, 1);
moov->trak = g_array_new (FALSE, FALSE, sizeof (GstTrakBox));
while (gst_byte_reader_get_remaining (reader) > 0) {
guint32 fourcc;
guint header_size;
guint64 size;
if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size,
&size))
goto error;
if (gst_byte_reader_get_remaining (reader) < size - header_size)
goto error;
switch (fourcc) {
case GST_ISOFF_FOURCC_TRAK:{
GstByteReader sub_reader;
GstTrakBox trak;
gst_byte_reader_get_sub_reader (reader, &sub_reader,
size - header_size);
if (!gst_isoff_trak_box_parse (&trak, &sub_reader))
goto error;
had_trak = TRUE;
g_array_append_val (moov->trak, trak);
break;
}
default:
gst_byte_reader_skip (reader, size - header_size);
break;
}
}
if (!had_trak)
goto error;
return moov;
error:
gst_isoff_moov_box_free (moov);
return NULL;
}
void
gst_isoff_moov_box_free (GstMoovBox * moov)
{
g_array_free (moov->trak, TRUE);
g_free (moov);
}
void
gst_isoff_sidx_parser_init (GstSidxParser * parser)
{
parser->status = GST_ISOFF_SIDX_PARSER_INIT;
parser->cumulative_entry_size = 0;
parser->sidx.entries = NULL;
parser->sidx.entries_count = 0;
}
void
gst_isoff_sidx_parser_clear (GstSidxParser * parser)
{
g_free (parser->sidx.entries);
memset (parser, 0, sizeof (*parser));
gst_isoff_sidx_parser_init (parser);
}
static void
gst_isoff_parse_sidx_entry (GstSidxBoxEntry * entry, GstByteReader * reader)
{
guint32 aux;
aux = gst_byte_reader_get_uint32_be_unchecked (reader);
entry->ref_type = aux >> 31;
entry->size = aux & 0x7FFFFFFF;
entry->duration = gst_byte_reader_get_uint32_be_unchecked (reader);
aux = gst_byte_reader_get_uint32_be_unchecked (reader);
entry->starts_with_sap = aux >> 31;
entry->sap_type = ((aux >> 28) & 0x7);
entry->sap_delta_time = aux & 0xFFFFFFF;
}
GstIsoffParserResult
gst_isoff_sidx_parser_parse (GstSidxParser * parser,
GstByteReader * reader, guint * consumed)
{
GstIsoffParserResult res = GST_ISOFF_PARSER_OK;
gsize remaining;
INITIALIZE_DEBUG_CATEGORY;
switch (parser->status) {
case GST_ISOFF_SIDX_PARSER_INIT:
/* Try again once we have enough data for the FullBox header */
if (gst_byte_reader_get_remaining (reader) < 4) {
gst_byte_reader_set_pos (reader, 0);
break;
}
parser->sidx.version = gst_byte_reader_get_uint8_unchecked (reader);
parser->sidx.flags = gst_byte_reader_get_uint24_le_unchecked (reader);
parser->status = GST_ISOFF_SIDX_PARSER_HEADER;
case GST_ISOFF_SIDX_PARSER_HEADER:
remaining = gst_byte_reader_get_remaining (reader);
if (remaining < 12 + (parser->sidx.version == 0 ? 8 : 16)) {
break;
}
parser->sidx.ref_id = gst_byte_reader_get_uint32_be_unchecked (reader);
parser->sidx.timescale = gst_byte_reader_get_uint32_be_unchecked (reader);
if (parser->sidx.version == 0) {
parser->sidx.earliest_pts =
gst_byte_reader_get_uint32_be_unchecked (reader);
parser->sidx.first_offset =
gst_byte_reader_get_uint32_be_unchecked (reader);
} else {
parser->sidx.earliest_pts =
gst_byte_reader_get_uint64_be_unchecked (reader);
parser->sidx.first_offset =
gst_byte_reader_get_uint64_be_unchecked (reader);
}
/* skip 2 reserved bytes */
gst_byte_reader_skip_unchecked (reader, 2);
parser->sidx.entries_count =
gst_byte_reader_get_uint16_be_unchecked (reader);
GST_LOG ("Timescale: %" G_GUINT32_FORMAT, parser->sidx.timescale);
GST_LOG ("Earliest pts: %" G_GUINT64_FORMAT, parser->sidx.earliest_pts);
GST_LOG ("First offset: %" G_GUINT64_FORMAT, parser->sidx.first_offset);
parser->cumulative_pts =
gst_util_uint64_scale_int_round (parser->sidx.earliest_pts,
GST_SECOND, parser->sidx.timescale);
if (parser->sidx.entries_count) {
parser->sidx.entries =
g_malloc (sizeof (GstSidxBoxEntry) * parser->sidx.entries_count);
}
parser->sidx.entry_index = 0;
parser->status = GST_ISOFF_SIDX_PARSER_DATA;
case GST_ISOFF_SIDX_PARSER_DATA:
while (parser->sidx.entry_index < parser->sidx.entries_count) {
GstSidxBoxEntry *entry =
&parser->sidx.entries[parser->sidx.entry_index];
remaining = gst_byte_reader_get_remaining (reader);
if (remaining < 12)
break;
entry->offset = parser->cumulative_entry_size;
entry->pts = parser->cumulative_pts;
gst_isoff_parse_sidx_entry (entry, reader);
entry->duration = gst_util_uint64_scale_int_round (entry->duration,
GST_SECOND, parser->sidx.timescale);
parser->cumulative_entry_size += entry->size;
parser->cumulative_pts += entry->duration;
GST_LOG ("Sidx entry %d) offset: %" G_GUINT64_FORMAT ", pts: %"
GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT " - size %"
G_GUINT32_FORMAT, parser->sidx.entry_index, entry->offset,
GST_TIME_ARGS (entry->pts), GST_TIME_ARGS (entry->duration),
entry->size);
parser->sidx.entry_index++;
}
if (parser->sidx.entry_index == parser->sidx.entries_count)
parser->status = GST_ISOFF_SIDX_PARSER_FINISHED;
else
break;
case GST_ISOFF_SIDX_PARSER_FINISHED:
parser->sidx.entry_index = 0;
res = GST_ISOFF_PARSER_DONE;
break;
}
*consumed = gst_byte_reader_get_pos (reader);
return res;
}
GstIsoffParserResult
gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buffer,
guint * consumed)
{
GstIsoffParserResult res = GST_ISOFF_PARSER_OK;
GstByteReader reader;
GstMapInfo info;
guint32 fourcc;
INITIALIZE_DEBUG_CATEGORY;
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
*consumed = 0;
return GST_ISOFF_PARSER_ERROR;
}
gst_byte_reader_init (&reader, info.data, info.size);
if (parser->status == GST_ISOFF_SIDX_PARSER_INIT) {
if (!gst_isoff_parse_box_header (&reader, &fourcc, NULL, NULL,
&parser->size))
goto done;
if (fourcc != GST_ISOFF_FOURCC_SIDX) {
res = GST_ISOFF_PARSER_UNEXPECTED;
gst_byte_reader_set_pos (&reader, 0);
goto done;
}
if (parser->size == 0) {
res = GST_ISOFF_PARSER_ERROR;
gst_byte_reader_set_pos (&reader, 0);
goto done;
}
/* Try again once we have enough data for the FullBox header */
if (gst_byte_reader_get_remaining (&reader) < 4) {
gst_byte_reader_set_pos (&reader, 0);
goto done;
}
}
res = gst_isoff_sidx_parser_parse (parser, &reader, consumed);
done:
gst_buffer_unmap (buffer, &info);
return res;
}

View file

@ -0,0 +1,299 @@
/*
* ISO File Format parsing library
*
* gstisoff.h
*
* Copyright (C) 2015 Samsung Electronics. All rights reserved.
* Author: Thiago Santos <thiagoss@osg.samsung.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_ISOFF_H__
#define __GST_ISOFF_H__
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#ifndef GST_ISOFF_API
# ifdef BUILDING_GST_ISOFF
# define GST_ISOFF_API GST_API_EXPORT /* from config.h */
# else
# define GST_ISOFF_API GST_API_IMPORT
# endif
#endif
typedef enum {
GST_ISOFF_PARSER_OK,
GST_ISOFF_PARSER_DONE,
GST_ISOFF_PARSER_UNEXPECTED,
GST_ISOFF_PARSER_ERROR
} GstIsoffParserResult;
GST_ISOFF_API
gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, guint8 extended_type[16], guint * header_size, guint64 * size);
#define GST_ISOFF_FOURCC_UUID GST_MAKE_FOURCC('u','u','i','d')
#define GST_ISOFF_FOURCC_MOOF GST_MAKE_FOURCC('m','o','o','f')
#define GST_ISOFF_FOURCC_MFHD GST_MAKE_FOURCC('m','f','h','d')
#define GST_ISOFF_FOURCC_TFHD GST_MAKE_FOURCC('t','f','h','d')
#define GST_ISOFF_FOURCC_TRUN GST_MAKE_FOURCC('t','r','u','n')
#define GST_ISOFF_FOURCC_TRAF GST_MAKE_FOURCC('t','r','a','f')
#define GST_ISOFF_FOURCC_TFDT GST_MAKE_FOURCC('t','f','d','t')
#define GST_ISOFF_FOURCC_MDAT GST_MAKE_FOURCC('m','d','a','t')
#define GST_ISOFF_FOURCC_MOOV GST_MAKE_FOURCC('m','o','o','v')
#define GST_ISOFF_FOURCC_TRAK GST_MAKE_FOURCC('t','r','a','k')
#define GST_ISOFF_FOURCC_TKHD GST_MAKE_FOURCC('t','k','h','d')
#define GST_ISOFF_FOURCC_MDIA GST_MAKE_FOURCC('m','d','i','a')
#define GST_ISOFF_FOURCC_MDHD GST_MAKE_FOURCC('m','d','h','d')
#define GST_ISOFF_FOURCC_HDLR GST_MAKE_FOURCC('h','d','l','r')
#define GST_ISOFF_FOURCC_SIDX GST_MAKE_FOURCC('s','i','d','x')
/* handler type */
#define GST_ISOFF_FOURCC_SOUN GST_MAKE_FOURCC('s','o','u','n')
#define GST_ISOFF_FOURCC_VIDE GST_MAKE_FOURCC('v','i','d','e')
#define GST_ISOFF_SAMPLE_FLAGS_IS_LEADING(flags) (((flags) >> 26) & 0x03)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEPENDS_ON(flags) (((flags) >> 24) & 0x03)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_DEPENDED_ON(flags) (((flags) >> 22) & 0x03)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_HAS_REDUNDANCY(flags) (((flags) >> 20) & 0x03)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_PADDING_VALUE(flags) (((flags) >> 17) & 0x07)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_NON_SYNC_SAMPLE(flags) (((flags) >> 16) & 0x01)
#define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEGRADATION_PRIORITY(flags) (((flags) >> 0) & 0x0f)
/* Smooth-Streaming specific boxes */
typedef struct _GstTfxdBox
{
guint8 version;
guint32 flags;
guint64 time;
guint64 duration;
} GstTfxdBox;
typedef struct _GstTfrfBoxEntry
{
guint64 time;
guint64 duration;
} GstTfrfBoxEntry;
typedef struct _GstTfrfBox
{
guint8 version;
guint32 flags;
gint entries_count;
GArray *entries;
} GstTfrfBox;
/* Common boxes */
typedef struct _GstMfhdBox
{
guint32 sequence_number;
} GstMfhdBox;
typedef enum
{
GST_TFHD_FLAGS_BASE_DATA_OFFSET_PRESENT = 0x000001,
GST_TFHD_FLAGS_SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002,
GST_TFHD_FLAGS_DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008,
GST_TFHD_FLAGS_DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010,
GST_TFHD_FLAGS_DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020,
GST_TFHD_FLAGS_DURATION_IS_EMPTY = 0x010000,
GST_TFHD_FLAGS_DEFAULT_BASE_IS_MOOF = 0x020000
} GstTfhdFlags;
typedef struct _GstTfhdBox
{
guint8 version;
GstTfhdFlags flags;
guint32 track_id;
/* optional */
guint64 base_data_offset;
guint32 sample_description_index;
guint32 default_sample_duration;
guint32 default_sample_size;
guint32 default_sample_flags;
} GstTfhdBox;
typedef enum
{
GST_TRUN_FLAGS_DATA_OFFSET_PRESENT = 0x000001,
GST_TRUN_FLAGS_FIRST_SAMPLE_FLAGS_PRESENT = 0x000004,
GST_TRUN_FLAGS_SAMPLE_DURATION_PRESENT = 0x000100,
GST_TRUN_FLAGS_SAMPLE_SIZE_PRESENT = 0x000200,
GST_TRUN_FLAGS_SAMPLE_FLAGS_PRESENT = 0x000400,
GST_TRUN_FLAGS_SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT = 0x000800
} GstTrunFlags;
typedef struct _GstTrunBox
{
guint8 version;
GstTrunFlags flags;
guint32 sample_count;
/* optional */
gint32 data_offset;
guint32 first_sample_flags;
GArray *samples;
} GstTrunBox;
typedef struct _GstTrunSample
{
guint32 sample_duration;
guint32 sample_size;
guint32 sample_flags;
union {
guint32 u; /* version 0 */
gint32 s; /* others */
} sample_composition_time_offset;
} GstTrunSample;
typedef struct _GstTdftBox
{
guint64 decode_time;
} GstTfdtBox;
typedef struct _GstTrafBox
{
GstTfhdBox tfhd;
GstTfdtBox tfdt;
GArray *trun;
/* smooth-streaming specific */
GstTfrfBox *tfrf;
GstTfxdBox *tfxd;
} GstTrafBox;
typedef struct _GstMoofBox
{
GstMfhdBox mfhd;
GArray *traf;
} GstMoofBox;
GST_ISOFF_API
GstMoofBox * gst_isoff_moof_box_parse (GstByteReader *reader);
GST_ISOFF_API
void gst_isoff_moof_box_free (GstMoofBox *moof);
typedef struct _GstTkhdBox
{
guint32 track_id;
} GstTkhdBox;
typedef struct _GstMdhdBox
{
guint32 timescale;
} GstMdhdBox;
typedef struct _GstHdlrBox
{
guint32 handler_type;
} GstHdlrBox;
typedef struct _GstMdiaBox
{
GstMdhdBox mdhd;
GstHdlrBox hdlr;
} GstMdiaBox;
typedef struct _GstTrakBox
{
GstTkhdBox tkhd;
GstMdiaBox mdia;
} GstTrakBox;
typedef struct _GstMoovBox
{
GArray *trak;
} GstMoovBox;
GST_ISOFF_API
GstMoovBox * gst_isoff_moov_box_parse (GstByteReader *reader);
GST_ISOFF_API
void gst_isoff_moov_box_free (GstMoovBox *moov);
typedef struct _GstSidxBoxEntry
{
gboolean ref_type;
guint32 size;
GstClockTime duration;
gboolean starts_with_sap;
guint8 sap_type;
guint32 sap_delta_time;
guint64 offset;
GstClockTime pts;
} GstSidxBoxEntry;
typedef struct _GstSidxBox
{
guint8 version;
guint32 flags;
guint32 ref_id;
guint32 timescale;
guint64 earliest_pts;
guint64 first_offset;
gint entry_index;
gint entries_count;
GstSidxBoxEntry *entries;
} GstSidxBox;
typedef enum _GstSidxParserStatus
{
GST_ISOFF_SIDX_PARSER_INIT,
GST_ISOFF_SIDX_PARSER_HEADER,
GST_ISOFF_SIDX_PARSER_DATA,
GST_ISOFF_SIDX_PARSER_FINISHED
} GstSidxParserStatus;
typedef struct _GstSidxParser
{
GstSidxParserStatus status;
guint64 size;
guint64 cumulative_entry_size;
guint64 cumulative_pts;
GstSidxBox sidx;
} GstSidxParser;
GST_ISOFF_API
void gst_isoff_sidx_parser_init (GstSidxParser * parser);
GST_ISOFF_API
void gst_isoff_sidx_parser_clear (GstSidxParser * parser);
GST_ISOFF_API
GstIsoffParserResult gst_isoff_sidx_parser_parse (GstSidxParser * parser, GstByteReader * reader, guint * consumed);
GST_ISOFF_API
GstIsoffParserResult gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buf, guint * consumed);
G_END_DECLS
#endif /* __GST_ISOFF_H__ */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,252 @@
/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* gsthlsdemux.h:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_HLS_DEMUX_H__
#define __GST_HLS_DEMUX_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include "m3u8.h"
#include "gstisoff.h"
#include "gstadaptivedemux.h"
#if defined(HAVE_OPENSSL)
#include <openssl/evp.h>
#elif defined(HAVE_NETTLE)
#include <nettle/aes.h>
#include <nettle/cbc.h>
#elif defined(HAVE_LIBGCRYPT)
#include <gcrypt.h>
#endif
G_BEGIN_DECLS
#define GST_TYPE_HLS_DEMUX2 \
(gst_hls_demux2_get_type())
#define GST_HLS_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_DEMUX2,GstHLSDemux))
#define GST_HLS_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLS_DEMUX2,GstHLSDemuxClass))
#define GST_IS_HLS_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLS_DEMUX2))
#define GST_IS_HLS_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLS_DEMUX2))
#define GST_HLS_DEMUX_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_HLS_DEMUX2,GstHLSDemuxClass))
#define GST_HLS_DEMUX_CAST(obj) \
((GstHLSDemux *)obj)
typedef struct _GstHLSDemux2 GstHLSDemux;
typedef struct _GstHLSDemux2Class GstHLSDemuxClass;
#define GST_TYPE_HLS_DEMUX_STREAM \
(gst_hls_demux_stream_get_type())
#define GST_HLS_DEMUX_STREAM(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_DEMUX_STREAM,GstHLSDemuxStream))
#define GST_HLS_DEMUX_STREAM_CAST(obj) ((GstHLSDemuxStream *)obj)
typedef struct _GstHLSDemuxStream GstHLSDemuxStream;
typedef GstAdaptiveDemux2StreamClass GstHLSDemuxStreamClass;
typedef enum {
GST_HLS_PARSER_NONE,
GST_HLS_PARSER_MPEGTS,
GST_HLS_PARSER_ID3,
GST_HLS_PARSER_WEBVTT,
GST_HLS_PARSER_ISOBMFF
} GstHLSParserType;
typedef enum {
/* More data is needed to parse the fragment */
GST_HLS_PARSER_RESULT_NEED_MORE_DATA,
/* An error happened, discard the fragment */
GST_HLS_PARSER_RESULT_ERROR,
/* Parsing suceeded, it no longer needs to be called for the fragment */
GST_HLS_PARSER_RESULT_DONE,
/* The fragment wasn't the expected one. Current data must be dropped and
* GST_ADAPTIVE_DEMUX_FLOW_RESTART_FRAGMENT returned to the parent class */
GST_HLS_PARSER_RESULT_RESYNC
} GstHLSParserResult;
struct _GstHLSDemuxStream
{
GstAdaptiveDemux2Stream adaptive_demux_stream;
/* A stream either variants or renditions */
gboolean is_variant;
/* Rendition-specific fields */
GstStreamType rendition_type; /* FIXME: Also used by variant streams */
gchar *lang;
gchar *name;
GstHLSRenditionStream *current_rendition;
/* rendition to switch to */
GstHLSRenditionStream *pending_rendition;
/* End of Rendition-specific fields */
/* Whether the underlying playlist was fetched on creation */
gboolean playlist_fetched;
/* The media playlist currently used */
GstHLSMediaPlaylist *playlist;
/* The segment (from the above playlist) currently being used */
GstM3U8MediaSegment *current_segment;
/* The previous ::advance_fragment() failed for live stream */
gboolean pending_advance;
/* Whether we need to typefind the next buffer */
gboolean do_typefind;
/* for collecting data until typefind succeeds */
GstBuffer *pending_typefind_buffer;
/* for chunking data into 16 byte multiples for decryption */
GstAdapter *pending_encrypted_data;
/* last decrypted buffer for pkcs7 unpadding. We only know that it is the last
* on ::finish_fragment() */
GstBuffer *pending_decrypted_buffer;
/* Current offset (in bytes) in fragment data we pushed downstream. Resets to
* -1 at every fragment start */
guint64 current_offset;
gboolean reset_pts;
/* decryption tooling */
#if defined(HAVE_OPENSSL)
# if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX aes_ctx;
# else
EVP_CIPHER_CTX *aes_ctx;
# endif
#elif defined(HAVE_NETTLE)
struct CBC_CTX (struct aes128_ctx, AES_BLOCK_SIZE) aes_ctx;
#elif defined(HAVE_LIBGCRYPT)
gcry_cipher_hd_t aes_ctx;
#endif
gchar *current_key;
guint8 *current_iv;
/* The type of parser used for data handling */
GstHLSParserType parser_type;
/* Is content processing required ? */
gboolean process_buffer_content;
/* Data to be analyzed by */
GstBuffer *pending_segment_data;
/* TRUE if pending_segment_data contains data from a header/index */
gboolean pending_data_is_header;
/* ISOBMFF */
GstMoovBox *moov;
/* Presentation offset to use and report. This value will be appended to all
* "output" stream times. Not enabled (i.e 0) if variant is ISOBMFF
*/
GstClockTime presentation_offset;
};
typedef struct {
guint8 data[16];
} GstHLSKey;
/**
* GstHLSDemux:
*
* Opaque #GstHLSDemux data structure.
*/
struct _GstHLSDemux2
{
GstAdaptiveDemux parent;
/* Initial bitrate to use before any bandwidth measurement */
guint start_bitrate;
/* Decryption key cache: url => GstHLSKey */
GHashTable *keys;
GMutex keys_lock;
/* FIXME: check locking, protected automatically by manifest_lock already? */
/* The master playlist with the available variant streams */
GstHLSMasterPlaylist *master;
GstHLSVariantStream *current_variant;
/* The variant to switch to */
GstHLSVariantStream *pending_variant;
GstHLSDemuxStream *main_stream;
/* Time Mappings (GstHLSTimeMap) */
GList *mappings;
};
struct _GstHLSDemux2Class
{
GstAdaptiveDemuxClass parent_class;
};
gchar *gst_hls_buf_to_utf8_text (GstBuffer * buf);
/* Private */
GstHLSParserResult gst_hlsdemux_handle_content_mpegts (GstHLSDemux *demux,
GstHLSDemuxStream *hls_stream,
gboolean draining,
GstBuffer **buffer);
GstHLSParserResult gst_hlsdemux_handle_content_id3 (GstHLSDemux *demux,
GstHLSDemuxStream *hls_stream,
gboolean draining,
GstBuffer **buffer);
GstHLSParserResult gst_hlsdemux_handle_content_isobmff (GstHLSDemux *demux,
GstHLSDemuxStream *hls_stream,
gboolean draining,
GstBuffer **buffer);
GstHLSParserResult gst_hlsdemux_handle_content_webvtt (GstHLSDemux *demux,
GstHLSDemuxStream *hls_stream,
gboolean draining,
GstBuffer **buffer);
GstHLSParserResult gst_hlsdemux_handle_internal_time (GstHLSDemux *demux,
GstHLSDemuxStream *hls_stream,
GstClockTime internal_time);
GstClockTimeDiff gst_hls_internal_to_stream_time (GstHLSTimeMap *map,
GstClockTime internal_time);
GstHLSTimeMap *gst_hls_find_time_map (GstHLSDemux * demux, gint64 dsn);
GType gst_hls_demux2_get_type (void);
GType gst_hls_demux_stream_get_type (void);
GST_ELEMENT_REGISTER_DECLARE (hlsdemux2);
G_END_DECLS
#endif /* __GST_HLS_DEMUX_H__ */

View file

@ -0,0 +1,19 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "gsthlselements.h"
GST_DEBUG_CATEGORY (hls_debug);
void
hls_element_init (void)
{
static gsize res = FALSE;
if (g_once_init_enter (&res)) {
GST_DEBUG_CATEGORY_INIT (hls_debug, "hlsng", 0,
"HTTP Live Streaming (HLS) NG");
g_once_init_leave (&res, TRUE);
}
}

View file

@ -0,0 +1,14 @@
#ifndef __GST_HLS_ELEMENT_H__
#define __GST_HLS_ELEMENT_H__
#include <gst/gst.h>
G_BEGIN_DECLS
void hls_element_init (void);
GST_DEBUG_CATEGORY_EXTERN (hls_debug);
G_END_DECLS
#endif /* __GST_HLS_ELEMENT_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,416 @@
/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* m3u8.h:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __M3U8_H__
#define __M3U8_H__
#include <gst/gst.h>
G_BEGIN_DECLS
typedef struct _GstHLSMediaPlaylist GstHLSMediaPlaylist;
typedef struct _GstHLSTimeMap GstHLSTimeMap;
typedef struct _GstM3U8MediaSegment GstM3U8MediaSegment;
typedef struct _GstM3U8InitFile GstM3U8InitFile;
typedef struct _GstHLSRenditionStream GstHLSRenditionStream;
typedef struct _GstM3U8Client GstM3U8Client;
typedef struct _GstHLSVariantStream GstHLSVariantStream;
typedef struct _GstHLSMasterPlaylist GstHLSMasterPlaylist;
#define GST_HLS_MEDIA_PLAYLIST(m) ((GstHLSMediaPlaylist*)m)
#define GST_M3U8_MEDIA_SEGMENT(f) ((GstM3U8MediaSegment*)f)
#define GST_HLS_MEDIA_PLAYLIST_LOCK(m) g_mutex_lock (&m->lock);
#define GST_HLS_MEDIA_PLAYLIST_UNLOCK(m) g_mutex_unlock (&m->lock);
#define GST_HLS_MEDIA_PLAYLIST_IS_LIVE(m) ((m)->endlist == FALSE)
/* hlsdemux must not get closer to the end of a live stream than
GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments. Section 6.3.3
"Playing the Playlist file" of the HLS draft states that this
value is three fragments */
#define GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE 3
typedef enum {
GST_HLS_PLAYLIST_TYPE_UNDEFINED,
GST_HLS_PLAYLIST_TYPE_EVENT,
GST_HLS_PLAYLIST_TYPE_VOD,
} GstHLSPlaylistType;
/**
* GstHLSMediaPlaylist:
*
* Official term in RFC : "Media Playlist". A List of Media Segments.
*
* It can be used by either a variant stream (GstHLSVariantStream) or an
* alternate rendition (GstHLSMedia).
*
* Note: Was called `GstM3u8` in legacy elements
*/
struct _GstHLSMediaPlaylist
{
gchar *uri; /* actually downloaded URI */
gchar *base_uri; /* URI to use as base for resolving relative URIs.
* This will be different to uri in case of redirects */
/* Base Tag */
gint version; /* EXT-X-VERSION (default 1) */
/* Media Playlist Tags */
GstClockTime targetduration; /* EXT-X-TARGETDURATION, default GST_CLOCK_TIME_NONE */
gint64 media_sequence; /* EXT-X-MEDIA-SEQUENCE, MSN of the first Media
Segment in the playlist. */
gint64 discont_sequence; /* EXT-X-DISCONTINUITY-SEQUENCE. Default : 0 */
gboolean has_ext_x_dsn; /* EXT-X-DISCONTINUITY-SEQUENCE present and specified */
gboolean endlist; /* EXT-X-ENDLIST present */
GstHLSPlaylistType type; /* EXT-X-PLAYLIST-TYPE. Default:
GST_HLS_PLAYLIST_TYE_UNDEFINED */
gboolean i_frame; /* EXT-X-I-FRAMES-ONLY present. */
gboolean allowcache; /* deprecated EXT-X-ALLOW-CACHE */
/* Overview of contained media segments */
gboolean ext_x_key_present; /* a valid EXT-X-KEY is present on at least one
media segment */
gboolean ext_x_pdt_present; /* a valid EXT-X-PROGRAM-DATE-TIME is present on
at least one media segment */
GPtrArray *segments; /* Array of GstM3U8MediaSegment */
/* Generated information */
GstClockTime duration; /* The estimated total duration of all segments
contained in this playlist */
gboolean reloaded; /* If TRUE, this indicates that this playlist
* was reloaded but had identical content */
/*< private > */
GMutex lock;
/* Copy of the incoming data that created this media playlist.
* See gst_hls_media_playlist_has_same_data() */
gchar *last_data;
gint ref_count; /* ATOMIC */
};
/* gst_hls_media_playlist_new: Internal function : Do not use from demuxer code, only for unit
* testing purposes */
GstHLSMediaPlaylist * gst_hls_media_playlist_new (const gchar * uri,
const gchar * base_uri);
GstHLSMediaPlaylist * gst_hls_media_playlist_ref (GstHLSMediaPlaylist * m3u8);
void gst_hls_media_playlist_unref (GstHLSMediaPlaylist * m3u8);
/**
* GstM3U8MediaSegment:
*
* Official term in RFC : "Media Segment"
*
* Note : Naming in legacy elements was GstM3U8MediaFile
*/
struct _GstM3U8MediaSegment
{
gchar *title;
GstClockTimeDiff stream_time; /* Computed stream time */
GstClockTime duration;
gchar *uri;
gint64 sequence; /* the sequence number of this segment */
gint64 discont_sequence; /* The Discontinuity Sequence Number of this segment */
gboolean discont; /* this file marks a discontinuity */
gchar *key;
guint8 iv[16];
gint64 offset, size;
gint ref_count; /* ATOMIC */
GstM3U8InitFile *init_file; /* Media Initialization (hold ref) */
GDateTime *datetime; /* EXT-X-PROGRAM-DATE-TIME */
};
struct _GstM3U8InitFile
{
gchar *uri;
gint64 offset, size;
guint ref_count; /* ATOMIC */
};
GstM3U8MediaSegment *
gst_m3u8_media_segment_ref (GstM3U8MediaSegment * mfile);
void
gst_m3u8_media_segment_unref (GstM3U8MediaSegment * mfile);
gboolean
gst_hls_media_playlist_has_same_data (GstHLSMediaPlaylist * m3u8,
gchar * playlist_data);
GstHLSMediaPlaylist *
gst_hls_media_playlist_parse (gchar * data,
const gchar * uri,
const gchar * base_uri);
void
gst_hls_media_playlist_recalculate_stream_time (GstHLSMediaPlaylist *playlist,
GstM3U8MediaSegment *anchor);
GstM3U8MediaSegment *
gst_hls_media_playlist_sync_to_segment (GstHLSMediaPlaylist * m3u8,
GstM3U8MediaSegment * segment);
gboolean
gst_hls_media_playlist_has_next_fragment (GstHLSMediaPlaylist * m3u8,
GstM3U8MediaSegment * current,
gboolean forward);
GstM3U8MediaSegment *
gst_hls_media_playlist_advance_fragment (GstHLSMediaPlaylist * m3u8,
GstM3U8MediaSegment * current,
gboolean forward);
GstM3U8MediaSegment *
gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist *self);
GstClockTime
gst_hls_media_playlist_get_duration (GstHLSMediaPlaylist * m3u8);
gchar *
gst_hls_media_playlist_get_uri (GstHLSMediaPlaylist * m3u8);
gboolean
gst_hls_media_playlist_is_live (GstHLSMediaPlaylist * m3u8);
gboolean
gst_hls_media_playlist_get_seek_range (GstHLSMediaPlaylist * m3u8,
gint64 * start,
gint64 * stop);
GstM3U8MediaSegment *
gst_hls_media_playlist_seek (GstHLSMediaPlaylist *playlist,
gboolean forward,
GstSeekFlags flags,
GstClockTimeDiff ts);
void
gst_hls_media_playlist_dump (GstHLSMediaPlaylist* self);
typedef enum
{
GST_HLS_RENDITION_STREAM_TYPE_INVALID = -1,
GST_HLS_RENDITION_STREAM_TYPE_AUDIO,
GST_HLS_RENDITION_STREAM_TYPE_VIDEO,
GST_HLS_RENDITION_STREAM_TYPE_SUBTITLES,
GST_HLS_RENDITION_STREAM_TYPE_CLOSED_CAPTIONS,
GST_HLS_N_MEDIA_TYPES
} GstHLSRenditionStreamType;
/**
* GstHLSRenditionStream:
*
* Official term in RFC : "Renditions are alternate versions of the content,
* such as audio produced in different languages or video recorded from
* different camera angles."
*
* Note: Was named GstHLSMedia in legacy elements
*/
struct _GstHLSRenditionStream {
GstHLSRenditionStreamType mtype;
gchar *group_id;
gchar *name;
gchar *lang;
gchar *uri;
GstCaps *caps;
gboolean is_default;
gboolean autoselect;
gboolean forced;
gint ref_count; /* ATOMIC */
};
GstHLSRenditionStream *
gst_hls_rendition_stream_ref (GstHLSRenditionStream * media);
void
gst_hls_rendition_stream_unref (GstHLSRenditionStream * media);
const gchar *
gst_hls_rendition_stream_type_get_name (GstHLSRenditionStreamType mtype);
/**
* GstHLSVariantStream:
*
* Official term in RFC :
* """
* A Master Playlist provides a set of Variant Streams, each of which describes
* a different version of the same content.
*
* A Variant Stream includes a Media Playlist that specifies media encoded at a
* particular bit rate, in a particular format, and at a particular resolution
* for media containing video.
* """
*/
struct _GstHLSVariantStream {
gchar *name; /* This will be the "name" of the playlist, the original
* relative/absolute uri in a variant playlist */
gchar *uri;
gchar *codecs;
GstCaps *caps;
GstStreamType codecs_stream_type; /* As defined by codecs */
gint bandwidth; /* bits per second */
gint program_id;
gint width;
gint height;
gboolean iframe;
gint refcount; /* ATOMIC */
/* alternative renditions (names) */
gchar *media_groups[GST_HLS_N_MEDIA_TYPES];
/* List of gchar* fallback uri */
GList *fallback;
};
/* Notes: #define are to avoid symbol clashes with legacy hlsdemux */
#define gst_hls_variant_stream_ref hls_variant_stream_ref
GstHLSVariantStream * hls_variant_stream_ref (GstHLSVariantStream * stream);
#define gst_hls_variant_stream_unref hls_variant_stream_unref
void hls_variant_stream_unref (GstHLSVariantStream * stream);
/**
* GstHLSMasterPlaylist:
*
* Official term in RFC : "A Playlist is either a Media Playlist or a Master
* Playlist."
*
* This is the top-level object, constructed by a manifest provided by external
* means.
*/
struct _GstHLSMasterPlaylist
{
/* Available variant streams, sorted by bitrate (low -> high) */
GList *variants; /* GstHLSVariantStream */
GList *iframe_variants; /* GstHLSVariantStream */
/* Default variant, first in the list (originally, before sorting) */
GstHLSVariantStream *default_variant;
/* Full list of Available Alternative Rendition (GstHLSRenditionStream) */
GList *renditions;
/* EXT-X-VERSION. 0 if unspecified */
gint version;
/* TRUE if this playlist is a simple media playlist (and not a master
* playlist). Implies that there is only a single variant and no alternate
* rendition groups */
gboolean is_simple;
/* TRUE if all variants have codecs specified */
gboolean have_codecs;
/*< private > */
gchar *last_data; /* Copy of the incoming data that created this master playlist */
gint refcount; /* ATOMIC */
};
/* Notes: #define are to avoid symbol clashes with legacy hlsdemux */
#define gst_hls_master_playlist_new_from_data hls_master_playlist_new_from_data
GstHLSMasterPlaylist * hls_master_playlist_new_from_data (gchar * data,
const gchar * base_uri);
#define gst_hls_master_playlist_get_variant_for_bitrate hls_master_playlist_get_variant_for_bitrate
GstHLSVariantStream * hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * playlist,
GstHLSVariantStream * current_variant,
guint bitrate,
guint min_bitrate);
#define gst_hls_master_playlist_get_common_caps hls_master_playlist_get_common_caps
GstCaps * hls_master_playlist_get_common_caps (GstHLSMasterPlaylist *playlist);
#define gst_hls_master_playlist_unref hls_master_playlist_unref
void hls_master_playlist_unref (GstHLSMasterPlaylist * playlist);
/* Time Mapping
*
* Used to map GStreamer times to internal segment timestamps
*/
struct _GstHLSTimeMap {
/* DISCONT SEQUENCE NUMBER */
gint64 dsn;
/* The stream time (used for gst timestamps, gst segments, seeking ...) */
GstClockTime stream_time;
/* The optional Program Date Time reference */
GDateTime *pdt;
/* The internal time (ex: mpeg-ts PTS) */
GstClockTime internal_time;
};
GstStreamType gst_stream_type_from_hls_type (GstHLSRenditionStreamType stype);
GstStreamType gst_hls_get_stream_type_from_structure (GstStructure *structure);
GstStreamType gst_hls_get_stream_type_from_caps (GstCaps *caps);
#if !GLIB_CHECK_VERSION(2, 62, 0)
static inline gchar* g_date_time_format_iso8601(GDateTime* datetime) {
GString* outstr = NULL;
gchar* main_date = NULL;
gint64 offset;
// Main date and time.
main_date = g_date_time_format(datetime, "%Y-%m-%dT%H:%M:%S");
outstr = g_string_new(main_date);
g_free(main_date);
// Timezone. Format it as `%:::z` unless the offset is zero, in which case
// we can simply use `Z`.
offset = g_date_time_get_utc_offset(datetime);
if (offset == 0) {
g_string_append_c(outstr, 'Z');
} else {
gchar* time_zone = g_date_time_format(datetime, "%:::z");
g_string_append(outstr, time_zone);
g_free(time_zone);
}
return g_string_free(outstr, FALSE);
}
#endif
G_END_DECLS
#endif /* __M3U8_H__ */

View file

@ -0,0 +1,41 @@
hls_sources = [
'hls/gsthlsdemux.c',
'hls/gsthlsdemux-util.c',
'hls/gsthlselement.c',
'hls/m3u8.c',
]
hls_cargs = []
hls_crypto = get_option('hls-crypto')
hls_crypto_dep = dependency('', required : false)
if ['auto', 'nettle'].contains(hls_crypto)
hls_crypto_dep = dependency('nettle', version : '>= 3.0', required : false)
if hls_crypto_dep.found()
hls_cargs += ['-DHAVE_NETTLE']
endif
endif
if not hls_crypto_dep.found() and ['auto', 'libgcrypt'].contains(hls_crypto)
hls_crypto_dep = cc.find_library('gcrypt', required : false)
if hls_crypto_dep.found()
hls_cargs += ['-DHAVE_LIBGCRYPT']
endif
endif
if not hls_crypto_dep.found() and ['auto', 'openssl'].contains(hls_crypto)
hls_crypto_dep = dependency('openssl', required : false)
if hls_crypto_dep.found()
hls_cargs += ['-DHAVE_OPENSSL']
endif
endif
if not hls_crypto_dep.found()
if hls_crypto == 'auto'
message('Could not find a supported crypto library for HLS support')
else
error('HLS crypto support library "@0@" not found'.format(hls_crypto))
endif
endif
hls_dep = declare_dependency(include_directories : include_directories('.'))

View file

@ -0,0 +1,153 @@
dash_sources = [
'dash/gstdashdemux.c',
'dash/gstmpdnode.c',
'dash/gstmpdrootnode.c',
'dash/gstmpdbaseurlnode.c',
'dash/gstmpdutctimingnode.c',
'dash/gstmpdmetricsnode.c',
'dash/gstmpdmetricsrangenode.c',
'dash/gstmpdsnode.c',
'dash/gstmpdsegmenttimelinenode.c',
'dash/gstmpdsegmenttemplatenode.c',
'dash/gstmpdsegmenturlnode.c',
'dash/gstmpdsegmentlistnode.c',
'dash/gstmpdsegmentbasenode.c',
'dash/gstmpdperiodnode.c',
'dash/gstmpdrepresentationbasenode.c',
'dash/gstmpdmultsegmentbasenode.c',
'dash/gstmpdrepresentationnode.c',
'dash/gstmpdsubrepresentationnode.c',
'dash/gstmpdcontentcomponentnode.c',
'dash/gstmpdadaptationsetnode.c',
'dash/gstmpdsubsetnode.c',
'dash/gstmpdprograminformationnode.c',
'dash/gstmpdlocationnode.c',
'dash/gstmpdreportingnode.c',
'dash/gstmpdurltypenode.c',
'dash/gstmpddescriptortypenode.c',
'dash/gstxmlhelper.c',
'dash/gstmpdhelper.c',
'dash/gstmpdparser.c',
'dash/gstmpdclient.c'
]
smoothstreaming_sources = [
'mss/gstmssdemux.c',
'mss/gstmssmanifest.c',
'mss/gstmssfragmentparser.c',
]
plugin_sources = [
'plugin.c',
'gstisoff.c',
'gstadaptivedemux.c',
'gstadaptivedemuxutils.c',
'gstadaptivedemux-period.c',
'gstadaptivedemux-stream.c',
'gstadaptivedemux-track.c',
'downloadhelper.c',
'downloadrequest.c',
'../soup/gstsouploader.c'
]
# Used for unit tests, so need to be defined even if we skip the subdir
hls_dep = dependency('', required : false)
adaptivedemux2_dep = dependency('', required : false)
adaptivedemux2_opt = get_option('adaptivedemux2')
if adaptivedemux2_opt.disabled()
message('Not building adaptivedemux2 plugin because it was disabled')
subdir_done()
endif
adaptive_xml2_dep = dependency('libxml-2.0', version : '>= 2.8', allow_fallback: true, required: adaptivedemux2_opt)
if not adaptive_xml2_dep.found()
message(f'Not building adaptivedemux2 plugin: libxml2 is needed')
subdir_done()
endif
subdir('hls')
plugin_sources += dash_sources
plugin_sources += smoothstreaming_sources
plugin_sources += hls_sources
soup_loader_args = ['-DBUILDING_ADAPTIVEDEMUX2']
default_library = get_option('default_library')
if default_library in ['static', 'both']
libsoup2_dep = dependency('libsoup-2.4', version : '>=2.48',
required : false, fallback : ['libsoup', 'libsoup_dep'],
default_options: ['sysprof=disabled'])
libsoup3_dep = dependency('libsoup-3.0', required : false,
fallback : ['libsoup3', 'libsoup_dep'])
if libsoup3_dep.found()
soup_dep = libsoup3_dep
static_soup_loader_args = ['-DSTATIC_SOUP=3']
elif libsoup2_dep.found()
soup_dep = libsoup2_dep
static_soup_loader_args = ['-DSTATIC_SOUP=2']
else
if adaptivedemux2_opt.enabled()
error(f'adaptivedemux2: Either libsoup2 or libsoup3 is needed for build with default_library=@default_library@')
endif
message(f'Not building adaptivedemux2 plugin: either libsoup2 or libsoup3 is needed for build with default_library=@default_library@')
subdir_done()
endif
# Static plugin links to libsoup directly at build time
adaptivedemux2_static = static_library('gstadaptivedemux2',
plugin_sources,
include_directories: [configinc, libsinc],
c_args: [gst_plugins_good_args, soup_loader_args, soup_loader_args, hls_cargs,
'-DGST_ISOFF_API=G_GNUC_INTERNAL'],
dependencies: [gsttag_dep, gstnet_dep, gstbase_dep,
gstpbutils_dep, gstapp_dep, soup_dep,
gio_dep, adaptive_xml2_dep,
hls_crypto_dep, libm],
install: true,
install_dir: plugins_install_dir)
endif
if default_library in ['shared', 'both']
# Shared plugin doesn't link to libsoup but dlopen()s it at runtime
libdl = cc.find_library('dl', required: false)
adaptivedemux2_shared = shared_library('gstadaptivedemux2',
plugin_sources,
include_directories: [configinc, libsinc],
c_args: [gst_plugins_good_args, soup_loader_args, hls_cargs,
'-DGST_ISOFF_API=G_GNUC_INTERNAL'],
dependencies: [gsttag_dep, gstnet_dep, gstbase_dep,
gstpbutils_dep, gstapp_dep, gio_dep,
gmodule_dep, adaptive_xml2_dep,
hls_crypto_dep, libm, libdl],
install: true,
install_dir: plugins_install_dir)
endif
# Use the static library to generate the .pc file if it's available. The shared
# library .pc file does not have a Requires: on libsoup, and we use plugin
# .pc files to generate dependencies for linking plugins statically.
if default_library == 'shared'
pkgconfig.generate(adaptivedemux2_shared, install_dir: plugins_pkgconfig_install_dir)
else
pkgconfig.generate(adaptivedemux2_static, install_dir: plugins_pkgconfig_install_dir)
endif
# Add the shared library to the plugins list if available. We pass this list of
# plugins to hotdoc to generate the plugins cache, which introspects the plugin
# by loading it. We need the shared plugin for that.
if default_library == 'static'
plugins += [adaptivedemux2_static]
else
plugins += [adaptivedemux2_shared]
endif
# For unit tests
adaptivedemux2_dep = declare_dependency(
include_directories : include_directories('.'),
dependencies: adaptive_xml2_dep)

View file

@ -0,0 +1,671 @@
/* GStreamer
* Copyright (C) 2012 Smart TV Alliance
* Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
*
* gstmssdemux.c:
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-mssdemux
* @title: mssdemux
*
* Demuxes a Microsoft's Smooth Streaming manifest into its audio and/or video streams.
*
*/
/*
* == Internals
*
* = Smooth streaming in a few lines
* A SS stream is defined by a xml manifest file. This file has a list of
* tracks (StreamIndex), each one can have multiple QualityLevels, that define
* different encoding/bitrates. When playing a track, only one of those
* QualityLevels can be active at a time (per stream).
*
* The StreamIndex defines a URL with {time} and {bitrate} tags that are
* replaced by values indicated by the fragment start times and the selected
* QualityLevel, that generates the fragments URLs.
*
* Another relevant detail is that the Isomedia fragments for smoothstreaming
* won't contains a 'moov' atom, nor a 'stsd', so there is no information
* about the media type/configuration on the fragments, it must be extracted
* from the Manifest and passed downstream. mssdemux does this via GstCaps.
*
* = How mssdemux works
* There is a gstmssmanifest.c utility that holds the manifest and parses
* and has functions to extract information from it. mssdemux received the
* manifest from its sink pad and starts processing it when it gets EOS.
*
* The Manifest is parsed and the streams are exposed, 1 pad for each, with
* a initially selected QualityLevel. Each stream starts its own GstTaks that
* is responsible for downloading fragments and pushing them downstream.
*
* When a new connection-speed is set, mssdemux evaluates the available
* QualityLevels and might decide to switch to another one. In this case it
* pushes a new GstCaps event indicating the new caps on the pads.
*
* All operations that intend to update the GstTasks state should be protected
* with the GST_OBJECT_LOCK.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gst/gst-i18n-plugin.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gstmssdemux.h"
GST_DEBUG_CATEGORY (mssdemux2_debug);
static GstStaticPadTemplate gst_mss_demux_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/vnd.ms-sstr+xml")
);
static GstStaticPadTemplate gst_mss_demux_videosrc_template =
GST_STATIC_PAD_TEMPLATE ("video_%02u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate gst_mss_demux_audiosrc_template =
GST_STATIC_PAD_TEMPLATE ("audio_%02u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
typedef struct _GstMssDemux2 GstMssDemux2;
typedef struct _GstMssDemux2Class GstMssDemux2Class;
#define gst_mss_demux2_parent_class parent_class
G_DEFINE_TYPE (GstMssDemux2, gst_mss_demux2, GST_TYPE_ADAPTIVE_DEMUX);
#define gst_hls_demux_stream_parent_class stream_parent_class
G_DEFINE_TYPE (GstMssDemuxStream, gst_mss_demux_stream,
GST_TYPE_ADAPTIVE_DEMUX2_STREAM);
GST_ELEMENT_REGISTER_DEFINE (mssdemux2, "mssdemux2",
GST_RANK_PRIMARY + 1, GST_TYPE_MSS_DEMUX2);
static void gst_mss_demux_dispose (GObject * object);
static gboolean gst_mss_demux_is_live (GstAdaptiveDemux * demux);
static gboolean gst_mss_demux_process_manifest (GstAdaptiveDemux * demux,
GstBuffer * buffer);
static GstClockTime gst_mss_demux_get_duration (GstAdaptiveDemux * demux);
static void gst_mss_demux_reset (GstAdaptiveDemux * demux);
static GstFlowReturn gst_mss_demux_stream_seek (GstAdaptiveDemux2Stream *
stream, gboolean forward, GstSeekFlags flags, GstClockTimeDiff ts,
GstClockTimeDiff * final_ts);
static gboolean gst_mss_demux_stream_has_next_fragment (GstAdaptiveDemux2Stream
* stream);
static GstFlowReturn
gst_mss_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream);
static gboolean gst_mss_demux_stream_select_bitrate (GstAdaptiveDemux2Stream *
stream, guint64 bitrate);
static GstFlowReturn
gst_mss_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream);
static gboolean gst_mss_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek);
static gint64
gst_mss_demux_get_manifest_update_interval (GstAdaptiveDemux * demux);
static GstClockTime
gst_mss_demux_stream_get_fragment_waiting_time (GstAdaptiveDemux2Stream *
stream);
static GstFlowReturn
gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux,
GstBuffer * buffer);
static gboolean gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux,
gint64 * start, gint64 * stop);
static GstFlowReturn gst_mss_demux_data_received (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream, GstBuffer * buffer);
static gboolean
gst_mss_demux_requires_periodical_playlist_update (GstAdaptiveDemux * demux);
GstStreamType gst_stream_type_from_mss_type (GstMssStreamType mtype);
static void
gst_mss_demux_stream_class_init (GstMssDemuxStreamClass * klass)
{
}
static void
gst_mss_demux_stream_init (GstMssDemuxStream * stream)
{
}
static void
gst_mss_demux2_class_init (GstMssDemuxClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstAdaptiveDemuxClass *gstadaptivedemux_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstadaptivedemux_class = (GstAdaptiveDemuxClass *) klass;
gst_element_class_add_static_pad_template (gstelement_class,
&gst_mss_demux_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_mss_demux_videosrc_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_mss_demux_audiosrc_template);
gst_element_class_set_static_metadata (gstelement_class,
"Smooth Streaming demuxer (v2)", "Codec/Demuxer/Adaptive",
"Parse and demultiplex a Smooth Streaming manifest into audio and video "
"streams", "Thiago Santos <thiago.sousa.santos@collabora.com>");
gobject_class->dispose = gst_mss_demux_dispose;
gstadaptivedemux_class->process_manifest = gst_mss_demux_process_manifest;
gstadaptivedemux_class->is_live = gst_mss_demux_is_live;
gstadaptivedemux_class->get_duration = gst_mss_demux_get_duration;
gstadaptivedemux_class->get_manifest_update_interval =
gst_mss_demux_get_manifest_update_interval;
gstadaptivedemux_class->reset = gst_mss_demux_reset;
gstadaptivedemux_class->seek = gst_mss_demux_seek;
gstadaptivedemux_class->stream_seek = gst_mss_demux_stream_seek;
gstadaptivedemux_class->stream_advance_fragment =
gst_mss_demux_stream_advance_fragment;
gstadaptivedemux_class->stream_has_next_fragment =
gst_mss_demux_stream_has_next_fragment;
gstadaptivedemux_class->stream_select_bitrate =
gst_mss_demux_stream_select_bitrate;
gstadaptivedemux_class->stream_update_fragment_info =
gst_mss_demux_stream_update_fragment_info;
gstadaptivedemux_class->stream_get_fragment_waiting_time =
gst_mss_demux_stream_get_fragment_waiting_time;
gstadaptivedemux_class->update_manifest_data =
gst_mss_demux_update_manifest_data;
gstadaptivedemux_class->get_live_seek_range =
gst_mss_demux_get_live_seek_range;
gstadaptivedemux_class->data_received = gst_mss_demux_data_received;
gstadaptivedemux_class->requires_periodical_playlist_update =
gst_mss_demux_requires_periodical_playlist_update;
GST_DEBUG_CATEGORY_INIT (mssdemux2_debug, "mssdemux2", 0,
"mssdemux2 element");
}
static void
gst_mss_demux2_init (GstMssDemux * mssdemux)
{
}
static void
gst_mss_demux_reset (GstAdaptiveDemux * demux)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
if (mssdemux->manifest) {
gst_mss_manifest_free (mssdemux->manifest);
mssdemux->manifest = NULL;
}
g_free (mssdemux->base_url);
mssdemux->base_url = NULL;
}
static void
gst_mss_demux_dispose (GObject * object)
{
gst_mss_demux_reset (GST_ADAPTIVE_DEMUX_CAST (object));
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static gboolean
gst_mss_demux_is_live (GstAdaptiveDemux * demux)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
g_return_val_if_fail (mssdemux->manifest != NULL, FALSE);
return gst_mss_manifest_is_live (mssdemux->manifest);
}
static GstClockTime
gst_mss_demux_get_duration (GstAdaptiveDemux * demux)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
g_return_val_if_fail (mssdemux->manifest != NULL, FALSE);
return gst_mss_manifest_get_gst_duration (mssdemux->manifest);
}
static GstFlowReturn
gst_mss_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream)
{
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (stream->demux);
GstFlowReturn ret;
gchar *path = NULL;
gst_adaptive_demux2_stream_fragment_clear (&stream->fragment);
ret = gst_mss_stream_get_fragment_url (mssstream->manifest_stream, &path);
if (ret == GST_FLOW_OK) {
stream->fragment.uri = g_strdup_printf ("%s/%s", mssdemux->base_url, path);
stream->fragment.stream_time =
gst_mss_stream_get_fragment_gst_timestamp (mssstream->manifest_stream);
stream->fragment.duration =
gst_mss_stream_get_fragment_gst_duration (mssstream->manifest_stream);
}
g_free (path);
return ret;
}
static GstFlowReturn
gst_mss_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
GstSeekFlags flags, GstClockTimeDiff ts, GstClockTimeDiff * final_ts)
{
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
gst_mss_stream_seek (mssstream->manifest_stream, forward, flags, ts,
final_ts);
return GST_FLOW_OK;
}
static GstFlowReturn
gst_mss_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream)
{
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
if (stream->demux->segment.rate >= 0)
return gst_mss_stream_advance_fragment (mssstream->manifest_stream);
else
return gst_mss_stream_regress_fragment (mssstream->manifest_stream);
}
static GstCaps *
create_mss_caps (GstMssDemuxStream * stream, GstCaps * caps)
{
return gst_caps_new_simple ("video/quicktime", "variant", G_TYPE_STRING,
"mss-fragmented", "timescale", G_TYPE_UINT64,
gst_mss_stream_get_timescale (stream->manifest_stream), "media-caps",
GST_TYPE_CAPS, caps, NULL);
}
static void
gst_mss_demux_apply_protection_system (GstCaps * caps,
const gchar * selected_system)
{
GstStructure *s;
g_return_if_fail (selected_system);
s = gst_caps_get_structure (caps, 0);
gst_structure_set (s,
"original-media-type", G_TYPE_STRING, gst_structure_get_name (s),
GST_PROTECTION_SYSTEM_ID_CAPS_FIELD, G_TYPE_STRING, selected_system,
NULL);
gst_structure_set_name (s, "application/x-cenc");
}
static gboolean
gst_mss_demux_setup_streams (GstAdaptiveDemux * demux)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
GSList *streams = gst_mss_manifest_get_streams (mssdemux->manifest);
GSList *active_streams = NULL;
GSList *iter;
const gchar *protection_system_id =
gst_mss_manifest_get_protection_system_id (mssdemux->manifest);
const gchar *protection_data =
gst_mss_manifest_get_protection_data (mssdemux->manifest);
gboolean protected = protection_system_id && protection_data;
const gchar *selected_system = NULL;
guint64 max_bitrate = G_MAXUINT64;
if (streams == NULL) {
GST_INFO_OBJECT (mssdemux, "No streams found in the manifest");
GST_ELEMENT_ERROR (mssdemux, STREAM, DEMUX,
(_("This file contains no playable streams.")),
("no streams found at the Manifest"));
return FALSE;
}
if (protected) {
const gchar *sys_ids[2] = { protection_system_id, NULL };
selected_system = gst_protection_select_system (sys_ids);
if (!selected_system) {
GST_ERROR_OBJECT (mssdemux, "stream is protected, but no "
"suitable decryptor element has been found");
return FALSE;
}
}
if (demux->connection_speed != 0)
max_bitrate = demux->connection_speed;
for (iter = streams; iter; iter = g_slist_next (iter)) {
GstAdaptiveDemux2Stream *stream = NULL;
GstMssDemuxStream *mss_stream;
GstMssStream *manifeststream = iter->data;
GstAdaptiveDemuxTrack *track;
GstStreamType stream_type =
gst_stream_type_from_mss_type (gst_mss_stream_get_type
(manifeststream));
const gchar *lang, *stream_id = gst_stream_type_get_name (stream_type);
gchar *name;
GstCaps *caps;
GstTagList *tags = NULL;
name = g_strdup_printf ("mss-stream-%s", stream_id);
mss_stream = g_object_new (GST_TYPE_MSS_DEMUX_STREAM, "name", name, NULL);
g_free (name);
stream = GST_ADAPTIVE_DEMUX2_STREAM_CAST (mss_stream);
stream->stream_type = stream_type;
mss_stream->manifest_stream = manifeststream;
gst_mss_stream_set_active (manifeststream, TRUE);
/* Set the maximum bitrate now that the underlying stream is active. This
* ensures that we get the proper caps and information. */
gst_mss_stream_select_bitrate (manifeststream, max_bitrate);
caps = gst_mss_stream_get_caps (mss_stream->manifest_stream);
gst_adaptive_demux2_stream_set_caps (stream, create_mss_caps (mss_stream,
caps));
lang = gst_mss_stream_get_lang (mss_stream->manifest_stream);
if (lang != NULL)
tags = gst_tag_list_new (GST_TAG_LANGUAGE_CODE, lang, NULL);
track = gst_adaptive_demux_track_new (demux, stream_type,
GST_STREAM_FLAG_NONE, (gchar *) stream_id, create_mss_caps (mss_stream,
caps), tags);
gst_adaptive_demux2_add_stream (demux, stream);
gst_adaptive_demux2_stream_add_track (stream, track);
gst_adaptive_demux_track_unref (track);
GST_DEBUG_OBJECT (stream, "Current quality bitrate %" G_GUINT64_FORMAT,
gst_mss_stream_get_current_bitrate (manifeststream));
if (tags)
gst_adaptive_demux2_stream_set_tags (stream, tags);
active_streams = g_slist_prepend (active_streams, mss_stream);
}
for (iter = active_streams; iter; iter = g_slist_next (iter)) {
GstMssDemuxStream *stream = iter->data;
if (protected) {
GstBuffer *protection_buffer =
gst_buffer_new_wrapped (g_strdup (protection_data),
strlen (protection_data));
GstEvent *event =
gst_event_new_protection (protection_system_id, protection_buffer,
"smooth-streaming");
GST_LOG_OBJECT (stream, "Queueing Protection event on source pad");
gst_adaptive_demux2_stream_queue_event ((GstAdaptiveDemux2Stream *)
stream, event);
gst_buffer_unref (protection_buffer);
}
}
g_slist_free (active_streams);
return TRUE;
}
static void
gst_mss_demux_update_base_url (GstMssDemux * mssdemux)
{
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (mssdemux);
gchar *baseurl_end;
g_free (mssdemux->base_url);
mssdemux->base_url =
g_strdup (demux->manifest_base_uri ? demux->manifest_base_uri : demux->
manifest_uri);
baseurl_end = g_strrstr (mssdemux->base_url, "/Manifest");
if (baseurl_end == NULL) {
/* second try */
baseurl_end = g_strrstr (mssdemux->base_url, "/manifest");
}
if (baseurl_end) {
/* set the new end of the string */
baseurl_end[0] = '\0';
} else {
GST_WARNING_OBJECT (mssdemux, "Stream's URI didn't end with /manifest");
}
}
static gboolean
gst_mss_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
gst_mss_demux_update_base_url (mssdemux);
mssdemux->manifest = gst_mss_manifest_new (buf);
if (!mssdemux->manifest) {
GST_ELEMENT_ERROR (mssdemux, STREAM, FORMAT, ("Bad manifest file"),
("Xml manifest file couldn't be parsed"));
return FALSE;
}
return gst_mss_demux_setup_streams (demux);
}
static gboolean
gst_mss_demux_stream_select_bitrate (GstAdaptiveDemux2Stream * stream,
guint64 bitrate)
{
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
gboolean ret = FALSE;
GST_DEBUG_OBJECT (stream,
"Using stream download bitrate %" G_GUINT64_FORMAT, bitrate);
if (gst_mss_stream_select_bitrate (mssstream->manifest_stream,
bitrate / MAX (1.0, ABS (stream->demux->segment.rate)))) {
GstCaps *caps;
GstCaps *msscaps;
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (stream->demux);
const gchar *protection_system_id =
gst_mss_manifest_get_protection_system_id (mssdemux->manifest);
const gchar *protection_data =
gst_mss_manifest_get_protection_data (mssdemux->manifest);
gboolean protected = protection_system_id && protection_data;
caps = gst_mss_stream_get_caps (mssstream->manifest_stream);
GST_DEBUG_OBJECT (stream,
"Starting streams reconfiguration due to bitrate changes");
if (protected) {
const gchar *sys_ids[2] = { protection_system_id, NULL };
const gchar *selected_system = gst_protection_select_system (sys_ids);
if (!selected_system) {
GST_ERROR_OBJECT (mssdemux, "stream is protected, but no "
"suitable decryptor element has been found");
gst_caps_unref (caps);
return FALSE;
}
gst_mss_demux_apply_protection_system (caps, selected_system);
}
msscaps = create_mss_caps (mssstream, caps);
GST_DEBUG_OBJECT (stream,
"Stream changed bitrate to %" G_GUINT64_FORMAT " caps: %"
GST_PTR_FORMAT,
gst_mss_stream_get_current_bitrate (mssstream->manifest_stream), caps);
gst_caps_unref (caps);
gst_adaptive_demux2_stream_set_caps (stream, msscaps);
ret = TRUE;
GST_DEBUG_OBJECT (stream, "Finished streams reconfiguration");
}
return ret;
}
#define SEEK_UPDATES_PLAY_POSITION(r, start_type, stop_type) \
((r >= 0 && start_type != GST_SEEK_TYPE_NONE) || \
(r < 0 && stop_type != GST_SEEK_TYPE_NONE))
static gboolean
gst_mss_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
{
gdouble rate;
GstFormat format;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 start, stop;
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start,
&stop_type, &stop);
GST_DEBUG_OBJECT (mssdemux,
"seek event, rate: %f start: %" GST_TIME_FORMAT " stop: %"
GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
if (SEEK_UPDATES_PLAY_POSITION (rate, start_type, stop_type)) {
if (rate >= 0)
gst_mss_manifest_seek (mssdemux->manifest, rate >= 0, start);
else
gst_mss_manifest_seek (mssdemux->manifest, rate >= 0, stop);
}
return TRUE;
}
static gboolean
gst_mss_demux_stream_has_next_fragment (GstAdaptiveDemux2Stream * stream)
{
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
return gst_mss_stream_has_next_fragment (mssstream->manifest_stream);
}
static gint64
gst_mss_demux_get_manifest_update_interval (GstAdaptiveDemux * demux)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
GstClockTime interval;
/* Not much information about this in the MSS spec. It seems that
* the fragments contain an UUID box that should tell the next
* fragments time and duration so one wouldn't need to fetch
* the Manifest again, but we need a fallback here. So use 2 times
* the current fragment duration */
interval = gst_mss_manifest_get_min_fragment_duration (mssdemux->manifest);
if (!GST_CLOCK_TIME_IS_VALID (interval))
interval = 2 * GST_SECOND; /* default to 2 seconds */
interval = 2 * (interval / GST_USECOND);
return interval;
}
static GstClockTime
gst_mss_demux_stream_get_fragment_waiting_time (GstAdaptiveDemux2Stream *
stream)
{
/* Wait a second for live streams so we don't try premature fragments downloading */
return GST_SECOND;
}
static GstFlowReturn
gst_mss_demux_update_manifest_data (GstAdaptiveDemux * demux,
GstBuffer * buffer)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
gst_mss_demux_update_base_url (mssdemux);
gst_mss_manifest_reload_fragments (mssdemux->manifest, buffer);
return GST_FLOW_OK;
}
static gboolean
gst_mss_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start,
gint64 * stop)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
return gst_mss_manifest_get_live_seek_range (mssdemux->manifest, start, stop);
}
static GstFlowReturn
gst_mss_demux_data_received (GstAdaptiveDemux * demux,
GstAdaptiveDemux2Stream * stream, GstBuffer * buffer)
{
GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (demux);
GstMssDemuxStream *mssstream = (GstMssDemuxStream *) stream;
gsize available;
if (!gst_mss_manifest_is_live (mssdemux->manifest)) {
return gst_adaptive_demux2_stream_push_buffer (stream, buffer);
}
if (gst_mss_stream_fragment_parsing_needed (mssstream->manifest_stream)) {
gst_mss_manifest_live_adapter_push (mssstream->manifest_stream, buffer);
available =
gst_mss_manifest_live_adapter_available (mssstream->manifest_stream);
// FIXME: try to reduce this minimal size.
if (available < 4096) {
return GST_FLOW_OK;
} else {
GST_LOG_OBJECT (stream, "enough data, parsing fragment.");
buffer =
gst_mss_manifest_live_adapter_take_buffer (mssstream->manifest_stream,
available);
gst_mss_stream_parse_fragment (mssstream->manifest_stream, buffer);
}
}
return gst_adaptive_demux2_stream_push_buffer (stream, buffer);
}
static gboolean
gst_mss_demux_requires_periodical_playlist_update (GstAdaptiveDemux * demux)
{
return TRUE;
}
GstStreamType
gst_stream_type_from_mss_type (GstMssStreamType mtype)
{
switch (mtype) {
case MSS_STREAM_TYPE_AUDIO:
return GST_STREAM_TYPE_AUDIO;
case MSS_STREAM_TYPE_VIDEO:
return GST_STREAM_TYPE_VIDEO;
default:
return GST_STREAM_TYPE_UNKNOWN;
}
}

View file

@ -0,0 +1,86 @@
/* GStreamer
* Copyright (C) 2012 Smart TV Alliance
* Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
*
* gstmssdemux.h:
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MSSDEMUX_H__
#define __GST_MSSDEMUX_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <gst/base/gstdataqueue.h>
#include <gst/gstprotection.h>
#include "gstmssmanifest.h"
#include "../gstadaptivedemux.h"
G_BEGIN_DECLS
GST_DEBUG_CATEGORY_EXTERN (mssdemux2_debug);
#define GST_CAT_DEFAULT mssdemux2_debug
#define GST_TYPE_MSS_DEMUX2 \
(gst_mss_demux2_get_type())
#define GST_MSS_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MSS_DEMUX,GstMssDemux))
#define GST_MSS_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MSS_DEMUX,GstMssDemuxClass))
#define GST_IS_MSS_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MSS_DEMUX))
#define GST_IS_MSS_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MSS_DEMUX))
#define GST_MSS_DEMUX_CAST(obj) ((GstMssDemux *)(obj))
#define GST_TYPE_MSS_DEMUX_STREAM \
(gst_mss_demux_stream_get_type())
#define GST_MSS_DEMUX_STREAM(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MSS_DEMUX_STREAM,GstMssDemuxStream))
#define GST_MSS_DEMUX_STREAM_CAST(obj) ((GstMssDemuxStream *)obj)
typedef struct _GstMssDemuxStream GstMssDemuxStream;
typedef GstAdaptiveDemux2StreamClass GstMssDemuxStreamClass;
typedef struct _GstMssDemux2 GstMssDemux;
typedef struct _GstMssDemux2Class GstMssDemuxClass;
struct _GstMssDemuxStream {
GstAdaptiveDemux2Stream parent;
GstMssStream *manifest_stream;
};
struct _GstMssDemux2 {
GstAdaptiveDemux bin;
GstMssManifest *manifest;
gchar *base_url;
};
struct _GstMssDemux2Class {
GstAdaptiveDemuxClass parent_class;
};
GType gst_mss_demux2_get_type (void);
GType gst_mss_demux_stream_get_type (void);
GST_ELEMENT_REGISTER_DECLARE (mssdemux2);
G_END_DECLS
#endif /* __GST_MSSDEMUX_H__ */

View file

@ -0,0 +1,122 @@
/*
* Microsoft Smooth-Streaming fragment parsing library
*
* gstmssfragmentparser.h
*
* Copyright (C) 2016 Igalia S.L
* Copyright (C) 2016 Metrological
* Author: Philippe Normand <philn@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gstmssfragmentparser.h"
#include <gst/base/gstbytereader.h>
#include <string.h>
GST_DEBUG_CATEGORY_EXTERN (mssdemux2_debug);
#define GST_CAT_DEFAULT mssdemux2_debug
void
gst_mss_fragment_parser_init (GstMssFragmentParser * parser)
{
parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_INIT;
}
void
gst_mss_fragment_parser_clear (GstMssFragmentParser * parser)
{
if (parser->moof)
gst_isoff_moof_box_free (parser->moof);
parser->moof = NULL;
parser->current_fourcc = 0;
}
gboolean
gst_mss_fragment_parser_add_buffer (GstMssFragmentParser * parser,
GstBuffer * buffer)
{
GstByteReader reader;
GstMapInfo info;
guint64 size;
guint32 fourcc;
guint header_size;
gboolean error = FALSE;
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
return FALSE;
}
gst_byte_reader_init (&reader, info.data, info.size);
GST_TRACE ("Total buffer size: %u", gst_byte_reader_get_size (&reader));
do {
parser->current_fourcc = 0;
if (!gst_isoff_parse_box_header (&reader, &fourcc, NULL, &header_size,
&size)) {
break;
}
parser->current_fourcc = fourcc;
GST_LOG ("box %" GST_FOURCC_FORMAT " size %" G_GUINT64_FORMAT,
GST_FOURCC_ARGS (fourcc), size);
parser->current_fourcc = fourcc;
if (parser->current_fourcc == GST_ISOFF_FOURCC_MOOF) {
GstByteReader sub_reader;
g_assert (parser->moof == NULL);
gst_byte_reader_get_sub_reader (&reader, &sub_reader, size - header_size);
parser->moof = gst_isoff_moof_box_parse (&sub_reader);
if (parser->moof == NULL) {
GST_ERROR ("Failed to parse moof");
error = TRUE;
}
} else if (parser->current_fourcc == GST_ISOFF_FOURCC_MDAT) {
goto beach;
} else {
gst_byte_reader_skip (&reader, size - header_size);
}
} while (gst_byte_reader_get_remaining (&reader) > 0);
beach:
/* Do sanity check */
if (parser->current_fourcc != GST_ISOFF_FOURCC_MDAT || !parser->moof ||
parser->moof->traf->len == 0)
error = TRUE;
if (!error) {
GstTrafBox *traf = &g_array_index (parser->moof->traf, GstTrafBox, 0);
if (!traf->tfxd) {
GST_ERROR ("no tfxd box");
error = TRUE;
} else if (!traf->tfrf) {
GST_ERROR ("no tfrf box");
error = TRUE;
}
}
if (!error)
parser->status = GST_MSS_FRAGMENT_HEADER_PARSER_FINISHED;
GST_LOG ("Fragment parsing successful: %s", error ? "no" : "yes");
gst_buffer_unmap (buffer, &info);
return !error;
}

View file

@ -0,0 +1,53 @@
/*
* Microsoft Smooth-Streaming fragment parsing library
*
* gstmssfragmentparser.h
*
* Copyright (C) 2016 Igalia S.L
* Copyright (C) 2016 Metrological
* Author: Philippe Normand <philn@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library (COPYING); if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MSS_FRAGMENT_PARSER_H__
#define __GST_MSS_FRAGMENT_PARSER_H__
#include <gst/gst.h>
#include "../gstisoff.h"
G_BEGIN_DECLS
typedef enum _GstFragmentHeaderParserStatus
{
GST_MSS_FRAGMENT_HEADER_PARSER_INIT,
GST_MSS_FRAGMENT_HEADER_PARSER_FINISHED
} GstFragmentHeaderParserStatus;
typedef struct _GstMssFragmentParser
{
GstFragmentHeaderParserStatus status;
GstMoofBox *moof;
guint32 current_fourcc;
} GstMssFragmentParser;
void gst_mss_fragment_parser_init (GstMssFragmentParser * parser);
void gst_mss_fragment_parser_clear (GstMssFragmentParser * parser);
gboolean gst_mss_fragment_parser_add_buffer (GstMssFragmentParser * parser, GstBuffer * buf);
G_END_DECLS
#endif /* __GST_MSS_FRAGMENT_PARSER_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,85 @@
/* GStreamer
* Copyright (C) 2012 Smart TV Alliance
* Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
*
* gstmssmanifest.h:
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_MSS_MANIFEST_H__
#define __GST_MSS_MANIFEST_H__
#include <glib.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
G_BEGIN_DECLS
typedef struct _GstMssManifest GstMssManifest;
typedef struct _GstMssStream GstMssStream;
typedef enum _GstMssStreamType {
MSS_STREAM_TYPE_UNKNOWN = 0,
MSS_STREAM_TYPE_VIDEO = 1,
MSS_STREAM_TYPE_AUDIO = 2
} GstMssStreamType;
GstMssManifest * gst_mss_manifest_new (GstBuffer * data);
void gst_mss_manifest_free (GstMssManifest * manifest);
GSList * gst_mss_manifest_get_streams (GstMssManifest * manifest);
guint64 gst_mss_manifest_get_timescale (GstMssManifest * manifest);
guint64 gst_mss_manifest_get_duration (GstMssManifest * manifest);
GstClockTime gst_mss_manifest_get_gst_duration (GstMssManifest * manifest);
void gst_mss_manifest_seek (GstMssManifest * manifest, gboolean forward, gint64 time);
gboolean gst_mss_manifest_change_bitrate (GstMssManifest *manifest, guint64 bitrate);
guint64 gst_mss_manifest_get_current_bitrate (GstMssManifest * manifest);
gboolean gst_mss_manifest_is_live (GstMssManifest * manifest);
gint64 gst_mss_manifest_get_dvr_window_length (GstMssManifest * manifest);
gint gst_mss_manifest_get_look_ahead_fragments_count (GstMssManifest * manifest);
void gst_mss_manifest_reload_fragments (GstMssManifest * manifest, GstBuffer * data);
GstClockTime gst_mss_manifest_get_min_fragment_duration (GstMssManifest * manifest);
const gchar * gst_mss_manifest_get_protection_system_id (GstMssManifest * manifest);
const gchar * gst_mss_manifest_get_protection_data (GstMssManifest * manifest);
gboolean gst_mss_manifest_get_live_seek_range (GstMssManifest * manifest, gint64 * start, gint64 * stop);
GstMssStreamType gst_mss_stream_get_type (GstMssStream *stream);
GstCaps * gst_mss_stream_get_caps (GstMssStream * stream);
gboolean gst_mss_stream_select_bitrate (GstMssStream * stream, guint64 bitrate);
guint64 gst_mss_stream_get_current_bitrate (GstMssStream * stream);
void gst_mss_stream_set_active (GstMssStream * stream, gboolean active);
guint64 gst_mss_stream_get_timescale (GstMssStream * stream);
GstFlowReturn gst_mss_stream_get_fragment_url (GstMssStream * stream, gchar ** url);
GstClockTime gst_mss_stream_get_fragment_gst_timestamp (GstMssStream * stream);
GstClockTime gst_mss_stream_get_fragment_gst_duration (GstMssStream * stream);
gboolean gst_mss_stream_has_next_fragment (GstMssStream * stream);
GstFlowReturn gst_mss_stream_advance_fragment (GstMssStream * stream);
GstFlowReturn gst_mss_stream_regress_fragment (GstMssStream * stream);
void gst_mss_stream_seek (GstMssStream * stream, gboolean forward, GstSeekFlags flags, gint64 time, gint64 * final_time);
const gchar * gst_mss_stream_get_lang (GstMssStream * stream);
const gchar * gst_mss_stream_type_name (GstMssStreamType streamtype);
void gst_mss_manifest_live_adapter_push(GstMssStream * stream, GstBuffer * buffer);
gsize gst_mss_manifest_live_adapter_available(GstMssStream * stream);
GstBuffer * gst_mss_manifest_live_adapter_take_buffer(GstMssStream * stream, gsize nbytes);
void gst_mss_manifest_live_adapter_clear (GstMssStream * stream);
gboolean gst_mss_stream_fragment_parsing_needed(GstMssStream * stream);
void gst_mss_stream_parse_fragment(GstMssStream * stream, GstBuffer * buffer);
G_END_DECLS
#endif /* __GST_MSS_MANIFEST_H__ */

View file

@ -0,0 +1,52 @@
/* GStreamer
* Copyright (C) 2021-2022 Jan Schmidt <jan@centricular.com>
*
* 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 "dash/gstdashdemux.h"
#include "hls/gsthlsdemux.h"
#include "mss/gstmssdemux.h"
#include "../soup/gstsouploader.h"
static gboolean
plugin_init (GstPlugin * plugin)
{
gboolean ret = TRUE;
#ifndef STATIC_SOUP
if (!gst_soup_load_library ()) {
GST_WARNING ("Failed to load libsoup library");
return TRUE;
}
#endif
ret |= GST_ELEMENT_REGISTER (hlsdemux2, plugin);
ret |= GST_ELEMENT_REGISTER (dashdemux2, plugin);
ret |= GST_ELEMENT_REGISTER (mssdemux2, plugin);
return ret;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
adaptivedemux2,
"Adaptive Streaming 2 plugin", plugin_init, VERSION,
GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View file

@ -1,4 +1,5 @@
subdir('aalib') subdir('aalib')
subdir('adaptivedemux2')
subdir('cairo') subdir('cairo')
subdir('flac') subdir('flac')
subdir('gdk_pixbuf') subdir('gdk_pixbuf')

View file

@ -30,8 +30,14 @@
#endif #endif
#endif /* G_OS_WIN32 */ #endif /* G_OS_WIN32 */
#ifdef BUILDING_ADAPTIVEDEMUX2
GST_DEBUG_CATEGORY (gst_adaptivedemux_soup_debug);
#define GST_CAT_DEFAULT gst_adaptivedemux_soup_debug
#else
GST_DEBUG_CATEGORY_EXTERN (gst_soup_debug); GST_DEBUG_CATEGORY_EXTERN (gst_soup_debug);
#define GST_CAT_DEFAULT gst_soup_debug #define GST_CAT_DEFAULT gst_soup_debug
#endif
#ifndef STATIC_SOUP #ifndef STATIC_SOUP
@ -106,6 +112,11 @@ typedef struct _GstSoupVTable
goffset (*_soup_message_headers_get_content_length) (SoupMessageHeaders * hdrs); goffset (*_soup_message_headers_get_content_length) (SoupMessageHeaders * hdrs);
const char *(*_soup_message_headers_get_content_type) (SoupMessageHeaders * hdrs, const char *(*_soup_message_headers_get_content_type) (SoupMessageHeaders * hdrs,
GHashTable ** value); GHashTable ** value);
#ifdef BUILDING_ADAPTIVEDEMUX2
gboolean (*_soup_message_headers_get_content_range) (SoupMessageHeaders *hdrs, goffset *start,
goffset *end, goffset *total_length);
void (*_soup_message_headers_set_range) (SoupMessageHeaders *hdrs, goffset start, goffset end);
#endif
SoupEncoding (*_soup_message_headers_get_encoding) (SoupMessageHeaders * hdrs); SoupEncoding (*_soup_message_headers_get_encoding) (SoupMessageHeaders * hdrs);
const char *(*_soup_message_headers_get_one) (SoupMessageHeaders * hdrs, const char *(*_soup_message_headers_get_one) (SoupMessageHeaders * hdrs,
const char * name); const char * name);
@ -146,6 +157,11 @@ gst_soup_load_library (void)
g_assert (g_module_supported ()); g_assert (g_module_supported ());
#ifdef BUILDING_ADAPTIVEDEMUX2
GST_DEBUG_CATEGORY_INIT (gst_adaptivedemux_soup_debug, "adaptivedemux2-soup",
0, "adaptivedemux2-soup");
#endif
#ifdef HAVE_RTLD_NOLOAD #ifdef HAVE_RTLD_NOLOAD
{ {
gpointer handle = NULL; gpointer handle = NULL;
@ -262,6 +278,10 @@ gst_soup_load_library (void)
LOAD_SYMBOL (soup_message_headers_foreach); LOAD_SYMBOL (soup_message_headers_foreach);
LOAD_SYMBOL (soup_message_headers_get_content_length); LOAD_SYMBOL (soup_message_headers_get_content_length);
LOAD_SYMBOL (soup_message_headers_get_content_type); LOAD_SYMBOL (soup_message_headers_get_content_type);
#ifdef BUILDING_ADAPTIVEDEMUX2
LOAD_SYMBOL (soup_message_headers_get_content_range);
LOAD_SYMBOL (soup_message_headers_set_range);
#endif
LOAD_SYMBOL (soup_message_headers_get_encoding); LOAD_SYMBOL (soup_message_headers_get_encoding);
LOAD_SYMBOL (soup_message_headers_get_one); LOAD_SYMBOL (soup_message_headers_get_one);
LOAD_SYMBOL (soup_message_headers_remove); LOAD_SYMBOL (soup_message_headers_remove);
@ -792,6 +812,34 @@ _soup_message_headers_get_content_type (SoupMessageHeaders * hdrs,
#endif #endif
} }
#ifdef BUILDING_ADAPTIVEDEMUX2
gboolean
_soup_message_headers_get_content_range (SoupMessageHeaders * hdrs,
goffset * start, goffset * end, goffset * total_length)
{
#ifdef STATIC_SOUP
return soup_message_headers_get_content_range (hdrs, start, end,
total_length);
#else
g_assert (gst_soup_vtable._soup_message_headers_get_content_range != NULL);
return gst_soup_vtable._soup_message_headers_get_content_range (hdrs, start,
end, total_length);
#endif
}
void
_soup_message_headers_set_range (SoupMessageHeaders * hdrs, goffset start,
goffset end)
{
#ifdef STATIC_SOUP
soup_message_headers_set_range (hdrs, start, end);
#else
g_assert (gst_soup_vtable._soup_message_headers_set_range != NULL);
gst_soup_vtable._soup_message_headers_set_range (hdrs, start, end);
#endif
}
#endif
void void
_soup_auth_authenticate (SoupAuth * auth, const char *username, _soup_auth_authenticate (SoupAuth * auth, const char *username,
const char *password) const char *password)

View file

@ -96,6 +96,14 @@ void _soup_message_disable_feature (SoupMessage *msg, GType feature_type);
const char *_soup_message_headers_get_content_type (SoupMessageHeaders *hdrs, const char *_soup_message_headers_get_content_type (SoupMessageHeaders *hdrs,
GHashTable **params); GHashTable **params);
#ifdef BUILDING_ADAPTIVEDEMUX2
gboolean _soup_message_headers_get_content_range (SoupMessageHeaders *hdrs,
goffset *start, goffset *end,
goffset *total_length);
void _soup_message_headers_set_range (SoupMessageHeaders *hdrs, goffset start, goffset end);
#endif
void _soup_auth_authenticate (SoupAuth *auth, const char *username, void _soup_auth_authenticate (SoupAuth *auth, const char *username,
const char *password); const char *password);

View file

@ -44,6 +44,7 @@ option('wavparse', type : 'feature', value : 'auto')
option('y4m', type : 'feature', value : 'auto') option('y4m', type : 'feature', value : 'auto')
# Feature options for plugins with external deps # Feature options for plugins with external deps
option('adaptivedemux2', type : 'feature', value : 'auto', description : '2nd generation adaptive demuxer plugin')
option('aalib', type : 'feature', value : 'auto', description : 'aalib text console video sink plugin') option('aalib', type : 'feature', value : 'auto', description : 'aalib text console video sink plugin')
option('bz2', type : 'feature', value : 'auto', description : 'libbz2 support in the matroska plugin') option('bz2', type : 'feature', value : 'auto', description : 'libbz2 support in the matroska plugin')
option('cairo', type : 'feature', value : 'auto', description : 'Cairo overlay plugin') option('cairo', type : 'feature', value : 'auto', description : 'Cairo overlay plugin')
@ -74,6 +75,10 @@ option('vpx', type : 'feature', value : 'auto', description : 'VP8 and VP9 video
option('waveform', type : 'feature', value : 'auto', description : 'Windows waveform audio sink plugin') option('waveform', type : 'feature', value : 'auto', description : 'Windows waveform audio sink plugin')
option('wavpack', type : 'feature', value : 'auto', description : 'Wavpack audio codec plugin') option('wavpack', type : 'feature', value : 'auto', description : 'Wavpack audio codec plugin')
# HLS plugin options
option('hls-crypto', type : 'combo', value : 'auto', choices : ['auto', 'nettle', 'libgcrypt', 'openssl'],
description: 'Crypto library to use for HLS plugin')
# rpicamsrc plugin options # rpicamsrc plugin options
option('rpicamsrc', type : 'feature', value : 'auto', description : 'Raspberry Pi camera module plugin') option('rpicamsrc', type : 'feature', value : 'auto', description : 'Raspberry Pi camera module plugin')
option('rpi-header-dir', type : 'string', value : '/opt/vc/include', description : 'Directory where VideoCore/MMAL headers and bcm_host.h can be found') option('rpi-header-dir', type : 'string', value : '/opt/vc/include', description : 'Directory where VideoCore/MMAL headers and bcm_host.h can be found')

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,883 @@
/* GStreamer
*
* unit test for hlsdemux
*
* Copyright (C) <2012> Fluendo S.A <support@fluendo.com>
* Authors: Andoni Morales Alastruey <amorales@fluendo.com>
* Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gst/check/gstcheck.h>
#undef GST_CAT_DEFAULT
#include "m3u8.h"
#include "m3u8.c"
GST_DEBUG_CATEGORY (hls_debug);
static const gchar *INVALID_PLAYLIST = "#EXTM3 UINVALID";
static const gchar *ON_DEMAND_PLAYLIST = "#EXTM3U \n\
#EXT-X-TARGETDURATION:10\n\
#EXTINF:10,Test\n\
http://media.example.com/001.ts\n\
#EXTINF:10,Test\n\
http://media.example.com/002.ts\n\
#EXTINF:10,Test\n\
http://media.example.com/003.ts\n\
#EXTINF:10,Test\n\
http://media.example.com/004.ts\n\
#EXT-X-ENDLIST";
static const gchar *DOUBLES_PLAYLIST = "#EXTM3U \n\
#EXT-X-TARGETDURATION:10\n\
#EXTINF:10.321,Test\n\
http://media.example.com/001.ts\n\
#EXTINF:9.6789,Test\n\
http://media.example.com/002.ts\n\
#EXTINF:10.2344,Test\n\
http://media.example.com/003.ts\n\
#EXTINF:9.92,Test\n\
http://media.example.com/004.ts\n\
#EXT-X-ENDLIST";
static const gchar *LIVE_PLAYLIST = "#EXTM3U\n\
#EXT-X-TARGETDURATION:8\n\
#EXT-X-MEDIA-SEQUENCE:2680\n\
\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence2680.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence2681.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence2682.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence2683.ts";
static const gchar *LIVE_ROTATED_PLAYLIST = "#EXTM3U\n\
#EXT-X-TARGETDURATION:8\n\
#EXT-X-MEDIA-SEQUENCE:3001\n\
\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence3001.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence3002.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence3003.ts\n\
#EXTINF:8,\n\
https://priv.example.com/fileSequence3004.ts";
static const gchar *VARIANT_PLAYLIST = "#EXTM3U \n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000\n\
http://example.com/low.m3u8\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000\n\
http://example.com/mid.m3u8\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=768000\n\
http://example.com/hi.m3u8\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio-only.m3u8";
static const gchar *VARIANT_PLAYLIST_WITH_URI_MISSING = "#EXTM3U \n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000\n\
http://example.com/low.m3u8\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000\n\
\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=768000\n\
http://example.com/hi.m3u8\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n\
http://example.com/audio-only.m3u8";
static const gchar *EMPTY_LINES_VARIANT_PLAYLIST = "#EXTM3U \n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000\n\n\
http://example.com/low.m3u8\n\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000\n\n\
http://example.com/mid.m3u8\n\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=768000\n\n\
http://example.com/hi.m3u8\n\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n\n\
http://example.com/audio-only.m3u8";
static const gchar *WINDOWS_EMPTY_LINES_VARIANT_PLAYLIST = "#EXTM3U \r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000\r\n\r\n\
http://example.com/low.m3u8\r\n\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000\r\n\r\n\
http://example.com/mid.m3u8\r\n\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=768000\r\n\r\n\
http://example.com/hi.m3u8\r\n\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\r\n\r\n\
http://example.com/audio-only.m3u8";
static const gchar *EMPTY_LINES_PLAYLIST = "#EXTM3U \n\n\
#EXT-X-TARGETDURATION:10\n\
#EXTINF:10,Testr\n\n\
http://media.example.com/001.ts\n\n\
#EXTINF:10,Test\n\n\
http://media.example.com/002.ts\n\n\
#EXTINF:10,Test\n\n\
http://media.example.com/003.ts\n\n\
#EXTINF:10,Test\n\n\
http://media.example.com/004.ts\n\n\
#EXT-X-ENDLIST";
static const gchar *WINDOWS_EMPTY_LINES_PLAYLIST = "#EXTM3U \r\n\
#EXT-X-TARGETDURATION:10\r\n\r\n\
#EXTINF:10,Test\r\n\r\n\
http://media.example.com/001.ts\r\n\r\n\
#EXTINF:10,Test\r\n\r\n\
http://media.example.com/002.ts\r\n\r\n\
#EXTINF:10,Test\r\n\r\n\
http://media.example.com/003.ts\r\n\r\n\
#EXTINF:10,Test\r\n\r\n\
http://media.example.com/004.ts\r\n\r\n\
#EXT-X-ENDLIST";
static const gchar *BYTE_RANGES_PLAYLIST = "#EXTM3U \n\
#EXT-X-TARGETDURATION:40\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000@100\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000@1000\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000@2000\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000@3000\n\
http://media.example.com/all.ts\n\
#EXT-X-ENDLIST";
static const gchar *BYTE_RANGES_ACC_OFFSET_PLAYLIST = "#EXTM3U \n\
#EXT-X-TARGETDURATION:40\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000\n\
http://media.example.com/all.ts\n\
#EXTINF:10,Test\n\
#EXT-X-BYTERANGE:1000\n\
http://media.example.com/all.ts\n\
#EXT-X-ENDLIST";
static const gchar *AES_128_ENCRYPTED_PLAYLIST = "#EXTM3U \n\
#EXT-X-TARGETDURATION:10\n\
#EXTINF:10,Test\n\
http://media.example.com/mid/video-only-001.ts\n\
#EXT-X-KEY:METHOD=NONE\n\
#EXTINF:10,Test\n\
http://media.example.com/mid/video-only-002.ts\n\
#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.bin\"\n\
#EXTINF:10,Test\n\
http://media.example.com/mid/video-only-003.ts\n\
#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key2.bin\",IV=0x00000000000000000000000000000001\n\
#EXTINF:10,Test\n\
http://media.example.com/mid/video-only-004.ts\n\
#EXTINF:10,Test\n\
http://media.example.com/mid/video-only-005.ts\n\
#EXT-X-ENDLIST";
static const gchar *WINDOWS_LINE_ENDINGS_PLAYLIST = "#EXTM3U \r\n\
#EXT-X-TARGETDURATION:10\r\n\
#EXTINF:10,Test\r\n\
http://media.example.com/001.ts\r\n\
#EXTINF:10,Test\r\n\
http://media.example.com/002.ts\r\n\
#EXTINF:10,Test\r\n\
http://media.example.com/003.ts\r\n\
#EXTINF:10,Test\r\n\
http://media.example.com/004.ts\r\n\
#EXT-X-ENDLIST";
static const gchar *WINDOWS_LINE_ENDINGS_VARIANT_PLAYLIST = "#EXTM3U \r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000\r\n\
http://example.com/low.m3u8\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000\r\n\
http://example.com/mid.m3u8\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=768000\r\n\
http://example.com/hi.m3u8\r\n\
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\r\n\
http://example.com/audio-only.m3u8";
static const gchar *MAP_TAG_PLAYLIST = "#EXTM3U \n\
#EXT-X-VERSION:7\n\
#EXT-X-MAP:URI=\"init1.mp4\",BYTERANGE=\"50@50\"\n\
#EXTINF:6.00000,\n\
#EXT-X-BYTERANGE:100@50\n\
main.mp4\n\
#EXTINF:6.00000,\n\
#EXT-X-BYTERANGE:100@150\n\
main.mp4\n\
#EXT-X-MAP:URI=\"init2.mp4\"\n\
#EXTINF:6.00000,\n\
#EXT-X-BYTERANGE:100@300\n\
main.mp4\n\
#EXT-X-ENDLIST";
static GstHLSMediaPlaylist *
load_m3u8 (const gchar * data)
{
GstHLSMediaPlaylist *playlist;
playlist = gst_hls_media_playlist_parse (g_strdup (data),
"http://localhost/test.m3u8", NULL);
fail_unless (playlist != NULL);
return playlist;
}
static GstHLSMasterPlaylist *
load_master_playlist (const gchar * data)
{
GstHLSMasterPlaylist *master;
master = gst_hls_master_playlist_new_from_data (g_strdup (data),
"http://localhost/test.m3u8");
fail_unless (master != NULL);
return master;
}
GST_START_TEST (test_load_main_playlist_invalid)
{
GstHLSMasterPlaylist *master;
master =
gst_hls_master_playlist_new_from_data (g_strdup (INVALID_PLAYLIST), NULL);
fail_unless (master == NULL);
}
GST_END_TEST;
GST_START_TEST (test_load_main_playlist_rendition)
{
GstHLSMediaPlaylist *playlist;
playlist = load_m3u8 (ON_DEMAND_PLAYLIST);
assert_equals_int (playlist->segments->len, 4);
assert_equals_int (playlist->version, 1);
gst_hls_media_playlist_unref (playlist);
}
GST_END_TEST;
static void
do_test_load_main_playlist_variant (const gchar * playlist)
{
GstHLSMasterPlaylist *master;
GstHLSVariantStream *stream;
GList *tmp;
master = load_master_playlist (playlist);
assert_equals_int (g_list_length (master->variants), 4);
/* Audio-Only */
tmp = g_list_first (master->variants);
stream = tmp->data;
assert_equals_int (stream->bandwidth, 65000);
assert_equals_int (stream->program_id, 1);
assert_equals_string (stream->uri, "http://example.com/audio-only.m3u8");
assert_equals_string (stream->codecs, "mp4a.40.5");
/* Low */
tmp = g_list_next (tmp);
stream = tmp->data;
assert_equals_int (stream->bandwidth, 128000);
assert_equals_int (stream->program_id, 1);
assert_equals_string (stream->uri, "http://example.com/low.m3u8");
/* Mid */
tmp = g_list_next (tmp);
stream = tmp->data;
assert_equals_int (stream->bandwidth, 256000);
assert_equals_int (stream->program_id, 1);
assert_equals_string (stream->uri, "http://example.com/mid.m3u8");
/* High */
tmp = g_list_next (tmp);
stream = tmp->data;
assert_equals_int (stream->bandwidth, 768000);
assert_equals_int (stream->program_id, 1);
assert_equals_string (stream->uri, "http://example.com/hi.m3u8");
/* Check the first playlist is selected */
assert_equals_int (master->default_variant != NULL, TRUE);
assert_equals_int (master->default_variant->bandwidth, 128000);
gst_hls_master_playlist_unref (master);
}
GST_START_TEST (test_load_main_playlist_variant)
{
do_test_load_main_playlist_variant (VARIANT_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_load_main_playlist_variant_with_missing_uri)
{
GstHLSMasterPlaylist *master;
master = load_master_playlist (VARIANT_PLAYLIST_WITH_URI_MISSING);
assert_equals_int (g_list_length (master->variants), 3);
gst_hls_master_playlist_unref (master);
}
GST_END_TEST;
GST_START_TEST (test_load_windows_line_endings_variant_playlist)
{
do_test_load_main_playlist_variant (WINDOWS_LINE_ENDINGS_VARIANT_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_load_main_playlist_with_empty_lines)
{
do_test_load_main_playlist_variant (EMPTY_LINES_VARIANT_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_load_windows_main_playlist_with_empty_lines)
{
do_test_load_main_playlist_variant (WINDOWS_EMPTY_LINES_VARIANT_PLAYLIST);
}
GST_END_TEST;
static void
check_on_demand_playlist (const gchar * data)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file;
pl = load_m3u8 (data);
/* Sequence should be 0 as it's an ondemand playlist */
assert_equals_int (pl->media_sequence, 0);
/* Check that we are not live */
assert_equals_int (gst_hls_media_playlist_is_live (pl), FALSE);
/* Check number of entries */
assert_equals_int (pl->segments->len, 4);
/* Check first media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_string (file->uri, "http://media.example.com/001.ts");
assert_equals_int (file->sequence, 0);
/* Check last media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 3));
assert_equals_string (file->uri, "http://media.example.com/004.ts");
assert_equals_int (file->sequence, 3);
gst_hls_media_playlist_unref (pl);
}
GST_START_TEST (test_on_demand_playlist)
{
check_on_demand_playlist (ON_DEMAND_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_windows_line_endings_playlist)
{
check_on_demand_playlist (WINDOWS_LINE_ENDINGS_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_empty_lines_playlist)
{
check_on_demand_playlist (EMPTY_LINES_PLAYLIST);
}
GST_END_TEST;
GST_START_TEST (test_windows_empty_lines_playlist)
{
check_on_demand_playlist (WINDOWS_EMPTY_LINES_PLAYLIST);
}
GST_END_TEST;
/* This test is for live streams in which we pause the stream for more than the
* DVR window and we resume playback. The playlist has rotated completely and
* there is a jump in the media sequence that must be handled correctly. */
GST_START_TEST (test_live_playlist_rotated)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file, *file2;
pl = load_m3u8 (LIVE_PLAYLIST);
/* Check first media segment */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_int (file->sequence, 2680);
gst_m3u8_media_segment_ref (file);
gst_hls_media_playlist_unref (pl);
pl = load_m3u8 (LIVE_ROTATED_PLAYLIST);
file2 = gst_hls_media_playlist_sync_to_segment (pl, file);
fail_unless (file2 != NULL);
gst_m3u8_media_segment_unref (file);
gst_m3u8_media_segment_unref (file2);
/* FIXME: Sequence should last - 3. Should it? */
/* Check first media segment */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_int (file->sequence, 3001);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_playlist_with_doubles_duration)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file;
gint64 start = -1;
gint64 stop = -1;
pl = load_m3u8 (DOUBLES_PLAYLIST);
/* Check first media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_float (file->duration / (double) GST_SECOND, 10.321);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 1));
assert_equals_float (file->duration / (double) GST_SECOND, 9.6789);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 2));
assert_equals_float (file->duration / (double) GST_SECOND, 10.2344);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 3));
assert_equals_float (file->duration / (double) GST_SECOND, 9.92);
fail_unless (gst_hls_media_playlist_get_seek_range (pl, &start, &stop));
assert_equals_int64 (start, 0);
assert_equals_float (stop / (double) GST_SECOND,
10.321 + 9.6789 + 10.2344 + 9.92);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_playlist_with_encryption)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file;
guint8 iv1[16] = { 0, };
guint8 iv2[16] = { 0, };
iv1[15] = 1;
iv2[15] = 2;
pl = load_m3u8 (AES_128_ENCRYPTED_PLAYLIST);
assert_equals_int (pl->segments->len, 5);
/* Check all media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
fail_unless (file->key == NULL);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 1));
fail_unless (file->key == NULL);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 2));
fail_unless (file->key != NULL);
assert_equals_string (file->key, "https://priv.example.com/key.bin");
fail_unless (memcmp (&file->iv, iv2, 16) == 0);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 3));
fail_unless (file->key != NULL);
assert_equals_string (file->key, "https://priv.example.com/key2.bin");
fail_unless (memcmp (&file->iv, iv1, 16) == 0);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 4));
fail_unless (file->key != NULL);
assert_equals_string (file->key, "https://priv.example.com/key2.bin");
fail_unless (memcmp (&file->iv, iv1, 16) == 0);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_parse_invalid_playlist)
{
GstHLSMediaPlaylist *pl;
pl = gst_hls_media_playlist_parse (g_strdup ("#INVALID"),
"http://localhost/test.m3u8", NULL);
fail_if (pl != NULL);
}
GST_END_TEST;
GST_START_TEST (test_sync_playlist_to_segment)
{
GstHLSMediaPlaylist *pl;
gchar *live_pl;
GstM3U8MediaSegment *file, *file2;
/* Test updates in live playlists */
pl = load_m3u8 (LIVE_PLAYLIST);
assert_equals_int (pl->segments->len, 4);
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
gst_m3u8_media_segment_ref (file);
gst_hls_media_playlist_unref (pl);
/* Add a new entry to the playlist and check the update */
live_pl = g_strdup_printf ("%s\n%s\n%s", LIVE_PLAYLIST, "#EXTINF:8",
"https://priv.example.com/fileSequence2684.ts");
pl = load_m3u8 (live_pl);
fail_unless (pl != NULL);
g_free (live_pl);
file2 = gst_hls_media_playlist_sync_to_segment (pl, file);
fail_unless (file2 != NULL);
gst_m3u8_media_segment_unref (file);
assert_equals_int (pl->segments->len, 5);
gst_hls_media_playlist_unref (pl);
/* Test sliding window */
pl = load_m3u8 (LIVE_PLAYLIST);
file = gst_hls_media_playlist_sync_to_segment (pl, file2);
fail_unless (file != NULL);
gst_m3u8_media_segment_unref (file);
gst_m3u8_media_segment_unref (file2);
assert_equals_int (pl->segments->len, 4);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_playlist_media_files)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file;
pl = load_m3u8 (ON_DEMAND_PLAYLIST);
/* Check number of entries */
assert_equals_int (pl->segments->len, 4);
/* Check first media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_string (file->uri, "http://media.example.com/001.ts");
assert_equals_int (file->sequence, 0);
assert_equals_float (file->duration, 10 * (double) GST_SECOND);
assert_equals_int (file->offset, 0);
assert_equals_int (file->size, -1);
assert_equals_string (file->title, "Test");
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_playlist_byte_range_media_files)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *file;
pl = load_m3u8 (BYTE_RANGES_PLAYLIST);
/* Check number of entries */
assert_equals_int (pl->segments->len, 4);
/* Check first media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_string (file->uri, "http://media.example.com/all.ts");
assert_equals_int (file->sequence, 0);
assert_equals_float (file->duration, 10 * (double) GST_SECOND);
assert_equals_int (file->offset, 100);
assert_equals_int (file->size, 1000);
/* Check last media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 3));
assert_equals_string (file->uri, "http://media.example.com/all.ts");
assert_equals_int (file->sequence, 3);
assert_equals_float (file->duration, 10 * (double) GST_SECOND);
assert_equals_int (file->offset, 3000);
assert_equals_int (file->size, 1000);
gst_hls_media_playlist_unref (pl);
pl = load_m3u8 (BYTE_RANGES_ACC_OFFSET_PLAYLIST);
/* Check number of entries */
assert_equals_int (pl->segments->len, 4);
/* Check first media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 0));
assert_equals_string (file->uri, "http://media.example.com/all.ts");
assert_equals_int (file->sequence, 0);
assert_equals_float (file->duration, 10 * (double) GST_SECOND);
assert_equals_int (file->offset, 0);
assert_equals_int (file->size, 1000);
/* Check last media segments */
file = GST_M3U8_MEDIA_SEGMENT (g_ptr_array_index (pl->segments, 3));
assert_equals_string (file->uri, "http://media.example.com/all.ts");
assert_equals_int (file->sequence, 3);
assert_equals_float (file->duration, 10 * (double) GST_SECOND);
assert_equals_int (file->offset, 3000);
assert_equals_int (file->size, 1000);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_advance_fragment)
{
GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *mf;
pl = load_m3u8 (BYTE_RANGES_PLAYLIST);
/* Check the next fragment */
mf = gst_hls_media_playlist_get_starting_segment (pl);
fail_unless (mf != NULL);
assert_equals_int (mf->discont, FALSE);
assert_equals_string (mf->uri, "http://media.example.com/all.ts");
assert_equals_uint64 (mf->stream_time, 0);
assert_equals_uint64 (mf->duration, 10 * GST_SECOND);
assert_equals_uint64 (mf->offset, 100);
assert_equals_uint64 (mf->offset + mf->size, 1100);
gst_m3u8_media_segment_unref (mf);
/* Check next media segments */
mf = gst_hls_media_playlist_advance_fragment (pl, mf, TRUE);
fail_unless (mf != NULL);
assert_equals_int (mf->discont, FALSE);
assert_equals_string (mf->uri, "http://media.example.com/all.ts");
assert_equals_uint64 (mf->stream_time, 10 * GST_SECOND);
assert_equals_uint64 (mf->duration, 10 * GST_SECOND);
assert_equals_uint64 (mf->offset, 1000);
assert_equals_uint64 (mf->offset + mf->size, 2000);
gst_m3u8_media_segment_unref (mf);
/* Check next media segments */
mf = gst_hls_media_playlist_advance_fragment (pl, mf, TRUE);
assert_equals_int (mf->discont, FALSE);
assert_equals_string (mf->uri, "http://media.example.com/all.ts");
assert_equals_uint64 (mf->stream_time, 20 * GST_SECOND);
assert_equals_uint64 (mf->duration, 10 * GST_SECOND);
assert_equals_uint64 (mf->offset, 2000);
assert_equals_uint64 (mf->offset + mf->size, 3000);
gst_m3u8_media_segment_unref (mf);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_get_duration)
{
GstHLSMediaPlaylist *pl;
/* Test duration for on-demand playlists */
pl = load_m3u8 (ON_DEMAND_PLAYLIST);
assert_equals_uint64 (gst_hls_media_playlist_get_duration (pl),
40 * GST_SECOND);
gst_hls_media_playlist_unref (pl);
/* Test duration for live playlists */
pl = load_m3u8 (LIVE_PLAYLIST);
assert_equals_uint64 (gst_hls_media_playlist_get_duration (pl),
GST_CLOCK_TIME_NONE);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_get_target_duration)
{
GstHLSMediaPlaylist *pl;
pl = load_m3u8 (ON_DEMAND_PLAYLIST);
assert_equals_uint64 (pl->targetduration, 10 * GST_SECOND);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
GST_START_TEST (test_get_stream_for_bitrate)
{
GstHLSMasterPlaylist *master;
GstHLSVariantStream *stream;
master = load_master_playlist (VARIANT_PLAYLIST);
stream = gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 0, 0);
assert_equals_int (stream->bandwidth, 65000);
stream =
gst_hls_master_playlist_get_variant_for_bitrate (master, NULL,
G_MAXINT32, 0);
assert_equals_int (stream->bandwidth, 768000);
stream =
gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 300000, 0);
assert_equals_int (stream->bandwidth, 256000);
stream =
gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 500000, 0);
assert_equals_int (stream->bandwidth, 256000);
stream =
gst_hls_master_playlist_get_variant_for_bitrate (master, NULL, 255000, 0);
assert_equals_int (stream->bandwidth, 128000);
gst_hls_master_playlist_unref (master);
}
GST_END_TEST;
GST_START_TEST (test_url_with_slash_query_param)
{
static const gchar *MASTER_PLAYLIST = "#EXTM3U \n"
"#EXT-X-VERSION:4\n"
"#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f, mp4a.40.2\", RESOLUTION=640x352\n"
"1251/media.m3u8?acl=/*1054559_h264_1500k.mp4\n";
GstHLSMasterPlaylist *master;
GstHLSVariantStream *stream;
master = load_master_playlist (MASTER_PLAYLIST);
assert_equals_int (g_list_length (master->variants), 1);
stream = g_list_nth_data (master->variants, 0);
assert_equals_string (stream->uri,
"http://localhost/1251/media.m3u8?acl=/*1054559_h264_1500k.mp4");
gst_hls_master_playlist_unref (master);
}
GST_END_TEST;
GST_START_TEST (test_stream_inf_tag)
{
static const gchar *MASTER_PLAYLIST = "#EXTM3U \n"
"#EXT-X-VERSION:4\n"
"#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f, mp4a.40.2\", RESOLUTION=640x352\n"
"media.m3u8\n";
GstHLSMasterPlaylist *master;
GstHLSVariantStream *stream;
master = load_master_playlist (MASTER_PLAYLIST);
assert_equals_int (g_list_length (master->variants), 1);
stream = g_list_nth_data (master->variants, 0);
assert_equals_int64 (stream->program_id, 1);
assert_equals_int64 (stream->width, 640);
assert_equals_int64 (stream->height, 352);
assert_equals_int64 (stream->bandwidth, 1251135);
assert_equals_string (stream->codecs, "avc1.42001f, mp4a.40.2");
gst_hls_master_playlist_unref (master);
}
GST_END_TEST;
GST_START_TEST (test_map_tag)
{
GstHLSMediaPlaylist *pl;
GPtrArray *segments;
GstM3U8MediaSegment *seg1, *seg2, *seg3;
GstM3U8InitFile *init1, *init2;
/* Test EXT-X-MAP tag
* This M3U8 has two EXT-X-MAP tag.
* the first one is applied to the 1st and 2nd segments, and the other is
* applied only to the 3rd segment
*/
pl = load_m3u8 (MAP_TAG_PLAYLIST);
segments = pl->segments;
assert_equals_int (segments->len, 3);
for (gsize i = 0; i < segments->len; i++) {
GstM3U8MediaSegment *file = g_ptr_array_index (segments, i);
GstM3U8InitFile *init_file = file->init_file;
fail_unless (init_file != NULL);
fail_unless (init_file->uri != NULL);
}
seg1 = g_ptr_array_index (segments, 0);
seg2 = g_ptr_array_index (segments, 1);
seg3 = g_ptr_array_index (segments, 2);
/* Segment 1 and 2 share the identical init segment */
fail_unless (seg1->init_file == seg2->init_file);
assert_equals_int (seg1->init_file->ref_count, 2);
fail_unless (seg2->init_file != seg3->init_file);
assert_equals_int (seg3->init_file->ref_count, 1);
init1 = seg1->init_file;
init2 = seg3->init_file;
fail_unless (g_strcmp0 (init1->uri, init2->uri));
assert_equals_int (init1->offset, 50);
assert_equals_int (init1->size, 50);
assert_equals_int (init2->offset, 0);
assert_equals_int (init2->size, -1);
gst_hls_media_playlist_unref (pl);
}
GST_END_TEST;
static Suite *
hlsdemux_suite (void)
{
Suite *s = suite_create ("hlsdemux_m3u8");
TCase *tc_m3u8 = tcase_create ("m3u8client");
GST_DEBUG_CATEGORY_INIT (hls_debug, "hlsdemux_m3u", 0, "hlsdemux m3u test");
suite_add_tcase (s, tc_m3u8);
tcase_add_test (tc_m3u8, test_load_main_playlist_invalid);
tcase_add_test (tc_m3u8, test_load_main_playlist_rendition);
tcase_add_test (tc_m3u8, test_load_main_playlist_variant);
tcase_add_test (tc_m3u8, test_load_main_playlist_variant_with_missing_uri);
tcase_add_test (tc_m3u8, test_load_windows_line_endings_variant_playlist);
tcase_add_test (tc_m3u8, test_load_main_playlist_with_empty_lines);
tcase_add_test (tc_m3u8, test_load_windows_main_playlist_with_empty_lines);
tcase_add_test (tc_m3u8, test_on_demand_playlist);
tcase_add_test (tc_m3u8, test_windows_line_endings_playlist);
tcase_add_test (tc_m3u8, test_windows_empty_lines_playlist);
tcase_add_test (tc_m3u8, test_empty_lines_playlist);
tcase_add_test (tc_m3u8, test_live_playlist_rotated);
tcase_add_test (tc_m3u8, test_playlist_with_doubles_duration);
tcase_add_test (tc_m3u8, test_playlist_with_encryption);
tcase_add_test (tc_m3u8, test_parse_invalid_playlist);
tcase_add_test (tc_m3u8, test_sync_playlist_to_segment);
tcase_add_test (tc_m3u8, test_playlist_media_files);
tcase_add_test (tc_m3u8, test_playlist_byte_range_media_files);
tcase_add_test (tc_m3u8, test_advance_fragment);
tcase_add_test (tc_m3u8, test_get_duration);
tcase_add_test (tc_m3u8, test_get_target_duration);
tcase_add_test (tc_m3u8, test_get_stream_for_bitrate);
tcase_add_test (tc_m3u8, test_url_with_slash_query_param);
tcase_add_test (tc_m3u8, test_stream_inf_tag);
tcase_add_test (tc_m3u8, test_map_tag);
return s;
}
GST_CHECK_MAIN (hlsdemux);

Some files were not shown because too many files have changed in this diff Show more