use crate::elements::VideoCodec;
pub(crate) fn try_test_video() -> Option<String> {
const SECONDS: f64 = 8.0;
const AUDIO_RATE: u32 = 44_100;
static SYNTHESIZED: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
SYNTHESIZED
.get_or_init(|| {
match build_fixture(
"test-video",
SECONDS,
AUDIO_RATE,
VideoCodec::OpenH264,
None,
) {
Ok(fixture) => {
eprintln!("using a synthesized fixture: {}", fixture.path.display());
Some(fixture.path.to_string_lossy().into_owned())
}
Err(error) => {
eprintln!("skipping: could not synthesize a test video: {error}");
None
}
}
})
.clone()
}
#[cfg(all(target_os = "windows", feature = "d3d11"))]
pub(crate) fn try_d3d11_device() -> Option<(
windows::Win32::Graphics::Direct3D11::ID3D11Device,
std::sync::Arc<std::sync::Mutex<windows::Win32::Graphics::Direct3D11::ID3D11DeviceContext>>,
)> {
use windows::Win32::Graphics::{
Direct3D::D3D_DRIVER_TYPE_HARDWARE,
Direct3D11::{D3D11_SDK_VERSION, D3D11CreateDevice},
};
let mut device = None;
let mut context = None;
let result = unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
Default::default(),
Default::default(),
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
};
if let Err(error) = result {
eprintln!("skipping: D3D11CreateDevice failed on this machine: {error}");
return None;
}
Some((
device.expect("D3D11CreateDevice succeeded without producing a device"),
std::sync::Arc::new(std::sync::Mutex::new(
context.expect("D3D11CreateDevice succeeded without producing a context"),
)),
))
}
#[cfg(all(target_os = "windows", feature = "d3d11"))]
pub(crate) fn try_single_threaded_d3d11_device()
-> Option<windows::Win32::Graphics::Direct3D11::ID3D11Device> {
use windows::Win32::Graphics::{
Direct3D::D3D_DRIVER_TYPE_HARDWARE,
Direct3D11::{
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_CREATE_DEVICE_FLAG,
D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_SDK_VERSION, D3D11CreateDevice,
},
};
let mut device = None;
let mut context = None;
let result = unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
Default::default(),
D3D11_CREATE_DEVICE_FLAG(
D3D11_CREATE_DEVICE_BGRA_SUPPORT.0 | D3D11_CREATE_DEVICE_SINGLETHREADED.0,
),
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
};
if let Err(error) = result {
eprintln!("skipping: D3D11CreateDevice failed on this machine: {error}");
return None;
}
device
}
#[cfg(all(target_os = "windows", feature = "d3d12"))]
pub(crate) fn try_d3d12_device() -> Option<windows::Win32::Graphics::Direct3D12::ID3D12Device> {
use windows::Win32::Graphics::{
Direct3D::D3D_FEATURE_LEVEL_11_0, Direct3D12::D3D12CreateDevice,
};
let mut device = None;
if let Err(error) = unsafe { D3D12CreateDevice(None, D3D_FEATURE_LEVEL_11_0, &mut device) } {
eprintln!("skipping: D3D12CreateDevice failed on this machine: {error}");
return None;
}
device
}
#[cfg(feature = "cuda")]
pub(crate) fn try_cuda_device() -> Option<(
crate::elements::CudaDevice,
std::sync::MutexGuard<'static, ()>,
)> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let guard = LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
match crate::elements::CudaDevice::new() {
Ok(device) => Some((device, guard)),
Err(error) => {
eprintln!("skipping: no usable CUDA device on this machine ({error})");
None
}
}
}
pub(crate) fn synthesize(name: &str, seconds: f64, audio_rate: u32) -> Fixture {
build_fixture(name, seconds, audio_rate, VideoCodec::OpenH264, None)
.expect("synthesize a fixture")
}
pub(crate) fn synthesize_reordered(name: &str, seconds: f64) -> Fixture {
build_fixture(name, seconds, 44_100, VideoCodec::Mpeg4, Some(2))
.expect("synthesize a reordered fixture")
}
pub(crate) struct Fixture {
pub(crate) path: std::path::PathBuf,
pub(crate) audio_rate: u32,
pub(crate) channels: u16,
}
const FIXTURE_WIDTH: u32 = 320;
const FIXTURE_HEIGHT: u32 = 240;
const FIXTURE_FPS: i32 = 30;
fn build_fixture(
name: &str,
seconds: f64,
audio_rate: u32,
codec: VideoCodec,
max_b_frames: Option<u32>,
) -> std::result::Result<Fixture, Box<dyn std::error::Error>> {
use crate::elements::{
AudioCodec, FileMuxer, SwAudioEncoder, SwAudioEncoderOptions, SwEncoder, SwEncoderOptions,
SwScaler, TestAudioOptions, TestAudioSource, TestVideoOptions, TestVideoSource,
};
use crate::pipeline::PipelineBuilder;
use ffmpeg_next as ffmpeg;
crate::init()?;
let directory = std::env::temp_dir().join("media-pp-fixtures");
std::fs::create_dir_all(&directory)?;
let path = directory.join(format!("{name}.mp4"));
let _ = std::fs::remove_file(&path);
let channels = 2;
let video = TestVideoSource::new(
format!("{name}-video"),
TestVideoOptions {
width: FIXTURE_WIDTH,
height: FIXTURE_HEIGHT,
framerate: ffmpeg::Rational::new(FIXTURE_FPS, 1),
},
);
let audio = TestAudioSource::new(
format!("{name}-audio"),
TestAudioOptions {
sample_rate: audio_rate,
channels,
frequency: 440.0,
},
);
let audio_time_base = audio.time_base();
let video_time_base = ffmpeg::Rational::new(1, FIXTURE_FPS);
let video_encoder = SwEncoder::new(
format!("{name}-video-encoder"),
SwEncoderOptions {
codec,
width: FIXTURE_WIDTH,
height: FIXTURE_HEIGHT,
time_base: video_time_base,
frame_rate: ffmpeg::Rational::new(FIXTURE_FPS, 1),
bit_rate: 1_000_000,
gop_size: 40,
max_b_frames,
},
)?;
let audio_encoder = SwAudioEncoder::new(
format!("{name}-audio-encoder"),
SwAudioEncoderOptions {
codec: AudioCodec::Aac,
sample_rate: audio_rate,
channels,
time_base: audio_time_base,
bit_rate: 128_000,
},
)?;
let mut muxer = FileMuxer::create(&path)?;
muxer.add_stream("video", video_encoder.parameters(), video_time_base)?;
muxer.add_stream(
"audio",
audio_encoder.parameters(),
audio_encoder.time_base(),
)?;
let mut sinks = muxer.open()?;
let audio_sink = sinks.pop().expect("the audio stream was added second");
let video_sink = sinks.pop().expect("the video stream was added first");
let scaler = SwScaler::new(
format!("{name}-to-yuv"),
ffmpeg::format::Pixel::YUV420P,
FIXTURE_WIDTH,
FIXTURE_HEIGHT,
ffmpeg::software::scaling::Flags::BILINEAR,
);
let pipeline = PipelineBuilder::new(format!("{name}-fixture"))
.add_source(video, move |source, context| {
let branch = context
.branch()
.pipe(scaler)
.pipe(video_encoder)
.to(video_sink)?;
context.attach(source, 0, branch)?;
Ok(())
})?
.add_source(audio, move |source, context| {
let branch = context.branch().pipe(audio_encoder).to(audio_sink)?;
context.attach(source, 0, branch)?;
Ok(())
})?
.build();
pipeline.run()?;
std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
pipeline.stop();
Ok(Fixture {
path,
audio_rate,
channels,
})
}