hlssink3: Allow setting i-frame-only playlist.

HLS allows manifest where all segments are single ifames.
This manifest requires `EXT-X-I-FRAMES-ONLY` tag in the
manifest.
I-FRAMES-ONLY playlist segments are video only segments.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1070>
This commit is contained in:
rajneeshksoni 2023-01-31 21:34:04 +04:00 committed by GStreamer Marge Bot
parent 44405e0cd7
commit 0f383a6545
3 changed files with 68 additions and 8 deletions

View file

@ -2041,6 +2041,18 @@
} }
}, },
"properties": { "properties": {
"i-frames-only": {
"blurb": "Each video segments is single iframe, So put EXT-X-I-FRAMES-ONLY tag in the playlist",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "false",
"mutable": "null",
"readable": true,
"type": "gboolean",
"writable": true
},
"location": { "location": {
"blurb": "Location of the file to write", "blurb": "Location of the file to write",
"conditionally-available": false, "conditionally-available": false,

View file

@ -25,6 +25,7 @@ const DEFAULT_MAX_NUM_SEGMENT_FILES: u32 = 10;
const DEFAULT_TARGET_DURATION: u32 = 15; const DEFAULT_TARGET_DURATION: u32 = 15;
const DEFAULT_PLAYLIST_LENGTH: u32 = 5; const DEFAULT_PLAYLIST_LENGTH: u32 = 5;
const DEFAULT_PLAYLIST_TYPE: HlsSink3PlaylistType = HlsSink3PlaylistType::Unspecified; const DEFAULT_PLAYLIST_TYPE: HlsSink3PlaylistType = HlsSink3PlaylistType::Unspecified;
const DEFAULT_I_FRAMES_ONLY_PLAYLIST: bool = false;
const DEFAULT_SEND_KEYFRAME_REQUESTS: bool = true; const DEFAULT_SEND_KEYFRAME_REQUESTS: bool = true;
const SIGNAL_GET_PLAYLIST_STREAM: &str = "get-playlist-stream"; const SIGNAL_GET_PLAYLIST_STREAM: &str = "get-playlist-stream";
@ -66,6 +67,7 @@ struct Settings {
playlist_type: Option<MediaPlaylistType>, playlist_type: Option<MediaPlaylistType>,
max_num_segment_files: usize, max_num_segment_files: usize,
target_duration: u32, target_duration: u32,
i_frames_only: bool,
send_keyframe_requests: bool, send_keyframe_requests: bool,
splitmuxsink: gst::Element, splitmuxsink: gst::Element,
@ -94,6 +96,7 @@ impl Default for Settings {
max_num_segment_files: DEFAULT_MAX_NUM_SEGMENT_FILES as usize, max_num_segment_files: DEFAULT_MAX_NUM_SEGMENT_FILES as usize,
target_duration: DEFAULT_TARGET_DURATION, target_duration: DEFAULT_TARGET_DURATION,
send_keyframe_requests: DEFAULT_SEND_KEYFRAME_REQUESTS, send_keyframe_requests: DEFAULT_SEND_KEYFRAME_REQUESTS,
i_frames_only: DEFAULT_I_FRAMES_ONLY_PLAYLIST,
splitmuxsink, splitmuxsink,
giostreamsink, giostreamsink,
@ -111,9 +114,13 @@ pub(crate) struct StartedState {
} }
impl StartedState { impl StartedState {
fn new(target_duration: f32, playlist_type: Option<MediaPlaylistType>) -> Self { fn new(
target_duration: f32,
playlist_type: Option<MediaPlaylistType>,
i_frames_only: bool,
) -> Self {
Self { Self {
playlist: Playlist::new(target_duration, playlist_type), playlist: Playlist::new(target_duration, playlist_type, i_frames_only),
current_segment_location: None, current_segment_location: None,
fragment_opened_at: None, fragment_opened_at: None,
old_segment_locations: Vec::new(), old_segment_locations: Vec::new(),
@ -148,17 +155,22 @@ impl HlsSink3 {
fn start(&self) { fn start(&self) {
gst::info!(CAT, imp: self, "Starting"); gst::info!(CAT, imp: self, "Starting");
let (target_duration, playlist_type) = { let (target_duration, playlist_type, i_frames_only) = {
let settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap();
( (
settings.target_duration as f32, settings.target_duration as f32,
settings.playlist_type.clone(), settings.playlist_type.clone(),
settings.i_frames_only,
) )
}; };
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if let State::Stopped = *state { if let State::Stopped = *state {
*state = State::Started(StartedState::new(target_duration, playlist_type)); *state = State::Started(StartedState::new(
target_duration,
playlist_type,
i_frames_only,
));
} }
} }
@ -452,6 +464,11 @@ impl ObjectImpl for HlsSink3 {
.nick("Playlist Type") .nick("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.") .blurb("The type of the playlist to use. When VOD type is set, the playlist will be live until the pipeline ends execution.")
.build(), .build(),
glib::ParamSpecBoolean::builder("i-frames-only")
.nick("I-Frames only playlist")
.blurb("Each video segments is single iframe, So put EXT-X-I-FRAMES-ONLY tag in the playlist")
.default_value(DEFAULT_I_FRAMES_ONLY_PLAYLIST)
.build(),
glib::ParamSpecBoolean::builder("send-keyframe-requests") glib::ParamSpecBoolean::builder("send-keyframe-requests")
.nick("Send Keyframe Requests") .nick("Send Keyframe Requests")
.blurb("Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.") .blurb("Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.")
@ -509,6 +526,17 @@ impl ObjectImpl for HlsSink3 {
.expect("type checked upstream") .expect("type checked upstream")
.into(); .into();
} }
"i-frames-only" => {
settings.i_frames_only = value.get().expect("type checked upstream");
if settings.i_frames_only && settings.audio_sink {
gst::element_error!(
self.obj(),
gst::StreamError::WrongType,
("Invalid configuration"),
["Audio not allowed for i-frames-only-stream"]
);
}
}
"send-keyframe-requests" => { "send-keyframe-requests" => {
settings.send_keyframe_requests = value.get().expect("type checked upstream"); settings.send_keyframe_requests = value.get().expect("type checked upstream");
settings settings
@ -535,6 +563,7 @@ impl ObjectImpl for HlsSink3 {
let playlist_type: HlsSink3PlaylistType = settings.playlist_type.as_ref().into(); let playlist_type: HlsSink3PlaylistType = settings.playlist_type.as_ref().into();
playlist_type.to_value() playlist_type.to_value()
} }
"i-frames-only" => settings.i_frames_only.to_value(),
"send-keyframe-requests" => settings.send_keyframe_requests.to_value(), "send-keyframe-requests" => settings.send_keyframe_requests.to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
@ -767,6 +796,15 @@ impl ElementImpl for HlsSink3 {
); );
return None; return None;
} }
if settings.i_frames_only {
gst::element_error!(
self.obj(),
gst::StreamError::WrongType,
("Invalid configuration"),
["Audio not allowed for i-frames-only-stream"]
);
return None;
}
let peer_pad = settings.splitmuxsink.request_pad_simple("audio_0").unwrap(); let peer_pad = settings.splitmuxsink.request_pad_simple("audio_0").unwrap();
let sink_pad = let sink_pad =

View file

@ -11,7 +11,8 @@ use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::io::Write; use std::io::Write;
const GST_M3U8_PLAYLIST_VERSION: usize = 3; const GST_M3U8_PLAYLIST_V3: usize = 3;
const GST_M3U8_PLAYLIST_V4: usize = 4;
static SEGMENT_IDX_PATTERN: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"(%0(\d+)d)").unwrap()); static SEGMENT_IDX_PATTERN: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"(%0(\d+)d)").unwrap());
@ -29,7 +30,11 @@ pub struct Playlist {
} }
impl Playlist { impl Playlist {
pub fn new(target_duration: f32, playlist_type: Option<MediaPlaylistType>) -> Self { pub fn new(
target_duration: f32,
playlist_type: Option<MediaPlaylistType>,
i_frames_only: bool,
) -> Self {
let mut turn_vod = false; let mut turn_vod = false;
let playlist_type = if playlist_type == Some(MediaPlaylistType::Vod) { let playlist_type = if playlist_type == Some(MediaPlaylistType::Vod) {
turn_vod = true; turn_vod = true;
@ -37,16 +42,21 @@ impl Playlist {
} else { } else {
playlist_type playlist_type
}; };
let m3u8_version = if i_frames_only {
GST_M3U8_PLAYLIST_V4
} else {
GST_M3U8_PLAYLIST_V3
};
Self { Self {
inner: MediaPlaylist { inner: MediaPlaylist {
version: Some(GST_M3U8_PLAYLIST_VERSION), version: Some(m3u8_version),
target_duration, target_duration,
media_sequence: 0, media_sequence: 0,
segments: vec![], segments: vec![],
discontinuity_sequence: 0, discontinuity_sequence: 0,
end_list: false, end_list: false,
playlist_type, playlist_type,
i_frames_only: false, i_frames_only,
start: None, start: None,
independent_segments: false, independent_segments: false,
unknown_tags: vec![], unknown_tags: vec![],