stream video file
Some checks failed
Rust / build (pull_request) Failing after 1m51s

This commit is contained in:
Vinzenz Schroeter 2025-03-08 12:06:13 +01:00
parent 05289581a1
commit 80e0e905d0
6 changed files with 157 additions and 5 deletions

64
Cargo.lock generated
View file

@ -162,6 +162,24 @@ dependencies = [
"syn",
]
[[package]]
name = "bindgen"
version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [
"bitflags 2.9.0",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bit_field"
version = "0.10.2"
@ -633,6 +651,31 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "ffmpeg-next"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9"
dependencies = [
"bitflags 2.9.0",
"ffmpeg-sys-next",
"libc",
]
[[package]]
name = "ffmpeg-sys-next"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc3234d0a4b2f7d083699d0860c6c9dd83713908771b60f94a96f8704adfe45"
dependencies = [
"bindgen 0.70.1",
"cc",
"libc",
"num_cpus",
"pkg-config",
"vcpkg",
]
[[package]]
name = "flate2"
version = "1.1.1"
@ -842,6 +885,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "http"
version = "1.3.1"
@ -1056,7 +1105,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f"
dependencies = [
"bindgen",
"bindgen 0.69.5",
"cc",
"system-deps",
]
@ -1237,6 +1286,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
@ -1346,7 +1405,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112"
dependencies = [
"bindgen",
"bindgen 0.69.5",
"libspa-sys",
"system-deps",
]
@ -1751,6 +1810,7 @@ dependencies = [
"clap",
"env_logger",
"fast_image_resize",
"ffmpeg-next",
"image",
"log",
"scap",

View file

@ -20,6 +20,7 @@ scap = "0.0.8"
image = "0.25.5"
fast_image_resize = { version = "5.1", features = ["image"] }
tungstenite = "0.26"
ffmpeg-next = "7.1.0"
[dependencies.servicepoint]
package = "servicepoint"

View file

@ -90,8 +90,7 @@
{
default = pkgs.mkShell rec {
inputsFrom = [ self.packages.${system}.default ];
packages = [
pkgs.gdb
packages = with pkgs; [
(pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [
@ -103,7 +102,11 @@
cargo-expand
];
})
pkgs.cargo-flamegraph
cargo-flamegraph
gdb
ffmpeg-headless
];
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}";
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";

View file

@ -74,6 +74,16 @@ pub enum PixelCommand {
#[command(flatten)]
image_processing_options: ImageProcessingOptions,
},
#[command(
visible_alias = "v",
about = "Stream a video file (e.g. mp4) to the display."
)]
Video {
#[command(flatten)]
send_image_options: SendImageOptions,
#[command(flatten)]
image_processing_options: ImageProcessingOptions,
},
#[command(
visible_alias = "s",
about = "Stream the default screen capture source to the display. \

View file

@ -35,6 +35,7 @@ impl ImageProcessingPipeline {
}
}
#[must_use]
pub fn process(&mut self, frame: DynamicImage) -> Bitmap {
let start_time = Instant::now();

View file

@ -4,6 +4,8 @@ use crate::{
stream_window::stream_window,
transport::Transport,
};
use ffmpeg_next as ffmpeg;
use image::{DynamicImage, RgbImage};
use log::info;
use servicepoint::{
BinaryOperation, BitVecCommand, BitmapCommand, ClearCommand, CompressionCode, DisplayBitVec,
@ -23,6 +25,10 @@ pub(crate) fn pixels(connection: &Transport, pixel_command: PixelCommand) {
stream_options,
image_processing,
} => stream_window(connection, stream_options, image_processing),
PixelCommand::Video {
image_processing_options: processing_options,
send_image_options: image_options,
} => pixels_video(connection, image_options, processing_options),
}
}
@ -78,3 +84,74 @@ fn pixels_image(
.expect("failed to send image command");
info!("sent image to display");
}
fn pixels_video(
connection: &Transport,
options: SendImageOptions,
processing_options: ImageProcessingOptions,
) {
ffmpeg::init().unwrap();
let mut ictx = ffmpeg::format::input(&options.file_name).expect("failed to open video input file");
let input = ictx
.streams()
.best(ffmpeg::media::Type::Video)
.ok_or(ffmpeg::Error::StreamNotFound)
.expect("could not get video stream from input file");
let video_stream_index = input.index();
let context_decoder = ffmpeg::codec::context::Context::from_parameters(input.parameters())
.expect("could not extract video context from parameters");
let mut decoder = context_decoder.decoder().video()
.expect("failed to create decoder for video stream");
let src_width = decoder.width();
let src_height = decoder.height();
let mut scaler = ffmpeg::software::scaling::Context::get(
decoder.format(),
src_width,
src_height,
ffmpeg::format::Pixel::RGB24,
src_width,
src_height,
ffmpeg::software::scaling::Flags::BILINEAR,
).expect("failed to create scaling context");
let mut frame_index = 0;
let mut processing_pipeline = ImageProcessingPipeline::new(processing_options);
let mut receive_and_process_decoded_frames =
|decoder: &mut ffmpeg::decoder::Video| -> Result<(), ffmpeg::Error> {
let mut decoded = ffmpeg::util::frame::video::Video::empty();
let mut rgb_frame = ffmpeg::util::frame::video::Video::empty();
while decoder.receive_frame(&mut decoded).is_ok() {
scaler.run(&decoded, &mut rgb_frame)
.expect("failed to scale frame");
let image = RgbImage::from_raw(src_width, src_height, rgb_frame.data(0).to_owned())
.expect("could not read rgb data to image");
let image = DynamicImage::from(image);
let bitmap= processing_pipeline.process(image);
connection.send_command(BitmapCommand::from(bitmap))
.expect("failed to send image command");
frame_index += 1;
}
Ok(())
};
for (stream, packet) in ictx.packets() {
if stream.index() == video_stream_index {
decoder.send_packet(&packet)
.expect("failed to send video packet");
receive_and_process_decoded_frames(&mut decoder)
.expect("failed to process video packet");
}
}
decoder.send_eof().expect("failed to send eof");
receive_and_process_decoded_frames(&mut decoder)
.expect("failed to eof packet");
}