Skip to main content

alpha2_native/
lib.rs

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