1#![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#[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#[derive(Clone, Debug, Eq, PartialEq)]
129pub struct FfmpegRuntimeIdentity {
130 pub version: String,
132 pub configuration: String,
134 pub pinned_sdk_release: &'static str,
136}
137
138impl FfmpegRuntimeIdentity {
139 #[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
174pub 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
208const PINNED_SDK_SOURCE_TAG: &str = "n8.1.3";
210
211const PINNED_SDK_EXTERNAL_LIBRARIES: &[&str] = &[
213 "--enable-libdav1d",
214 "--enable-libopus",
215 "--enable-libsvtav1",
216 "--enable-libvpx",
217];
218
219#[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}