Files
wl-webrtc/src/avhw/hardware_muxer.rs
T
2026-07-03 10:57:05 +08:00

93 lines
3.1 KiB
Rust

use std::ffi::CString;
use std::path::Path;
use std::ptr;
use anyhow::{bail, Result};
use ffmpeg_next as ff;
use ffmpeg_next::ffi;
use super::ff_err;
pub(super) fn create_output_context(
output_path: &Path,
enc_video: &ff::codec::encoder::video::Video,
) -> Result<ff::format::context::Output> {
let output_cstr = CString::new(output_path.to_str().unwrap())?;
let mut fmt_ctx_ptr: *mut ffi::AVFormatContext = ptr::null_mut();
// SAFETY: avformat_alloc_output_context2 creates format context from
// the file extension. Does NOT open the file.
let ret = unsafe {
ffi::avformat_alloc_output_context2(
&mut fmt_ctx_ptr,
ptr::null_mut(),
ptr::null(),
output_cstr.as_ptr(),
)
};
if ret < 0 || fmt_ctx_ptr.is_null() {
bail!("Failed to allocate output format context: {}", ff_err(ret));
}
// SAFETY: enc_video is a valid AVCodecContext pointer; codec_id is a plain
// i32 enum discriminant read from it. fmt_ctx_ptr is a valid AVFormatContext
// allocated above; oformat is a const pointer field read from it.
// avformat_query_codec checks codec+format compatibility; both pointers are
// valid and FF_COMPLIANCE_NORMAL is a constant. All three reads happen in one
// block so a single SAFETY rationale covers them.
let compat = unsafe {
let codec_id = (*enc_video.as_ptr()).codec_id;
let oformat = (*fmt_ctx_ptr).oformat;
ffi::avformat_query_codec(oformat, codec_id, ffi::FF_COMPLIANCE_NORMAL)
};
if compat < 0 {
bail!("H.264 codec not supported by output container format");
}
// SAFETY: avformat_new_stream creates a new stream in the format context.
let stream_ptr = unsafe { ffi::avformat_new_stream(fmt_ctx_ptr, ptr::null()) };
if stream_ptr.is_null() {
bail!("Failed to create new stream in output context");
}
// SAFETY: avcodec_parameters_from_context copies encoder params + extradata.
let ret =
unsafe { ffi::avcodec_parameters_from_context((*stream_ptr).codecpar, enc_video.as_ptr()) };
if ret < 0 {
bail!(
"Failed to copy encoder parameters to stream: {}",
ff_err(ret)
);
}
// SAFETY: Copy encoder time_base to stream.
unsafe {
(*stream_ptr).time_base = (*enc_video.as_ptr()).time_base;
}
// SAFETY: avio_open opens the output file for writing.
let ret = unsafe {
ffi::avio_open(
&mut (*fmt_ctx_ptr).pb,
output_cstr.as_ptr(),
ffi::AVIO_FLAG_WRITE,
)
};
if ret < 0 {
bail!(
"Failed to open output file '{}': {}",
output_path.display(),
ff_err(ret)
);
}
// SAFETY: avformat_write_header writes the container header.
let ret = unsafe { ffi::avformat_write_header(fmt_ctx_ptr, ptr::null_mut()) };
if ret < 0 {
bail!("Failed to write output header: {}", ff_err(ret));
}
// SAFETY: We created fmt_ctx_ptr above and it's valid.
Ok(unsafe { ff::format::context::Output::wrap(fmt_ctx_ptr) })
}