@@ -3,66 +3,79 @@ use std::process::Stdio;
3
3
use anyhow:: { bail, Context , Result } ;
4
4
use tokio:: {
5
5
io:: { AsyncRead , AsyncWrite } ,
6
- process:: Command ,
6
+ process:: { Child , Command } ,
7
7
} ;
8
8
use tracing:: { info, warn} ;
9
9
10
- // pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
11
- // use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
12
- // if !ffmpeg_is_installed() {
13
- // tracing::info!("FFmpeg not found, downloading...");
14
- // tokio::task::spawn_blocking(|| {
15
- // ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
16
- // })
17
- // .await??;
18
- // }
19
- // let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
20
- // println!("FFmpeg version: {}", version);
21
- // Ok(())
22
- // }
10
+ pub fn capture_stdin ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
11
+ let input = [ "-i" , "pipe:" ] ;
12
+ capture_ffmpeg ( input. to_vec ( ) )
13
+ }
23
14
24
- pub fn publish ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
25
- // let bin = ffmpeg_path();
26
- let bin = "ffmpeg" ;
15
+ pub fn capture_camera ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
27
16
let input = match std:: env:: consts:: OS {
17
+ // TODO: this is same as desktop, find out if we can find the correct device
28
18
"macos" => vec ! [ "-f" , "avfoundation" , "-i" , "default:default" , "-r" , "30" ] ,
29
19
"linux" => vec ! [
20
+ "-f" ,
21
+ "pulse" ,
22
+ "-ac" ,
23
+ "2" ,
24
+ "-i" ,
25
+ "default" ,
30
26
"-f" ,
31
27
"v4l2" ,
32
28
"-i" ,
33
29
"/dev/video0" ,
34
30
"-r" ,
35
31
"30" ,
32
+ ] ,
33
+ "windows" => {
34
+ // TODO: find out how windows dshow args work
35
+ // likely have to get device name from `ffmpeg -list_devices`
36
+ bail ! ( "windows is not yet supported" ) ;
37
+ }
38
+ _ => bail ! ( "Unsupported OS" . to_string( ) ) ,
39
+ } ;
40
+ capture_ffmpeg ( input)
41
+ }
42
+
43
+ pub fn capture_desktop ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
44
+ let input = match std:: env:: consts:: OS {
45
+ // TODO: this is same as camera, find out if we can find the correct device
46
+ "macos" => vec ! [ "-f" , "avfoundation" , "-i" , "default:default" , "-r" , "30" ] ,
47
+ "linux" => vec ! [
36
48
"-f" ,
37
49
"pulse" ,
38
50
"-ac" ,
39
51
"2" ,
40
52
"-i" ,
41
53
"default" ,
54
+ "-framerate" ,
55
+ "30" ,
56
+ "-f" ,
57
+ "x11grab" ,
58
+ "-i" ,
59
+ ":0.0" ,
42
60
] ,
43
61
"windows" => {
44
- // TODO: find out how windows dshow args work
45
- // likely have to get device name from `ffmpeg -list_devices`
46
- bail ! ( "windows is not yet supported" ) ;
62
+ vec ! [ "-f" , "dshow" , "-i" , "video='screen-capture-recorder'" ]
47
63
}
48
64
_ => bail ! ( "Unsupported OS" . to_string( ) ) ,
49
65
} ;
50
- // TODO: Find out if this actually helps, found it on the internets..
51
- let reduce_latency = [
52
- "-max_delay" ,
53
- "0" ,
54
- "-analyzeduration" ,
55
- "0" ,
56
- "-flags" ,
57
- "+low_delay" ,
58
- "-fflags" ,
59
- "+nobuffer" ,
60
- ] ;
61
- let encode = [
66
+ capture_ffmpeg ( input)
67
+ }
68
+
69
+ pub fn capture_ffmpeg ( input : Vec < & ' static str > ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
70
+ let bin = match std:: env:: consts:: OS {
71
+ "windows" => "ffmpeg.exe" ,
72
+ _ => "ffmpeg" ,
73
+ } ;
74
+ let encode_video = [
62
75
"-vcodec" ,
63
76
"libx264" ,
64
77
"-preset" ,
65
- "ultrafast " ,
78
+ "fast " ,
66
79
"-tune" ,
67
80
"zerolatency" ,
68
81
] ;
@@ -77,16 +90,11 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
77
90
"-" ,
78
91
] ;
79
92
let mut args = vec ! [ "-hide_banner" , "-v" , "quiet" ] ;
80
- args. extend_from_slice ( & reduce_latency) ;
81
93
args. extend_from_slice ( & input) ;
82
- args. extend_from_slice ( & encode ) ;
94
+ args. extend_from_slice ( & encode_video ) ;
83
95
args. extend_from_slice ( & output) ;
84
96
85
- info ! (
86
- "spawning ffmpeg: {} {}" ,
87
- bin,
88
- args. join( " " )
89
- ) ;
97
+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
90
98
let mut cmd = Command :: new ( bin) ;
91
99
cmd. args ( args) ;
92
100
cmd. stdout ( Stdio :: piped ( ) ) ;
@@ -95,38 +103,76 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
95
103
. stdout
96
104
. take ( )
97
105
. context ( "failed to capture FFmpeg stdout" ) ?;
98
- // Ensure the child process is spawned in the runtime so it can
99
- // make progress on its own while we await for any output.
100
- tokio:: spawn ( async move {
101
- let status = child. wait ( ) . await ;
102
- match status {
103
- Ok ( status) => info ! ( "FFmpeg exited with status {status}" ) ,
104
- Err ( err) => warn ! ( "FFmpeg exited with error {err}" ) ,
105
- }
106
- } ) ;
106
+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
107
107
Ok ( stdout)
108
108
}
109
109
110
- pub fn subscribe ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
111
- let bin = "ffplay" ;
112
- let args = [ "-" ] ;
110
+ pub fn out_ffplay ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
111
+ let bin = match std:: env:: consts:: OS {
112
+ "windows" => "ffplay.exe" ,
113
+ _ => "ffplay" ,
114
+ } ;
115
+ // TODO: Find out if this actually helps, found it on the internets..
116
+ let args = [
117
+ "-nostats" ,
118
+ "-sync" ,
119
+ "ext" ,
120
+ "-max_delay" ,
121
+ "0" ,
122
+ "-analyzeduration" ,
123
+ "0" ,
124
+ "-flags" ,
125
+ "+low_delay" ,
126
+ "-fflags" ,
127
+ "+nobuffer+fastseek+flush_packets" ,
128
+ "-" ,
129
+ ] ;
113
130
131
+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
114
132
let mut cmd = Command :: new ( bin) ;
115
133
cmd. args ( args) ;
116
134
cmd. stdin ( Stdio :: piped ( ) ) ;
117
135
let mut child = cmd. spawn ( ) ?;
118
- let stdin = child
119
- . stdin
120
- . take ( )
121
- . context ( "failed to capture FFmpeg stdout" ) ?;
122
- // Ensure the child process is spawned in the runtime so it can
123
- // make progress on its own while we await for any output.
124
- tokio:: spawn ( async move {
125
- let status = child. wait ( ) . await ;
126
- match status {
127
- Ok ( status) => info ! ( "ffplay exited with status {status}" ) ,
128
- Err ( err) => warn ! ( "ffplay exited with error {err}" ) ,
129
- }
130
- } ) ;
136
+ let stdin = child. stdin . take ( ) . context ( "failed to capture stdin" ) ?;
137
+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
131
138
Ok ( stdin)
132
139
}
140
+
141
+ pub fn out_mpv ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
142
+ let bin = match std:: env:: consts:: OS {
143
+ "windows" => "mpv.exe" ,
144
+ _ => "mpv" ,
145
+ } ;
146
+ let args = [ "--profile=low-latency" , "--no-cache" , "--untimed" , "-" ] ;
147
+
148
+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
149
+ let mut cmd = Command :: new ( bin) ;
150
+ cmd. args ( args) ;
151
+ cmd. stdin ( Stdio :: piped ( ) ) ;
152
+ let mut child = cmd. spawn ( ) ?;
153
+ let stdin = child. stdin . take ( ) . context ( "failed to capture stdin" ) ?;
154
+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
155
+ Ok ( stdin)
156
+ }
157
+
158
+ async fn wait_and_log ( name : & str , mut child : Child ) {
159
+ let status = child. wait ( ) . await ;
160
+ match status {
161
+ Ok ( status) => info ! ( "{name} exited with status {status}" ) ,
162
+ Err ( err) => warn ! ( "{name} exited with error {err}" ) ,
163
+ }
164
+ }
165
+
166
+ // pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
167
+ // use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
168
+ // if !ffmpeg_is_installed() {
169
+ // tracing::info!("FFmpeg not found, downloading...");
170
+ // tokio::task::spawn_blocking(|| {
171
+ // ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
172
+ // })
173
+ // .await??;
174
+ // }
175
+ // let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
176
+ // println!("FFmpeg version: {}", version);
177
+ // Ok(())
178
+ // }
0 commit comments