Skip to main content

alpha2_native/
lib.rs

1//! Native `FFmpeg` 8 media adapter.
2//!
3//! rsmpeg and `FFmpeg` types stay private; callers see only `alpha2-media` contracts. Timeline
4//! audio mixing needs the `timeline` feature, the only part that links the engine.
5
6// Diagnostics go through `alpha2_os::diagnostic!` so a closed stderr cannot panic the process.
7#![cfg_attr(not(test), warn(clippy::print_stderr))]
8
9mod audio_host;
10mod audio_input;
11pub use audio_input::AudioStreamConfig;
12mod audio_timeline;
13mod bitstream;
14mod capabilities;
15mod convert;
16mod decode;
17mod recovery;
18pub use recovery::{
19    ConcealedAudioGap, CorruptPacket, DECODE_WORK_LIMIT, DecodeDiagnostic, DecodeDiagnosticCounts,
20    DecodeDiagnostics, DecodePolicy, DecodeProgressWatch, DroppedAudioOverlap, VideoRecoveryGap,
21    watch_decode_progress,
22};
23mod encode;
24mod mux;
25mod partial;
26pub use alpha2_os::PublishMethod;
27pub use partial::{
28    ExistingOutput, OutputTarget, PARTIAL_OUTPUT_IN_USE, PublishReport,
29    remove_stale_partial_output, write_png, write_whole,
30};
31// Every decode reads the HEVC layers; only hardware decode reads the leading crop.
32#[cfg_attr(
33    not(all(
34        any(target_os = "linux", target_os = "macos", target_os = "windows"),
35        feature = "hwdecode"
36    )),
37    allow(dead_code)
38)]
39mod parameter_sets;
40mod passthrough;
41pub use passthrough::{
42    AudioBlockFeed, CopyAudio, PacketCopy, PacketCopyAudio, PacketCopyPlan, PacketCopyPlanner,
43    PacketCopyReport, PacketCopyRequest, check_yuv_transcode, copy_packets, plan_packet_copy,
44    same_chroma_siting,
45};
46#[cfg(all(target_os = "linux", feature = "hwdecode"))]
47mod hwdecode;
48#[cfg(feature = "hwencode")]
49mod hwencode;
50mod openh264;
51pub use openh264::{
52    OPENH264_ENCODER_NAME, OPENH264_INSTALL_COMMAND, OPENH264_NOTICE, OPENH264_VERSION,
53    OpenH264Library, OpenH264Provider, OpenH264Status, openh264_status,
54};
55mod rate_control;
56pub use rate_control::{AppliedRateControl, RateControlMechanism};
57#[cfg(test)]
58mod tests;
59mod tools;
60pub use tools::{
61    FrameScore, THUMBNAIL_LUMA_RANGE, choose_thumbnail, downscale_frame, resize_rgba, score_frame,
62};
63mod video_backend;
64mod video_encode;
65mod video_frame;
66mod vt_sei;
67pub use alpha2_ffmpeg_bridge::Yuv420pFrameBuffer;
68pub use alpha2_media::{EncoderRefusal, OpenFailureKind};
69#[cfg(feature = "cpal-host")]
70pub use audio_host::{CpalHostError, CpalOutputHost, RealtimeAudioSource};
71pub use audio_host::{
72    DecodedAudioSources, HostAudioConfig, HostSampleFormat, OfflineAudioError,
73    negotiate_output_config, render_offline,
74};
75#[cfg(feature = "timeline")]
76pub use audio_host::{ProjectAudioRequest, render_project_audio};
77pub use capabilities::{
78    AvEncodeConfig, EncoderInventory, NativeAudioCodec, NativeAudioEncoderCapability,
79    NativeCodecCapabilities, NativeEncoderClass, NativeEncoderHardwareConfig, NativeEncoderLibrary,
80    NativeEncoderRejection, NativeHardwareDecodeCapabilities, NativeHardwareDecodeCapability,
81    NativeRateControlProbe, NativeVideoCodec, NativeVideoEncoderCapability,
82    NativeVideoEncoderFallback, NativeVideoEncoderProbe, NativeVideoEncoderSelection,
83    audio_encoder_capabilities, codec_capabilities, encoder_minimum_dimensions, encodes_pixels,
84    h264_encoder_remedy, hardware_decode_backend, hardware_decode_capabilities,
85    hybrid_encoders_use_hardware, no_video_encoder_message, probe_video_encoders,
86    video_encoder_inventory, video_encoders_without_probing,
87};
88pub use convert::packed_frame;
89pub use convert::{
90    CodecThreadAllocation, CodecThreadRequest, HARDWARE_DECODER_THREADS, codec_thread_allocation,
91    codec_thread_budget, codec_thread_override, configured_codec_thread_override,
92};
93pub use decode::{
94    DecodeStageTimings, DroppedLayer, NativeAudioDecoder, NativeDecoder, ProbeResult,
95    SOURCE_SAMPLE_RATES, alpha_dropped_reason, decodes_vp9_alpha, probe,
96    software_decode_unsupported, visit_packet_times,
97};
98pub use encode::{
99    EncodeCallWatch, EncodeStageTimings, NativeAvEncodeSink, NativeWavEncodeSink,
100    watch_encode_calls,
101};
102#[cfg(all(target_os = "linux", feature = "hwdecode"))]
103pub use hwdecode::{VulkanDecodeProbe, probe_vulkan_decode};
104#[cfg(all(target_os = "windows", feature = "hwencode"))]
105pub use hwencode::{
106    D3D12EncodeProbe, D3D12RateControlOpen, D3D12RateControlRunner, D3D12TestEncodeRunner,
107    D3D12VA_TEST_ENCODE_SIZE, d3d12va_encoder, open_d3d12va_rate_control, probe_d3d12va_encode,
108    set_d3d12va_rate_control_runner, set_d3d12va_test_encode_runner,
109};
110#[cfg(feature = "hwencode")]
111pub use hwencode::{
112    EncoderInputHandoff, EncoderInputLayout, EncoderPoolStats, EncoderSurface,
113    EncoderSurfaceDescriptor, HardwareEncodeBackend, HardwareEncodeCapability,
114    HardwareEncodeDevice, HardwareEncodeInput, HardwareEncodeOpenError, HardwareEncodeRequest,
115    HardwareEncodeSession, InputFrame,
116};
117#[cfg(all(target_os = "macos", feature = "hwencode"))]
118pub use hwencode::{VideoToolboxEncodeProbe, probe_videotoolbox_encode};
119#[cfg(all(target_os = "linux", feature = "hwencode"))]
120pub use hwencode::{VulkanEncodeProbe, probe_vulkan_encode};
121pub use video_encode::{NativeVideoEncodeSink, VideoDisplay, VideoEncodeConfig};
122pub use video_frame::{
123    HardwareDecodeFallback, HardwareFrameLease, HardwareOpenError, NativeExportDecoder,
124    NativePlaneLayout, NativeVideoFrame, NativeVideoFrameMetadata,
125};
126
127/// `FFmpeg` runtime and pinned SDK identity used by native diagnostics.
128#[derive(Clone, Debug, Eq, PartialEq)]
129pub struct FfmpegRuntimeIdentity {
130    /// Version reported by the loaded `FFmpeg` shared library.
131    pub version: String,
132    /// Configure flags reported by the loaded libavcodec.
133    pub configuration: String,
134    /// SDK release used by alpha2 release builds.
135    pub pinned_sdk_release: &'static str,
136}
137
138impl FfmpegRuntimeIdentity {
139    /// Why the loaded runtime is not the pinned SDK, or `None` when it is.
140    ///
141    /// Cargo links whichever `FFmpeg` `pkg-config` finds, and a system build offers different
142    /// encoders than the release. Besides the version, the configuration must match the SDK's
143    /// `manifest.toml`: no GPL, nonfree or version 3 code, exactly the required external
144    /// `--enable-lib*` codecs, and `--disable-autodetect`.
145    #[must_use]
146    pub fn pinned_sdk_mismatch(&self) -> Option<String> {
147        let mut reasons = Vec::new();
148        if self.version != PINNED_SDK_SOURCE_TAG {
149            reasons.push(format!(
150                "version {} is not {PINNED_SDK_SOURCE_TAG}",
151                self.version
152            ));
153        }
154        let flags = || self.configuration.split_whitespace();
155        for flag in flags().filter(|flag| {
156            ["--enable-gpl", "--enable-nonfree", "--enable-version3"].contains(flag)
157                || (flag.starts_with("--enable-lib")
158                    && !PINNED_SDK_EXTERNAL_LIBRARIES.contains(flag))
159        }) {
160            reasons.push(format!("configured with {flag}"));
161        }
162        for library in PINNED_SDK_EXTERNAL_LIBRARIES {
163            if !flags().any(|flag| flag == *library) {
164                reasons.push(format!("not configured with {library}"));
165            }
166        }
167        if !flags().any(|flag| flag == "--disable-autodetect") {
168            reasons.push("not configured with --disable-autodetect".to_owned());
169        }
170        (!reasons.is_empty()).then(|| reasons.join(", "))
171    }
172}
173
174/// Panics unless the loaded `FFmpeg` is the pinned SDK, for tests to call before they use it.
175///
176/// A test linked against a system `FFmpeg` exercises different encoders than the release.
177/// `ALPHA2_ALLOW_UNPINNED_FFMPEG=1` runs them anyway. The runtime is checked once per process.
178///
179/// # Panics
180/// When the runtime is not the pinned SDK and the variable is not `1`.
181pub fn require_pinned_ffmpeg_for_tests() {
182    static MISMATCH: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
183    let mismatch = MISMATCH.get_or_init(|| {
184        let identity = ffmpeg_runtime_identity();
185        let reason = identity.pinned_sdk_mismatch()?;
186        if std::env::var_os("ALPHA2_ALLOW_UNPINNED_FFMPEG").is_some_and(|value| value == "1") {
187            alpha2_os::diagnostic!(
188                "alpha2: testing against FFmpeg {} although it is not the pinned SDK ({reason}): \
189                 ALPHA2_ALLOW_UNPINNED_FFMPEG=1",
190                identity.version
191            );
192            return None;
193        }
194        Some(format!(
195            "these tests loaded FFmpeg {}, not the pinned SDK {}: {reason}. The build \
196             linked whatever FFmpeg pkg-config found. Rebuild with \
197             FFMPEG_PKG_CONFIG_PATH=\"$(scripts/fetch-ffmpeg-sdk.sh)/lib/pkgconfig\" \
198             (on Windows, FFMPEG_INCLUDE_DIR and FFMPEG_LIBS_DIR). \
199             ALPHA2_ALLOW_UNPINNED_FFMPEG=1 runs them against this FFmpeg anyway.",
200            identity.version, identity.pinned_sdk_release,
201        ))
202    });
203    if let Some(message) = mismatch {
204        panic!("{message}");
205    }
206}
207
208/// `source_tag` of `release/ffmpeg-lgpl/manifest.toml`, as `av_version_info` reports it.
209const PINNED_SDK_SOURCE_TAG: &str = "n8.1.3";
210
211/// The `--enable-lib*` flags in `required_configure` of `release/ffmpeg-lgpl/manifest.toml`.
212const PINNED_SDK_EXTERNAL_LIBRARIES: &[&str] = &[
213    "--enable-libdav1d",
214    "--enable-libopus",
215    "--enable-libsvtav1",
216    "--enable-libvpx",
217];
218
219/// Returns the loaded `FFmpeg` identity together with the release SDK pin.
220#[must_use]
221pub fn ffmpeg_runtime_identity() -> FfmpegRuntimeIdentity {
222    FfmpegRuntimeIdentity {
223        version: alpha2_ffmpeg_bridge::runtime_version(),
224        configuration: alpha2_ffmpeg_bridge::runtime_configuration(),
225        pinned_sdk_release: "ffmpeg-8.1.3-shared-build4",
226    }
227}