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 { 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) }) }