mirror of
https://github.com/v0l/zap-stream-core.git
synced 2025-06-20 21:52:18 +00:00
refactor: split hls module
This commit is contained in:
139
crates/core/src/mux/hls/mod.rs
Normal file
139
crates/core/src/mux/hls/mod.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use crate::egress::EgressResult;
|
||||||
|
use crate::mux::hls::variant::HlsVariant;
|
||||||
|
use crate::variant::{StreamMapping, VariantStream};
|
||||||
|
use anyhow::Result;
|
||||||
|
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVPacket;
|
||||||
|
use ffmpeg_rs_raw::Encoder;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::trace;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
mod segment;
|
||||||
|
mod variant;
|
||||||
|
|
||||||
|
pub enum HlsVariantStream {
|
||||||
|
Video {
|
||||||
|
group: usize,
|
||||||
|
index: usize,
|
||||||
|
id: Uuid,
|
||||||
|
},
|
||||||
|
Audio {
|
||||||
|
group: usize,
|
||||||
|
index: usize,
|
||||||
|
id: Uuid,
|
||||||
|
},
|
||||||
|
Subtitle {
|
||||||
|
group: usize,
|
||||||
|
index: usize,
|
||||||
|
id: Uuid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlsVariantStream {
|
||||||
|
pub fn id(&self) -> &Uuid {
|
||||||
|
match self {
|
||||||
|
HlsVariantStream::Video { id, .. } => id,
|
||||||
|
HlsVariantStream::Audio { id, .. } => id,
|
||||||
|
HlsVariantStream::Subtitle { id, .. } => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> &usize {
|
||||||
|
match self {
|
||||||
|
HlsVariantStream::Video { index, .. } => index,
|
||||||
|
HlsVariantStream::Audio { index, .. } => index,
|
||||||
|
HlsVariantStream::Subtitle { index, .. } => index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HlsVariantStream {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
HlsVariantStream::Video { index, .. } => write!(f, "v:{}", index),
|
||||||
|
HlsVariantStream::Audio { index, .. } => write!(f, "a:{}", index),
|
||||||
|
HlsVariantStream::Subtitle { index, .. } => write!(f, "s:{}", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum SegmentType {
|
||||||
|
MPEGTS,
|
||||||
|
FMP4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HlsMuxer {
|
||||||
|
pub out_dir: PathBuf,
|
||||||
|
pub variants: Vec<HlsVariant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlsMuxer {
|
||||||
|
pub fn new<'a>(
|
||||||
|
id: &Uuid,
|
||||||
|
out_dir: &str,
|
||||||
|
encoders: impl Iterator<Item = (&'a VariantStream, &'a Encoder)>,
|
||||||
|
segment_type: SegmentType,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let base = PathBuf::from(out_dir).join(id.to_string());
|
||||||
|
|
||||||
|
if !base.exists() {
|
||||||
|
std::fs::create_dir_all(&base)?;
|
||||||
|
}
|
||||||
|
let mut vars = Vec::new();
|
||||||
|
for (k, group) in &encoders
|
||||||
|
.sorted_by(|a, b| a.0.group_id().cmp(&b.0.group_id()))
|
||||||
|
.chunk_by(|a| a.0.group_id())
|
||||||
|
{
|
||||||
|
let var = HlsVariant::new(base.to_str().unwrap(), k, group, segment_type)?;
|
||||||
|
vars.push(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = Self {
|
||||||
|
out_dir: base,
|
||||||
|
variants: vars,
|
||||||
|
};
|
||||||
|
ret.write_master_playlist()?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_master_playlist(&self) -> Result<()> {
|
||||||
|
let mut pl = m3u8_rs::MasterPlaylist::default();
|
||||||
|
pl.version = Some(3);
|
||||||
|
pl.variants = self
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.to_playlist_variant())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut f_out = File::create(self.out_dir.join("live.m3u8"))?;
|
||||||
|
pl.write_to(&mut f_out)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mux an encoded packet from [Encoder]
|
||||||
|
pub unsafe fn mux_packet(
|
||||||
|
&mut self,
|
||||||
|
pkt: *mut AVPacket,
|
||||||
|
variant: &Uuid,
|
||||||
|
) -> Result<EgressResult> {
|
||||||
|
for var in self.variants.iter_mut() {
|
||||||
|
if let Some(vs) = var.streams.iter().find(|s| s.id() == variant) {
|
||||||
|
// very important for muxer to know which stream this pkt belongs to
|
||||||
|
(*pkt).stream_index = *vs.index() as _;
|
||||||
|
return var.process_packet(pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This HLS muxer doesn't handle this variant, return None instead of failing
|
||||||
|
// This can happen when multiple egress handlers are configured with different variant sets
|
||||||
|
trace!(
|
||||||
|
"HLS muxer received packet for variant {} which it doesn't handle",
|
||||||
|
variant
|
||||||
|
);
|
||||||
|
Ok(EgressResult::None)
|
||||||
|
}
|
||||||
|
}
|
75
crates/core/src/mux/hls/segment.rs
Normal file
75
crates/core/src/mux/hls/segment.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use crate::mux::hls::variant::HlsVariant;
|
||||||
|
use crate::mux::SegmentType;
|
||||||
|
use m3u8_rs::{ByteRange, MediaSegment, MediaSegmentType, Part};
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum HlsSegment {
|
||||||
|
Full(SegmentInfo),
|
||||||
|
Partial(PartialSegmentInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlsSegment {
|
||||||
|
pub fn to_media_segment(&self) -> MediaSegmentType {
|
||||||
|
match self {
|
||||||
|
HlsSegment::Full(f) => f.to_media_segment(),
|
||||||
|
HlsSegment::Partial(p) => p.to_media_segment(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct SegmentInfo {
|
||||||
|
pub index: u64,
|
||||||
|
pub duration: f32,
|
||||||
|
pub kind: SegmentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentInfo {
|
||||||
|
pub fn to_media_segment(&self) -> MediaSegmentType {
|
||||||
|
MediaSegmentType::Full(MediaSegment {
|
||||||
|
uri: self.filename(),
|
||||||
|
duration: self.duration,
|
||||||
|
..MediaSegment::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filename(&self) -> String {
|
||||||
|
HlsVariant::segment_name(self.kind, self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct PartialSegmentInfo {
|
||||||
|
pub index: u64,
|
||||||
|
pub parent_index: u64,
|
||||||
|
pub parent_kind: SegmentType,
|
||||||
|
pub duration: f64,
|
||||||
|
pub independent: bool,
|
||||||
|
pub byte_range: Option<(u64, Option<u64>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialSegmentInfo {
|
||||||
|
pub fn to_media_segment(&self) -> MediaSegmentType {
|
||||||
|
MediaSegmentType::Partial(Part {
|
||||||
|
uri: self.filename(),
|
||||||
|
duration: self.duration,
|
||||||
|
independent: self.independent,
|
||||||
|
gap: false,
|
||||||
|
byte_range: self.byte_range.map(|r| ByteRange {
|
||||||
|
length: r.0,
|
||||||
|
offset: r.1,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filename(&self) -> String {
|
||||||
|
HlsVariant::segment_name(self.parent_kind, self.parent_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Byte offset where this partial segment ends
|
||||||
|
pub fn end_pos(&self) -> Option<u64> {
|
||||||
|
self.byte_range
|
||||||
|
.as_ref()
|
||||||
|
.map(|(len, start)| start.unwrap_or(0) + len)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
use crate::egress::{EgressResult, EgressSegment};
|
use crate::egress::{EgressResult, EgressSegment};
|
||||||
|
use crate::mux::hls::segment::{HlsSegment, PartialSegmentInfo, SegmentInfo};
|
||||||
|
use crate::mux::{HlsVariantStream, SegmentType};
|
||||||
use crate::variant::{StreamMapping, VariantStream};
|
use crate::variant::{StreamMapping, VariantStream};
|
||||||
use anyhow::{bail, ensure, Result};
|
use anyhow::{bail, ensure, Result};
|
||||||
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVCodecID::AV_CODEC_ID_H264;
|
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVCodecID::AV_CODEC_ID_H264;
|
||||||
@ -8,67 +10,12 @@ use ffmpeg_rs_raw::ffmpeg_sys_the_third::{
|
|||||||
AVIO_FLAG_WRITE, AV_NOPTS_VALUE, AV_PKT_FLAG_KEY,
|
AVIO_FLAG_WRITE, AV_NOPTS_VALUE, AV_PKT_FLAG_KEY,
|
||||||
};
|
};
|
||||||
use ffmpeg_rs_raw::{cstr, Encoder, Muxer};
|
use ffmpeg_rs_raw::{cstr, Encoder, Muxer};
|
||||||
use itertools::Itertools;
|
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use m3u8_rs::{ByteRange, ExtTag, MediaSegment, MediaSegmentType, Part, PartInf, PreloadHint};
|
use m3u8_rs::{ExtTag, MediaSegmentType, PartInf, PreloadHint};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
pub enum SegmentType {
|
|
||||||
MPEGTS,
|
|
||||||
FMP4,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum HlsVariantStream {
|
|
||||||
Video {
|
|
||||||
group: usize,
|
|
||||||
index: usize,
|
|
||||||
id: Uuid,
|
|
||||||
},
|
|
||||||
Audio {
|
|
||||||
group: usize,
|
|
||||||
index: usize,
|
|
||||||
id: Uuid,
|
|
||||||
},
|
|
||||||
Subtitle {
|
|
||||||
group: usize,
|
|
||||||
index: usize,
|
|
||||||
id: Uuid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HlsVariantStream {
|
|
||||||
pub fn id(&self) -> &Uuid {
|
|
||||||
match self {
|
|
||||||
HlsVariantStream::Video { id, .. } => id,
|
|
||||||
HlsVariantStream::Audio { id, .. } => id,
|
|
||||||
HlsVariantStream::Subtitle { id, .. } => id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn index(&self) -> &usize {
|
|
||||||
match self {
|
|
||||||
HlsVariantStream::Video { index, .. } => index,
|
|
||||||
HlsVariantStream::Audio { index, .. } => index,
|
|
||||||
HlsVariantStream::Subtitle { index, .. } => index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for HlsVariantStream {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
HlsVariantStream::Video { index, .. } => write!(f, "v:{}", index),
|
|
||||||
HlsVariantStream::Audio { index, .. } => write!(f, "a:{}", index),
|
|
||||||
HlsVariantStream::Subtitle { index, .. } => write!(f, "s:{}", index),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HlsVariant {
|
pub struct HlsVariant {
|
||||||
/// Name of this variant (720p)
|
/// Name of this variant (720p)
|
||||||
@ -76,9 +23,9 @@ pub struct HlsVariant {
|
|||||||
/// MPEG-TS muxer for this variant
|
/// MPEG-TS muxer for this variant
|
||||||
mux: Muxer,
|
mux: Muxer,
|
||||||
/// List of streams ids in this variant
|
/// List of streams ids in this variant
|
||||||
streams: Vec<HlsVariantStream>,
|
pub(crate) streams: Vec<HlsVariantStream>,
|
||||||
/// Segment length in seconds
|
/// Segment length in seconds
|
||||||
segment_length: f32,
|
segment_length_target: f32,
|
||||||
/// Total number of seconds of video to store
|
/// Total number of seconds of video to store
|
||||||
segment_window: f32,
|
segment_window: f32,
|
||||||
/// Current segment index
|
/// Current segment index
|
||||||
@ -109,82 +56,7 @@ pub struct HlsVariant {
|
|||||||
init_segment_path: Option<String>,
|
init_segment_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum HlsSegment {
|
|
||||||
Full(SegmentInfo),
|
|
||||||
Partial(PartialSegmentInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HlsSegment {
|
|
||||||
fn to_media_segment(&self) -> MediaSegmentType {
|
|
||||||
match self {
|
|
||||||
HlsSegment::Full(f) => f.to_media_segment(),
|
|
||||||
HlsSegment::Partial(p) => p.to_media_segment(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct SegmentInfo {
|
|
||||||
index: u64,
|
|
||||||
duration: f32,
|
|
||||||
kind: SegmentType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SegmentInfo {
|
|
||||||
fn to_media_segment(&self) -> MediaSegmentType {
|
|
||||||
MediaSegmentType::Full(MediaSegment {
|
|
||||||
uri: self.filename(),
|
|
||||||
duration: self.duration,
|
|
||||||
..MediaSegment::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filename(&self) -> String {
|
|
||||||
HlsVariant::segment_name(self.kind, self.index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct PartialSegmentInfo {
|
|
||||||
index: u64,
|
|
||||||
parent_index: u64,
|
|
||||||
parent_kind: SegmentType,
|
|
||||||
duration: f64,
|
|
||||||
independent: bool,
|
|
||||||
byte_range: Option<(u64, Option<u64>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialSegmentInfo {
|
|
||||||
fn to_media_segment(&self) -> MediaSegmentType {
|
|
||||||
MediaSegmentType::Partial(Part {
|
|
||||||
uri: self.filename(),
|
|
||||||
duration: self.duration,
|
|
||||||
independent: self.independent,
|
|
||||||
gap: false,
|
|
||||||
byte_range: self.byte_range.map(|r| ByteRange {
|
|
||||||
length: r.0,
|
|
||||||
offset: r.1,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filename(&self) -> String {
|
|
||||||
HlsVariant::segment_name(self.parent_kind, self.parent_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Byte offset where this partial segment ends
|
|
||||||
fn end_pos(&self) -> Option<u64> {
|
|
||||||
self.byte_range
|
|
||||||
.as_ref()
|
|
||||||
.map(|(len, start)| start.unwrap_or(0) + len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HlsVariant {
|
impl HlsVariant {
|
||||||
const LOW_LATENCY: bool = false;
|
|
||||||
const LL_PARTS: usize = 3;
|
|
||||||
|
|
||||||
pub fn new<'a>(
|
pub fn new<'a>(
|
||||||
out_dir: &'a str,
|
out_dir: &'a str,
|
||||||
group: usize,
|
group: usize,
|
||||||
@ -209,7 +81,7 @@ impl HlsVariant {
|
|||||||
let mut streams = Vec::new();
|
let mut streams = Vec::new();
|
||||||
let mut ref_stream_index = -1;
|
let mut ref_stream_index = -1;
|
||||||
let mut has_video = false;
|
let mut has_video = false;
|
||||||
let mut seg_size = 2.0;
|
let mut segment_length = 1.0;
|
||||||
|
|
||||||
for (var, enc) in encoded_vars {
|
for (var, enc) in encoded_vars {
|
||||||
match var {
|
match var {
|
||||||
@ -224,9 +96,9 @@ impl HlsVariant {
|
|||||||
has_video = true;
|
has_video = true;
|
||||||
// Always use video stream as reference for segmentation
|
// Always use video stream as reference for segmentation
|
||||||
ref_stream_index = stream_idx as _;
|
ref_stream_index = stream_idx as _;
|
||||||
let v_seg = v.keyframe_interval as f32 / v.fps;
|
let sg = v.keyframe_interval as f32 / v.fps;
|
||||||
if v_seg > seg_size {
|
if sg > segment_length {
|
||||||
seg_size = v_seg;
|
segment_length = sg;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
VariantStream::Audio(a) => unsafe {
|
VariantStream::Audio(a) => unsafe {
|
||||||
@ -262,12 +134,6 @@ impl HlsVariant {
|
|||||||
ref_stream_index
|
ref_stream_index
|
||||||
);
|
);
|
||||||
|
|
||||||
let min_segment_length = if Self::LOW_LATENCY {
|
|
||||||
(seg_size * 3.0).max(6.0) // make segments 3x longer in LL mode or minimum 6s
|
|
||||||
} else {
|
|
||||||
2.0
|
|
||||||
};
|
|
||||||
let segment_length = seg_size.max(min_segment_length);
|
|
||||||
let mut opts = HashMap::new();
|
let mut opts = HashMap::new();
|
||||||
if let SegmentType::FMP4 = segment_type {
|
if let SegmentType::FMP4 = segment_type {
|
||||||
//opts.insert("fflags".to_string(), "-autobsf".to_string());
|
//opts.insert("fflags".to_string(), "-autobsf".to_string());
|
||||||
@ -276,8 +142,6 @@ impl HlsVariant {
|
|||||||
"+frag_custom+dash+delay_moov".to_string(),
|
"+frag_custom+dash+delay_moov".to_string(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let mut partial_seg_size = segment_length / Self::LL_PARTS as f32;
|
|
||||||
partial_seg_size -= partial_seg_size % seg_size; // align to keyframe
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
mux.open(Some(opts))?;
|
mux.open(Some(opts))?;
|
||||||
@ -286,7 +150,6 @@ impl HlsVariant {
|
|||||||
|
|
||||||
let mut variant = Self {
|
let mut variant = Self {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
segment_length,
|
|
||||||
segment_window: 30.0,
|
segment_window: 30.0,
|
||||||
mux,
|
mux,
|
||||||
streams,
|
streams,
|
||||||
@ -294,14 +157,15 @@ impl HlsVariant {
|
|||||||
segments: Vec::new(),
|
segments: Vec::new(),
|
||||||
out_dir: out_dir.to_string(),
|
out_dir: out_dir.to_string(),
|
||||||
segment_type,
|
segment_type,
|
||||||
packets_written: 0,
|
|
||||||
ref_stream_index,
|
|
||||||
partial_target_duration: partial_seg_size,
|
|
||||||
current_partial_index: 0,
|
|
||||||
current_segment_start: 0.0,
|
current_segment_start: 0.0,
|
||||||
current_partial_start: 0.0,
|
current_partial_start: 0.0,
|
||||||
|
packets_written: 0,
|
||||||
|
ref_stream_index,
|
||||||
|
low_latency: false,
|
||||||
|
partial_target_duration: 0.0,
|
||||||
|
current_partial_index: 0,
|
||||||
next_partial_independent: false,
|
next_partial_independent: false,
|
||||||
low_latency: Self::LOW_LATENCY,
|
segment_length_target: segment_length,
|
||||||
init_segment_path: None,
|
init_segment_path: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -315,6 +179,21 @@ impl HlsVariant {
|
|||||||
Ok(variant)
|
Ok(variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn segment_length(&self) -> f32 {
|
||||||
|
let min_segment_length = if self.low_latency {
|
||||||
|
(self.segment_length_target * 3.0).max(6.0) // make segments 3x longer in LL mode or minimum 6s
|
||||||
|
} else {
|
||||||
|
2.0
|
||||||
|
};
|
||||||
|
self.segment_length_target.max(min_segment_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn partial_segment_length(&self) -> f32 {
|
||||||
|
let seg_size = self.segment_length();
|
||||||
|
let partial_seg_size = seg_size / 3.0; // 3 segments min
|
||||||
|
partial_seg_size - partial_seg_size % seg_size
|
||||||
|
}
|
||||||
|
|
||||||
pub fn segment_name(t: SegmentType, idx: u64) -> String {
|
pub fn segment_name(t: SegmentType, idx: u64) -> String {
|
||||||
match t {
|
match t {
|
||||||
SegmentType::MPEGTS => format!("{}.ts", idx),
|
SegmentType::MPEGTS => format!("{}.ts", idx),
|
||||||
@ -335,7 +214,7 @@ impl HlsVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process a single packet through the muxer
|
/// Process a single packet through the muxer
|
||||||
unsafe fn process_packet(&mut self, pkt: *mut AVPacket) -> Result<EgressResult> {
|
pub(crate) unsafe fn process_packet(&mut self, pkt: *mut AVPacket) -> Result<EgressResult> {
|
||||||
let pkt_stream = *(*self.mux.context())
|
let pkt_stream = *(*self.mux.context())
|
||||||
.streams
|
.streams
|
||||||
.add((*pkt).stream_index as usize);
|
.add((*pkt).stream_index as usize);
|
||||||
@ -358,7 +237,7 @@ impl HlsVariant {
|
|||||||
let cur_part_duration = pkt_pts - self.current_partial_start;
|
let cur_part_duration = pkt_pts - self.current_partial_start;
|
||||||
|
|
||||||
// check if current packet is keyframe, flush current segment
|
// check if current packet is keyframe, flush current segment
|
||||||
if can_split && cur_duration >= self.segment_length as f64 {
|
if can_split && cur_duration >= self.segment_length() as f64 {
|
||||||
result = self.split_next_seg(pkt_pts)?;
|
result = self.split_next_seg(pkt_pts)?;
|
||||||
} else if self.low_latency && cur_part_duration >= self.partial_target_duration as f64 {
|
} else if self.low_latency && cur_part_duration >= self.partial_target_duration as f64 {
|
||||||
result = self.create_partial_segment(pkt_pts)?;
|
result = self.create_partial_segment(pkt_pts)?;
|
||||||
@ -666,9 +545,9 @@ impl HlsVariant {
|
|||||||
|
|
||||||
pl.version = Some(self.playlist_version());
|
pl.version = Some(self.playlist_version());
|
||||||
pl.target_duration = if self.playlist_version() >= 6 {
|
pl.target_duration = if self.playlist_version() >= 6 {
|
||||||
self.segment_length.round() as _
|
self.segment_length().round() as _
|
||||||
} else {
|
} else {
|
||||||
self.segment_length
|
self.segment_length()
|
||||||
};
|
};
|
||||||
if self.low_latency {
|
if self.low_latency {
|
||||||
pl.part_inf = Some(PartInf {
|
pl.part_inf = Some(PartInf {
|
||||||
@ -760,75 +639,3 @@ impl HlsVariant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HlsMuxer {
|
|
||||||
pub out_dir: PathBuf,
|
|
||||||
pub variants: Vec<HlsVariant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HlsMuxer {
|
|
||||||
pub fn new<'a>(
|
|
||||||
id: &Uuid,
|
|
||||||
out_dir: &str,
|
|
||||||
encoders: impl Iterator<Item = (&'a VariantStream, &'a Encoder)>,
|
|
||||||
segment_type: SegmentType,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let base = PathBuf::from(out_dir).join(id.to_string());
|
|
||||||
|
|
||||||
if !base.exists() {
|
|
||||||
std::fs::create_dir_all(&base)?;
|
|
||||||
}
|
|
||||||
let mut vars = Vec::new();
|
|
||||||
for (k, group) in &encoders
|
|
||||||
.sorted_by(|a, b| a.0.group_id().cmp(&b.0.group_id()))
|
|
||||||
.chunk_by(|a| a.0.group_id())
|
|
||||||
{
|
|
||||||
let var = HlsVariant::new(base.to_str().unwrap(), k, group, segment_type)?;
|
|
||||||
vars.push(var);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = Self {
|
|
||||||
out_dir: base,
|
|
||||||
variants: vars,
|
|
||||||
};
|
|
||||||
ret.write_master_playlist()?;
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_master_playlist(&self) -> Result<()> {
|
|
||||||
let mut pl = m3u8_rs::MasterPlaylist::default();
|
|
||||||
pl.version = Some(3);
|
|
||||||
pl.variants = self
|
|
||||||
.variants
|
|
||||||
.iter()
|
|
||||||
.map(|v| v.to_playlist_variant())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut f_out = File::create(self.out_dir.join("live.m3u8"))?;
|
|
||||||
pl.write_to(&mut f_out)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mux an encoded packet from [Encoder]
|
|
||||||
pub unsafe fn mux_packet(
|
|
||||||
&mut self,
|
|
||||||
pkt: *mut AVPacket,
|
|
||||||
variant: &Uuid,
|
|
||||||
) -> Result<EgressResult> {
|
|
||||||
for var in self.variants.iter_mut() {
|
|
||||||
if let Some(vs) = var.streams.iter().find(|s| s.id() == variant) {
|
|
||||||
// very important for muxer to know which stream this pkt belongs to
|
|
||||||
(*pkt).stream_index = *vs.index() as _;
|
|
||||||
return var.process_packet(pkt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This HLS muxer doesn't handle this variant, return None instead of failing
|
|
||||||
// This can happen when multiple egress handlers are configured with different variant sets
|
|
||||||
trace!(
|
|
||||||
"HLS muxer received packet for variant {} which it doesn't handle",
|
|
||||||
variant
|
|
||||||
);
|
|
||||||
Ok(EgressResult::None)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1,3 @@
|
|||||||
mod hls;
|
mod hls;
|
||||||
|
|
||||||
pub use hls::*;
|
pub use hls::*;
|
||||||
|
Reference in New Issue
Block a user