#![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 std::ptr;
use ff_format::{AudioFrame, VideoFrame};
use ff_sys::{
AVCodecContext, AVPixelFormat_AV_PIX_FMT_YUV420P, av_opt_set, avformat_alloc_output_context2,
avformat_free_context, avformat_new_stream, avformat_write_header,
};
use crate::codec_utils::{ffmpeg_err, ffmpeg_err_msg};
use crate::error::StreamError;
use crate::muxer_core::MuxerCore;
pub(crate) struct LiveDashInner {
core: MuxerCore,
}
unsafe impl Send for LiveDashInner {}
impl LiveDashInner {
#[allow(clippy::too_many_arguments)]
pub(crate) fn open(
output_dir: &str,
segment_secs: u32,
enc_width: i32,
enc_height: i32,
fps_int: i32,
video_bitrate: u64,
audio: Option<(i32, i32, i64)>,
) -> Result<Self, StreamError> {
unsafe {
Self::open_unsafe(
output_dir,
segment_secs,
enc_width,
enc_height,
fps_int,
video_bitrate,
audio,
)
}
}
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,
enc_width: i32,
enc_height: i32,
fps_int: i32,
video_bitrate: u64,
audio: Option<(i32, i32, i64)>,
) -> Result<Self, StreamError> {
ff_sys::ensure_initialized();
let manifest_path = format!("{output_dir}/manifest.mpd");
let c_manifest = CString::new(manifest_path.as_str())
.map_err(|_| ffmpeg_err_msg("manifest path contains null byte"))?;
let mut out_ctx: *mut ff_sys::AVFormatContext = ptr::null_mut();
let ret = avformat_alloc_output_context2(
&mut out_ctx,
ptr::null_mut(),
c"dash".as_ptr(),
c_manifest.as_ptr(),
);
if ret < 0 || out_ctx.is_null() {
return Err(ffmpeg_err(ret));
}
let seg_time_str = format!("{segment_secs}");
if let Ok(c_seg_time) = CString::new(seg_time_str.as_str()) {
let ret = av_opt_set(
(*out_ctx).priv_data,
c"seg_duration".as_ptr(),
c_seg_time.as_ptr(),
0,
);
if ret < 0 {
log::warn!(
"live_dash seg_duration option not supported, using default \
requested={seg_time_str} error={}",
ff_sys::av_error_string(ret)
);
}
}
let ret = av_opt_set(
(*out_ctx).priv_data,
c"use_template".as_ptr(),
c"1".as_ptr(),
0,
);
if ret < 0 {
log::warn!(
"live_dash use_template option not supported error={}",
ff_sys::av_error_string(ret)
);
}
let ret = av_opt_set(
(*out_ctx).priv_data,
c"use_timeline".as_ptr(),
c"1".as_ptr(),
0,
);
if ret < 0 {
log::warn!(
"live_dash use_timeline option not supported error={}",
ff_sys::av_error_string(ret)
);
}
let ret = av_opt_set(
(*out_ctx).priv_data,
c"remove_at_exit".as_ptr(),
c"0".as_ptr(),
0,
);
if ret < 0 {
log::warn!(
"live_dash remove_at_exit option not supported error={}",
ff_sys::av_error_string(ret)
);
}
let vid_enc_codec =
crate::codec_utils::select_h264_encoder("live_dash").ok_or_else(|| {
avformat_free_context(out_ctx);
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::avcodec::alloc_context3(vid_enc_codec).map_err(|e| {
avformat_free_context(out_ctx);
ffmpeg_err(e)
})?;
(*vid_enc_ctx).width = enc_width;
(*vid_enc_ctx).height = enc_height;
(*vid_enc_ctx).pix_fmt = AVPixelFormat_AV_PIX_FMT_YUV420P;
(*vid_enc_ctx).time_base.num = 1;
(*vid_enc_ctx).time_base.den = fps_int;
(*vid_enc_ctx).framerate.num = fps_int;
(*vid_enc_ctx).framerate.den = 1;
(*vid_enc_ctx).gop_size = fps_int * segment_secs as i32;
(*vid_enc_ctx).bit_rate = video_bitrate as i64;
ff_sys::avcodec::open2(vid_enc_ctx, vid_enc_codec, ptr::null_mut()).map_err(|e| {
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
ffmpeg_err(e)
})?;
let vid_out_stream = avformat_new_stream(out_ctx, vid_enc_codec);
if vid_out_stream.is_null() {
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
return Err(ffmpeg_err_msg("cannot create video output stream"));
}
(*vid_out_stream).time_base = (*vid_enc_ctx).time_base;
let vid_out_stream_idx = ((*out_ctx).nb_streams - 1) as i32;
ff_sys::avcodec::parameters_from_context((*vid_out_stream).codecpar, vid_enc_ctx).map_err(
|e| {
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
ffmpeg_err(e)
},
)?;
let mut aud_enc_ctx: *mut AVCodecContext = ptr::null_mut();
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::open_aac_encoder(sr, nc, abr, "live_dash") {
Ok(ctx) => {
aud_enc_ctx = ctx;
aud_frame_size = if (*aud_enc_ctx).frame_size > 0 {
(*aud_enc_ctx).frame_size
} else {
1024
};
let aud_out_stream = avformat_new_stream(out_ctx, ptr::null());
if aud_out_stream.is_null() {
ff_sys::avcodec::free_context(&mut aud_enc_ctx as *mut *mut _);
log::warn!("live_dash cannot create audio output stream, skipping audio");
} else {
(*aud_out_stream).time_base.num = 1;
(*aud_out_stream).time_base.den = sr;
aud_out_stream_idx = ((*out_ctx).nb_streams - 1) as i32;
if ff_sys::avcodec::parameters_from_context(
(*aud_out_stream).codecpar,
aud_enc_ctx,
)
.is_err()
{
log::warn!("live_dash audio stream codecpar copy failed");
}
}
}
Err(e) => {
log::warn!("live_dash aac encoder unavailable: {e}, skipping audio");
}
}
}
let pb = ff_sys::avformat::open_output(
Path::new(&manifest_path),
ff_sys::avformat::avio_flags::WRITE,
)
.map_err(|e| {
if !aud_enc_ctx.is_null() {
ff_sys::avcodec::free_context(&mut aud_enc_ctx as *mut *mut _);
}
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
ffmpeg_err(e)
})?;
(*out_ctx).pb = pb;
let ret = avformat_write_header(out_ctx, ptr::null_mut());
if ret < 0 {
ff_sys::avformat::close_output(&mut (*out_ctx).pb);
if !aud_enc_ctx.is_null() {
ff_sys::avcodec::free_context(&mut aud_enc_ctx as *mut *mut _);
}
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
return Err(ffmpeg_err(ret));
}
ff_sys::avformat::close_output(&mut (*out_ctx).pb);
log::info!(
"live_dash output opened \
output_dir={output_dir} segment_duration={segment_secs}s \
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_dash",
false, )
.inspect_err(|_| {
if !aud_enc_ctx.is_null() {
ff_sys::avcodec::free_context(&mut aud_enc_ctx as *mut *mut _);
}
ff_sys::avcodec::free_context(&mut vid_enc_ctx as *mut *mut _);
avformat_free_context(out_ctx);
})?;
Ok(Self { core })
}
}