Skip to content

Commit

Permalink
make things a bit nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
Frando committed Feb 12, 2024
1 parent de6f804 commit ff3d30c
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 150 deletions.
48 changes: 48 additions & 0 deletions iroh-livestream/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions iroh-livestream/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ anyhow = "1.0.79"
clap = { version = "4.5.0", features = ["derive"] }
# ffmpeg-sidecar = "0.5.1"
futures = "0.3.30"
iroh-base = "0.12.0"
iroh-net = "0.12.0"
moq-pub = { git = "https://github.com/Frando/moq-rs.git", branch = "iroh" }
moq-sub = { git = "https://github.com/Frando/moq-rs.git", branch = "iroh" }
moq-transport = { git = "https://github.com/Frando/moq-rs.git", branch = "iroh" }
postcard = "1.0.8"
quinn = "0.10.2"
serde = { version = "1.0.196", features = ["derive"] }
thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["full"] }
tracing = "0.1.40"
Expand Down
176 changes: 111 additions & 65 deletions iroh-livestream/src/ffmpeg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,79 @@ use std::process::Stdio;
use anyhow::{bail, Context, Result};
use tokio::{
io::{AsyncRead, AsyncWrite},
process::Command,
process::{Child, Command},
};
use tracing::{info, warn};

// pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
// use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
// if !ffmpeg_is_installed() {
// tracing::info!("FFmpeg not found, downloading...");
// tokio::task::spawn_blocking(|| {
// ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
// })
// .await??;
// }
// let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
// println!("FFmpeg version: {}", version);
// Ok(())
// }
pub fn capture_stdin() -> Result<impl AsyncRead + Send + Unpin + 'static> {
let input = ["-i", "pipe:"];
capture_ffmpeg(input.to_vec())
}

pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
// let bin = ffmpeg_path();
let bin = "ffmpeg";
pub fn capture_camera() -> Result<impl AsyncRead + Send + Unpin + 'static> {
let input = match std::env::consts::OS {
// TODO: this is same as desktop, find out if we can find the correct device
"macos" => vec!["-f", "avfoundation", "-i", "default:default", "-r", "30"],
"linux" => vec![
"-f",
"pulse",
"-ac",
"2",
"-i",
"default",
"-f",
"v4l2",
"-i",
"/dev/video0",
"-r",
"30",
],
"windows" => {
// TODO: find out how windows dshow args work
// likely have to get device name from `ffmpeg -list_devices`
bail!("windows is not yet supported");
}
_ => bail!("Unsupported OS".to_string()),
};
capture_ffmpeg(input)
}

pub fn capture_desktop() -> Result<impl AsyncRead + Send + Unpin + 'static> {
let input = match std::env::consts::OS {
// TODO: this is same as camera, find out if we can find the correct device
"macos" => vec!["-f", "avfoundation", "-i", "default:default", "-r", "30"],
"linux" => vec![
"-f",
"pulse",
"-ac",
"2",
"-i",
"default",
"-framerate",
"30",
"-f",
"x11grab",
"-i",
":0.0",
],
"windows" => {
// TODO: find out how windows dshow args work
// likely have to get device name from `ffmpeg -list_devices`
bail!("windows is not yet supported");
vec!["-f", "dshow", "-i", "video='screen-capture-recorder'"]
}
_ => bail!("Unsupported OS".to_string()),
};
// TODO: Find out if this actually helps, found it on the internets..
let reduce_latency = [
"-max_delay",
"0",
"-analyzeduration",
"0",
"-flags",
"+low_delay",
"-fflags",
"+nobuffer",
];
let encode = [
capture_ffmpeg(input)
}

pub fn capture_ffmpeg(input: Vec<&'static str>) -> Result<impl AsyncRead + Send + Unpin + 'static> {
let bin = match std::env::consts::OS {
"windows" => "ffmpeg.exe",
_ => "ffmpeg",
};
let encode_video = [
"-vcodec",
"libx264",
"-preset",
"ultrafast",
"fast",
"-tune",
"zerolatency",
];
Expand All @@ -77,16 +90,11 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
"-",
];
let mut args = vec!["-hide_banner", "-v", "quiet"];
args.extend_from_slice(&reduce_latency);
args.extend_from_slice(&input);
args.extend_from_slice(&encode);
args.extend_from_slice(&encode_video);
args.extend_from_slice(&output);

info!(
"spawning ffmpeg: {} {}",
bin,
args.join(" ")
);
info!("spawn: {} {}", bin, args.join(" "));
let mut cmd = Command::new(bin);
cmd.args(args);
cmd.stdout(Stdio::piped());
Expand All @@ -95,38 +103,76 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
.stdout
.take()
.context("failed to capture FFmpeg stdout")?;
// Ensure the child process is spawned in the runtime so it can
// make progress on its own while we await for any output.
tokio::spawn(async move {
let status = child.wait().await;
match status {
Ok(status) => info!("FFmpeg exited with status {status}"),
Err(err) => warn!("FFmpeg exited with error {err}"),
}
});
tokio::spawn(wait_and_log(bin, child));
Ok(stdout)
}

pub fn subscribe() -> Result<impl AsyncWrite + Send + Unpin + 'static> {
let bin = "ffplay";
let args = ["-"];
pub fn out_ffplay() -> Result<impl AsyncWrite + Send + Unpin + 'static> {
let bin = match std::env::consts::OS {
"windows" => "ffplay.exe",
_ => "ffplay",
};
// TODO: Find out if this actually helps, found it on the internets..
let args = [
"-nostats",
"-sync",
"ext",
"-max_delay",
"0",
"-analyzeduration",
"0",
"-flags",
"+low_delay",
"-fflags",
"+nobuffer+fastseek+flush_packets",
"-",
];

info!("spawn: {} {}", bin, args.join(" "));
let mut cmd = Command::new(bin);
cmd.args(args);
cmd.stdin(Stdio::piped());
let mut child = cmd.spawn()?;
let stdin = child
.stdin
.take()
.context("failed to capture FFmpeg stdout")?;
// Ensure the child process is spawned in the runtime so it can
// make progress on its own while we await for any output.
tokio::spawn(async move {
let status = child.wait().await;
match status {
Ok(status) => info!("ffplay exited with status {status}"),
Err(err) => warn!("ffplay exited with error {err}"),
}
});
let stdin = child.stdin.take().context("failed to capture stdin")?;
tokio::spawn(wait_and_log(bin, child));
Ok(stdin)
}

pub fn out_mpv() -> Result<impl AsyncWrite + Send + Unpin + 'static> {
let bin = match std::env::consts::OS {
"windows" => "mpv.exe",
_ => "mpv",
};
let args = ["--profile=low-latency", "--no-cache", "--untimed", "-"];

info!("spawn: {} {}", bin, args.join(" "));
let mut cmd = Command::new(bin);
cmd.args(args);
cmd.stdin(Stdio::piped());
let mut child = cmd.spawn()?;
let stdin = child.stdin.take().context("failed to capture stdin")?;
tokio::spawn(wait_and_log(bin, child));
Ok(stdin)
}

async fn wait_and_log(name: &str, mut child: Child) {
let status = child.wait().await;
match status {
Ok(status) => info!("{name} exited with status {status}"),
Err(err) => warn!("{name} exited with error {err}"),
}
}

// pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
// use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
// if !ffmpeg_is_installed() {
// tracing::info!("FFmpeg not found, downloading...");
// tokio::task::spawn_blocking(|| {
// ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
// })
// .await??;
// }
// let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
// println!("FFmpeg version: {}", version);
// Ok(())
// }
Loading

0 comments on commit ff3d30c

Please sign in to comment.