Add HLS sink with multi-variant playlist support

`hlsmultivariantsink` adds support for the following as per RFC 8216

- Multivariant/master playlist
- Alternate Renditions
- Variant Streams

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1515>
This commit is contained in:
Sanchayan Maity 2024-06-03 14:15:28 +05:30
parent f7ba4c40a7
commit 4218e88fce
10 changed files with 3451 additions and 0 deletions

57
Cargo.lock generated
View file

@ -917,6 +917,15 @@ version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]]
name = "bitreader"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066"
dependencies = [
"cfg-if",
]
[[package]]
name = "bitstream-io"
version = "2.6.0"
@ -1333,6 +1342,22 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "cros-codecs"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "277a30a0ddadfa014380ee30cc60330d260369855417c492fa94421d7c7e9229"
dependencies = [
"anyhow",
"bitreader",
"byteorder",
"bytes",
"crc32fast",
"enumn",
"log",
"thiserror 1.0.69",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -1783,6 +1808,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enumn"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
dependencies = [
"proc-macro2",
"quote 1.0.38",
"syn 2.0.96",
]
[[package]]
name = "env_filter"
version = "0.1.3"
@ -2688,6 +2724,27 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "gst-plugin-hlsmultivariantsink"
version = "0.14.0-alpha.1"
dependencies = [
"anyhow",
"chrono",
"cros-codecs",
"gio",
"gst-plugin-fmp4",
"gst-plugin-hlssink3",
"gst-plugin-version-helper",
"gstreamer",
"gstreamer-app",
"gstreamer-audio",
"gstreamer-check",
"gstreamer-pbutils",
"gstreamer-video",
"m3u8-rs",
"sprintf",
]
[[package]]
name = "gst-plugin-hlssink3"
version = "0.14.0-alpha.1"

View file

@ -25,6 +25,7 @@ members = [
"mux/mp4",
"net/aws",
"net/hlsmultivariantsink",
"net/hlssink3",
"net/mpegtslive",
"net/ndi",

View file

@ -3680,6 +3680,267 @@
"tracers": {},
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
},
"hlsmultivariantsink": {
"description": "GStreamer HLS (HTTP Live Streaming) multi-variant sink Plugin",
"elements": {
"hlsmultivariantsink": {
"author": "Sanchayan Maity <sanchayan@asymptotic.io>",
"description": "HTTP Live Streaming sink",
"hierarchy": [
"GstHlsMultivariantSink",
"GstBin",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"interfaces": [
"GstChildProxy"
],
"klass": "Sink/Muxer",
"pad-templates": {
"audio_%%u": {
"caps": "ANY",
"direction": "sink",
"presence": "request",
"type": "HlsMultivariantSinkPad"
},
"video_%%u": {
"caps": "ANY",
"direction": "sink",
"presence": "request",
"type": "HlsMultivariantSinkPad"
}
},
"properties": {
"max-files": {
"blurb": "Maximum number of files to keep on disk. Once the maximum is reached, old files start to be deleted to make room for new ones.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "10",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"multivariant-playlist-location": {
"blurb": "Location of the multivariant playlist file to write",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "multivariant.m3u8",
"mutable": "null",
"readable": true,
"type": "gchararray",
"writable": true
},
"muxer-type": {
"blurb": "The muxer to use, cmafmux or mpegtsmux, accordingly selects hlssink3 or hlscmafsink",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "cmaf (0)",
"mutable": "null",
"readable": true,
"type": "GstHlsMultivariantSinkMuxerType",
"writable": true
},
"playlist-length": {
"blurb": "Length of HLS playlist. To allow players to conform to section 6.3.3 of the HLS specification, this should be at least 3. If set to 0, the playlist will be infinite.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "5",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"playlist-type": {
"blurb": "The type of the playlist to use. When VOD type is set, the playlist will be live until the pipeline ends execution.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "unspecified (0)",
"mutable": "null",
"readable": true,
"type": "GstHlsMultivariantSinkPlaylistType",
"writable": true
},
"send-keyframe-requests": {
"blurb": "Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "true",
"mutable": "null",
"readable": true,
"type": "gboolean",
"writable": true
},
"target-duration": {
"blurb": "The target duration in seconds of a segment/file. (0 - disabled, useful for management of segment duration by the streaming server)",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "15",
"max": "-1",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
}
},
"rank": "none",
"signals": {
"delete-fragment": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "gboolean",
"when": "last"
},
"get-fragment-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
},
"get-init-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
},
"get-multivariant-playlist-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
},
"get-playlist-stream": {
"args": [
{
"name": "arg0",
"type": "gchararray"
}
],
"return-type": "GOutputStream",
"when": "last"
}
}
}
},
"filename": "gsthlsmultivariantsink",
"license": "MPL",
"other-types": {
"GstHlsMultivariantSinkMuxerType": {
"kind": "enum",
"values": [
{
"desc": "cmaf",
"name": "cmaf",
"value": "0"
},
{
"desc": "mpegts",
"name": "mpegts",
"value": "1"
}
]
},
"GstHlsMultivariantSinkPlaylistType": {
"kind": "enum",
"values": [
{
"desc": "Unspecified: The tag `#EXT-X-PLAYLIST-TYPE` won't be present in the playlist during the pipeline processing.",
"name": "unspecified",
"value": "0"
},
{
"desc": "Event: No segments will be removed from the playlist. At the end of the processing, the tag `#EXT-X-ENDLIST` is added to the playlist. The tag `#EXT-X-PLAYLIST-TYPE:EVENT` will be present in the playlist.",
"name": "event",
"value": "1"
},
{
"desc": "Vod: The playlist behaves like the `event` option (a live event), but at the end of the processing, the playlist will be set to `#EXT-X-PLAYLIST-TYPE:VOD`.",
"name": "vod",
"value": "2"
}
]
},
"HlsMultivariantSinkPad": {
"hierarchy": [
"HlsMultivariantSinkPad",
"GstGhostPad",
"GstProxyPad",
"GstPad",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"kind": "object",
"properties": {
"alternate-rendition": {
"blurb": "Alternate Rendition",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "variant-stream, is-i-frame=(boolean)false, uri=(string)\"\", bandwidth=(guint64)0, codecs=(string)NULL, audio=(string)NULL, video=(string)NULL;",
"mutable": "ready",
"readable": true,
"type": "GstStructure",
"writable": true
},
"variant": {
"blurb": "Variant Stream",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "variant-stream, is-i-frame=(boolean)false, uri=(string)\"\", bandwidth=(guint64)0, codecs=(string)NULL, audio=(string)NULL, video=(string)NULL;",
"mutable": "ready",
"readable": true,
"type": "GstStructure",
"writable": true
}
}
}
},
"package": "gst-plugin-hlsmultivariantsink",
"source": "gst-plugin-hlsmultivariantsink",
"tracers": {},
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
},
"hlssink3": {
"description": "GStreamer HLS (HTTP Live Streaming) Plugin",
"elements": {

View file

@ -151,6 +151,7 @@ plugins = {
'extra-deps': {'openssl': ['>=1.1']},
},
'mpegtslive': {'library': 'libgstmpegtslive'},
'hlsmultivariantsink': {'library': 'libgsthlsmultivariantsink'},
'hlssink3': {'library': 'libgsthlssink3'},
'ndi': {'library': 'libgstndi'},
'onvif': {

View file

@ -27,6 +27,7 @@ option('mp4', type: 'feature', value: 'auto', description: 'Build mp4 plugin')
# net
option('aws', type: 'feature', value: 'auto', description: 'Build aws plugin')
option('hlsmultivariantsink', type: 'feature', value: 'auto', description: 'Build hlsmultivariantsink plugin')
option('hlssink3', type: 'feature', value: 'auto', description: 'Build hlssink3 plugin')
option('mpegtslive', type: 'feature', value: 'auto', description: 'Build mpegtslive plugin')
option('ndi', type: 'feature', value: 'auto', description: 'Build ndi plugin')

View file

@ -0,0 +1,53 @@
[package]
name = "gst-plugin-hlsmultivariantsink"
description = "GStreamer HLS (HTTP Live Streaming) multi-variant sink Plugin"
repository.workspace = true
version.workspace = true
authors = ["Sanchayan Maity <sanchayan@asymptotic.io>"]
edition.workspace = true
license = "MPL-2.0"
rust-version.workspace = true
[dependencies]
gst.workspace = true
gst-app.workspace = true
gio.workspace = true
m3u8-rs = "5.0"
chrono = "0.4"
sprintf = "0.2"
gst-pbutils = { workspace = true, features = ["v1_22"] }
cros-codecs = { version = "0.0.4", default-features = false }
[dev-dependencies]
gst-audio.workspace = true
gst-video.workspace = true
gst-check.workspace = true
m3u8-rs = "5.0"
anyhow = "1"
[build-dependencies]
gst-plugin-version-helper.workspace = true
[lib]
name = "gsthlsmultivariantsink"
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[features]
static = []
capi = []
doc = ["gst/v1_18"]
[package.metadata.capi]
min_version = "0.9.21"
[package.metadata.capi.header]
enabled = false
[package.metadata.capi.library]
install_subdir = "gstreamer-1.0"
versioning = false
import_library = false
[package.metadata.capi.pkg_config]
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"

View file

@ -0,0 +1,12 @@
// Copyright (C) 2024, Asymptotic Inc.
// Author: Sanchayan Maity <sanchayan@asymptotic.io>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
fn main() {
gst_plugin_version_helper::info()
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
// Copyright (C) 2024, Asymptotic Inc.
// Author: Sanchayan Maity <sanchayan@asymptotic.io>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
use gst::prelude::*;
mod imp;
#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstHlsMultivariantSinkMuxerType")]
#[non_exhaustive]
pub enum HlsMultivariantSinkMuxerType {
#[default]
#[enum_value(name = "cmaf", nick = "cmaf")]
Cmaf = 0,
#[enum_value(name = "mpegts", nick = "mpegts")]
MpegTs = 1,
}
#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstHlsMultivariantSinkAlternativeMediaType")]
#[non_exhaustive]
pub enum HlsMultivariantSinkAlternativeMediaType {
#[default]
#[enum_value(name = "AUDIO", nick = "audio")]
Audio = 0,
#[enum_value(name = "VIDEO", nick = "video")]
Video = 1,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstHlsMultivariantSinkPlaylistType")]
#[non_exhaustive]
pub enum HlsMultivariantSinkPlaylistType {
#[enum_value(
name = "Unspecified: The tag `#EXT-X-PLAYLIST-TYPE` won't be present in the playlist during the pipeline processing.",
nick = "unspecified"
)]
Unspecified = 0,
#[enum_value(
name = "Event: No segments will be removed from the playlist. At the end of the processing, the tag `#EXT-X-ENDLIST` is added to the playlist. The tag `#EXT-X-PLAYLIST-TYPE:EVENT` will be present in the playlist.",
nick = "event"
)]
Event = 1,
#[enum_value(
name = "Vod: The playlist behaves like the `event` option (a live event), but at the end of the processing, the playlist will be set to `#EXT-X-PLAYLIST-TYPE:VOD`.",
nick = "vod"
)]
Vod = 2,
}
glib::wrapper! {
pub struct HlsMultivariantSink(ObjectSubclass<imp::HlsMultivariantSink>) @extends gst::Bin, gst::Element, gst::Object, @implements gst::ChildProxy;
}
glib::wrapper! {
pub(crate) struct HlsMultivariantSinkPad(ObjectSubclass<imp::HlsMultivariantSinkPad>) @extends gst::GhostPad, gst::ProxyPad, gst::Pad, gst::Object;
}
pub fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
#[cfg(feature = "doc")]
{
HlsMultivariantSinkPad::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
HlsMultivariantSinkMuxerType::static_type()
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
HlsMultivariantSinkPlaylistType::static_type()
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
HlsMultivariantSinkAlternativeMediaType::static_type()
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
}
gst::Element::register(
Some(plugin),
"hlsmultivariantsink",
gst::Rank::NONE,
HlsMultivariantSink::static_type(),
)
}
gst::plugin_define!(
hlsmultivariantsink,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
"MPL",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_REPOSITORY"),
env!("BUILD_REL_DATE")
);

File diff suppressed because it is too large Load diff