Initial implementation of Wayland AirPlay desktop sharing
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
use spielab::{
|
||||
media::{VideoCapture, VideoEncoder},
|
||||
settings::{VideoConfig, VideoQuality},
|
||||
};
|
||||
use std::{process::Stdio, time::Duration};
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
|
||||
/// Exercises the actual encoder, Annex-B reader and an independent H.264 decoder.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires FFmpeg with libx264 installed"]
|
||||
async fn encoded_access_units_decode_to_three_video_frames() {
|
||||
decode_frames(VideoConfig {
|
||||
width: 320,
|
||||
height: 240,
|
||||
..VideoQuality::Efficient.config()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires FFmpeg with libx264 installed"]
|
||||
async fn every_quality_preset_encodes_the_requested_resolution_and_frame_rate() {
|
||||
for quality in VideoQuality::ALL {
|
||||
decode_frames(quality.config()).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn decode_frames(config: VideoConfig) {
|
||||
decode_with_encoder(config, &VideoEncoder::Software, 3).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires a working VA-API H.264 encoder on /dev/dri/renderD128"]
|
||||
async fn hardware_frames_decode_at_every_quality_and_auto_detection_selects_gpu() {
|
||||
let encoder = VideoEncoder::Vaapi("/dev/dri/renderD128".into());
|
||||
for quality in VideoQuality::ALL {
|
||||
encoder.probe(quality.config()).await.unwrap();
|
||||
decode_with_encoder(
|
||||
quality.config(),
|
||||
&encoder,
|
||||
quality.config().fps as usize + 2,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
assert!(matches!(
|
||||
VideoEncoder::detect(VideoQuality::High.config()).await,
|
||||
VideoEncoder::Vaapi(_)
|
||||
));
|
||||
}
|
||||
|
||||
async fn decode_with_encoder(config: VideoConfig, encoder: &VideoEncoder, count: usize) {
|
||||
let mut capture = VideoCapture::start_with_encoder(None, config, encoder).unwrap();
|
||||
let mut stream = vec![];
|
||||
for index in 0..count {
|
||||
let nals = tokio::time::timeout(Duration::from_secs(5), capture.frame())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
if index == 0 {
|
||||
assert!(nals.iter().any(|n| n[0] & 31 == 7));
|
||||
assert!(nals.iter().any(|n| n[0] & 31 == 8));
|
||||
}
|
||||
for nal in nals {
|
||||
stream.extend_from_slice(&[0, 0, 0, 1]);
|
||||
stream.extend_from_slice(&nal);
|
||||
}
|
||||
}
|
||||
drop(capture);
|
||||
let mut decoder = Command::new("ffmpeg")
|
||||
.args([
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-f",
|
||||
"h264",
|
||||
"-i",
|
||||
"pipe:0",
|
||||
"-f",
|
||||
"framehash",
|
||||
"pipe:1",
|
||||
])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
decoder
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(&stream)
|
||||
.await
|
||||
.unwrap();
|
||||
let output = tokio::time::timeout(Duration::from_secs(5), decoder.wait_with_output())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let decoded = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(
|
||||
decoded.contains(&format!(
|
||||
"#dimensions 0: {}x{}",
|
||||
config.width, config.height
|
||||
)),
|
||||
"{decoded}"
|
||||
);
|
||||
assert!(
|
||||
decoded.contains(&format!("#tb 0: 1/{}", config.fps)),
|
||||
"{decoded}"
|
||||
);
|
||||
assert_eq!(
|
||||
decoded
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with('#') && !line.is_empty())
|
||||
.count(),
|
||||
count
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user