#![allow(unsafe_code)]
#![allow(clippy::ptr_as_ptr)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::borrow_as_ptr)]
#![allow(clippy::ref_as_ptr)]
#![allow(clippy::too_many_lines)]
use std::ffi::CString;
use std::path::Path;
use ff_format::{AudioFrame, VideoFrame};
use ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P;
use crate::codec_utils_inner::{ffmpeg_err, ffmpeg_err_msg};
use crate::error::StreamError;
use crate::muxer_inner::MuxerCore;
pub(crate) struct LiveHlsInner {
core: MuxerCore,
}
unsafe impl Send for LiveHlsInner {}
impl LiveHlsInner {
#[allow(clippy::too_many_arguments)]
pub(crate) fn open(
output_dir: &str,
segment_secs: u32,
playlist_size: u32,
enc_width: i32,
enc_height: i32,
fps_int: i32,
video_bitrate: u64,
audio: Option<(i32, i32, i64)>,
segment_format: crate::hls::HlsSegmentFormat,
) -> Result<Self, StreamError> {
unsafe {
Self::open_unsafe(
output_dir,
segment_secs,
playlist_size,
enc_width,
enc_height,
fps_int,
video_bitrate,
audio,
segment_format,
)
}
}
pub(crate) fn push_video(&mut self, frame: &VideoFrame) -> Result<(), StreamError> {
unsafe { self.core.push_video_unsafe(frame) }
}
pub(crate) fn push_audio(&mut self, frame: &AudioFrame) {
unsafe {
self.core.push_audio_unsafe(frame);
}
}
pub(crate) fn flush_and_close(mut self) {
unsafe {
self.core.flush_and_close_unsafe();
}
}
#[allow(unsafe_op_in_unsafe_fn)]
#[allow(clippy::too_many_arguments)]
unsafe fn open_unsafe(
output_dir: &str,
segment_secs: u32,
playlist_size: u32,
enc_width: i32,
enc_height: i32,
fps_int: i32,
video_bitrate: u64,
audio: Option<(i32, i32, i64)>,
segment_format: crate::hls::HlsSegmentFormat,
) -> Result<Self, StreamError> {
ff_sys::ensure_initialized();
let playlist_path = format!("{output_dir}/index.m3u8");
let mut out_ctx = ff_sys::OutputFormatContext::new(Some("hls"), Path::new(&playlist_path))
.map_err(|e| ffmpeg_err(e.code()))?;
let seg_time_str = format!("{segment_secs}");
let list_size_str = format!("{playlist_size}");
let use_fmp4 = segment_format == crate::hls::HlsSegmentFormat::Fmp4;
let seg_ext = if use_fmp4 { "m4s" } else { "ts" };
let seg_filename = format!("{output_dir}/segment%03d.{seg_ext}");
if let (Ok(c_seg_time), Ok(c_list_size), Ok(c_seg_file)) = (
CString::new(seg_time_str.as_str()),
CString::new(list_size_str.as_str()),
CString::new(seg_filename.as_str()),
) {
if let Err(e) = out_ctx.set_opt(c"hls_time", &c_seg_time) {
log::warn!(
"live_hls hls_time option not supported, using default \
requested={seg_time_str} error={}",
ff_sys::av_error_string(e.code())
);
}
if let Err(e) = out_ctx.set_opt(c"hls_list_size", &c_list_size) {
log::warn!(
"live_hls hls_list_size option not supported, using default \
requested={list_size_str} error={}",
ff_sys::av_error_string(e.code())
);
}
if let Err(e) = out_ctx.set_opt(c"hls_flags", c"delete_segments") {
log::warn!(
"live_hls hls_flags option not supported error={}",
ff_sys::av_error_string(e.code())
);
}
if let Err(e) = out_ctx.set_opt(c"hls_segment_filename", &c_seg_file) {
log::warn!(
"live_hls hls_segment_filename option not supported, using default \
requested={seg_filename} error={}",
ff_sys::av_error_string(e.code())
);
}
if use_fmp4 && let Err(e) = out_ctx.set_opt(c"hls_segment_type", c"fmp4") {
log::warn!(
"live_hls hls_segment_type fmp4 option not supported error={}",
ff_sys::av_error_string(e.code())
);
}
}
let vid_enc_codec =
crate::codec_utils_inner::select_h264_encoder("live_hls").ok_or_else(|| {
ffmpeg_err_msg(
"no H.264 encoder available \
(tried h264_nvenc, h264_qsv, h264_amf, h264_videotoolbox, libx264, mpeg4)",
)
})?;
let mut vid_enc_ctx =
ff_sys::CodecContext::new(Some(vid_enc_codec)).map_err(|e| ffmpeg_err(e.code()))?;
vid_enc_ctx.set_width(enc_width);
vid_enc_ctx.set_height(enc_height);
vid_enc_ctx.set_pix_fmt(AVPixelFormat_AV_PIX_FMT_YUV420P);
vid_enc_ctx.set_time_base(ff_sys::AVRational {
num: 1,
den: fps_int,
});
vid_enc_ctx.set_framerate(ff_sys::AVRational {
num: fps_int,
den: 1,
});
vid_enc_ctx.set_gop_size(fps_int * segment_secs as i32);
vid_enc_ctx.set_bit_rate(video_bitrate as i64);
vid_enc_ctx
.open_codec(vid_enc_codec)
.map_err(|e| ffmpeg_err(e.code()))?;
let vid_out_stream_idx = out_ctx
.new_stream(Some(&vid_enc_codec))
.map_err(|e| ffmpeg_err(e.code()))? as i32;
out_ctx.set_stream_time_base(vid_out_stream_idx as usize, vid_enc_ctx.time_base());
out_ctx
.apply_stream_params_from_context(vid_out_stream_idx as usize, &vid_enc_ctx)
.map_err(|e| ffmpeg_err(e.code()))?;
let mut aud_enc_ctx: Option<ff_sys::CodecContext> = None;
let mut aud_out_stream_idx: i32 = -1;
let mut aud_sample_rate = 44100i32;
let mut aud_frame_size = 1024i32;
if let Some((sr, nc, abr)) = audio {
aud_sample_rate = sr;
match crate::codec_utils_inner::open_aac_encoder(sr, nc, abr, "live_hls") {
Ok(ctx) => {
aud_frame_size = if ctx.frame_size() > 0 {
ctx.frame_size()
} else {
1024
};
match out_ctx.new_stream(None) {
Ok(idx) => {
let idx = idx as i32;
aud_out_stream_idx = idx;
out_ctx.set_stream_time_base(
idx as usize,
ff_sys::AVRational { num: 1, den: sr },
);
if out_ctx
.apply_stream_params_from_context(idx as usize, &ctx)
.is_err()
{
log::warn!("live_hls audio stream codecpar copy failed");
}
aud_enc_ctx = Some(ctx);
}
Err(_) => {
log::warn!(
"live_hls cannot create audio output stream, skipping audio"
);
}
}
}
Err(e) => {
log::warn!("live_hls aac encoder unavailable: {e}, skipping audio");
}
}
}
out_ctx
.open_io(Path::new(&playlist_path))
.map_err(|e| ffmpeg_err(e.code()))?;
out_ctx.write_header().map_err(|e| ffmpeg_err(e.code()))?;
out_ctx.close_io();
log::info!(
"live_hls output opened \
output_dir={output_dir} segment_duration={segment_secs}s \
playlist_size={playlist_size} width={enc_width} height={enc_height} \
fps={fps_int} audio={}",
aud_out_stream_idx >= 0
);
let core = MuxerCore::new(
out_ctx,
vid_enc_ctx,
aud_enc_ctx,
vid_out_stream_idx,
aud_out_stream_idx,
fps_int,
enc_width,
enc_height,
aud_frame_size,
aud_sample_rate,
"live_hls",
)?;
Ok(Self { core })
}
}