Skip to content

Commit e3ccb12

Browse files
Stream capture example from Guilherme-j10 ☕
feat(example): add stream_capture example and update Cargo.toml
2 parents 38ea964 + 1eb5412 commit e3ccb12

5 files changed

Lines changed: 264 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,9 @@ doc-scrape-examples = false
8282
name = "cli"
8383
doc-scrape-examples = false
8484

85+
[[example]]
86+
name = "stream_capture"
87+
doc-scrape-examples = false
88+
8589
[workspace]
8690
members = ["windows-capture-python"]

README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,124 @@ fn main() {
173173
}
174174
```
175175

176+
## Stream-based encoding example
177+
178+
Instead of writing directly to a file, you can encode into an in-memory stream using `VideoEncoder::new_from_stream`. This is useful when you need to send the encoded video over a network, pipe it to another process, or process it further before saving.
179+
180+
```rust
181+
use std::io::{self, Write};
182+
use std::time::Instant;
183+
184+
use windows::Storage::Streams::InMemoryRandomAccessStream;
185+
use windows::core::Interface;
186+
use windows_capture::capture::{Context, GraphicsCaptureApiHandler};
187+
use windows_capture::encoder::{
188+
AudioSettingsBuilder, ContainerSettingsBuilder, VideoEncoder, VideoSettingsBuilder,
189+
};
190+
use windows_capture::frame::Frame;
191+
use windows_capture::graphics_capture_api::InternalCaptureControl;
192+
use windows_capture::graphics_capture_picker::GraphicsCapturePicker;
193+
use windows_capture::settings::{
194+
ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings,
195+
MinimumUpdateIntervalSettings, SecondaryWindowSettings, Settings,
196+
};
197+
198+
struct StreamCapture {
199+
encoder: Option<VideoEncoder>,
200+
stream: InMemoryRandomAccessStream,
201+
start: Instant,
202+
}
203+
204+
impl GraphicsCaptureApiHandler for StreamCapture {
205+
type Flags = (i32, i32);
206+
type Error = Box<dyn std::error::Error + Send + Sync>;
207+
208+
fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error> {
209+
// Create an in-memory stream that the encoder will write into.
210+
let stream = InMemoryRandomAccessStream::new()?;
211+
212+
let encoder = VideoEncoder::new_from_stream(
213+
VideoSettingsBuilder::new(ctx.flags.0 as u32, ctx.flags.1 as u32),
214+
AudioSettingsBuilder::default().disabled(true),
215+
ContainerSettingsBuilder::default(),
216+
stream.cast()?,
217+
)?;
218+
219+
Ok(Self {
220+
encoder: Some(encoder),
221+
stream,
222+
start: Instant::now(),
223+
})
224+
}
225+
226+
fn on_frame_arrived(
227+
&mut self,
228+
frame: &mut Frame,
229+
capture_control: InternalCaptureControl,
230+
) -> Result<(), Self::Error> {
231+
print!(
232+
"\rStreaming for: {} seconds | Buffer size: {} bytes",
233+
self.start.elapsed().as_secs(),
234+
self.stream.Size()?
235+
);
236+
io::stdout().flush()?;
237+
238+
self.encoder.as_mut().unwrap().send_frame(frame)?;
239+
240+
if self.start.elapsed().as_secs() >= 6 {
241+
self.encoder.take().unwrap().finish()?;
242+
243+
let size = self.stream.Size()?;
244+
println!("\nCapture finished. Stream contains {size} bytes.");
245+
246+
// Read the encoded bytes from the in-memory stream.
247+
let reader = windows::Storage::Streams::DataReader::CreateDataReader(
248+
&self.stream.GetInputStreamAt(0)?,
249+
)?;
250+
reader.LoadAsync(size as u32)?.join()?;
251+
252+
let mut bytes = vec![0u8; size as usize];
253+
reader.ReadBytes(&mut bytes)?;
254+
std::fs::write("stream_output.mp4", &bytes)?;
255+
256+
println!("Saved stream to stream_output.mp4");
257+
capture_control.stop();
258+
}
259+
260+
Ok(())
261+
}
262+
263+
fn on_closed(&mut self) -> Result<(), Self::Error> {
264+
println!("Capture session ended");
265+
Ok(())
266+
}
267+
}
268+
269+
fn main() {
270+
let item = GraphicsCapturePicker::pick_item().expect("Failed to pick item");
271+
272+
let Some(item) = item else {
273+
println!("No item selected");
274+
return;
275+
};
276+
277+
let size = item.size().expect("Failed to get item size");
278+
279+
let settings = Settings::new(
280+
item,
281+
CursorCaptureSettings::Default,
282+
DrawBorderSettings::Default,
283+
SecondaryWindowSettings::Default,
284+
MinimumUpdateIntervalSettings::Default,
285+
DirtyRegionSettings::Default,
286+
ColorFormat::Rgba8,
287+
size,
288+
);
289+
290+
StreamCapture::start(settings).expect("Stream capture failed");
291+
}
292+
```
293+
176294
## DXGI Desktop Duplication example
177295

178296
```rust

examples/graphics_capture.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::io::{self, Write};
22
use std::time::Instant;
33

4+
use windows::Storage::Streams::IRandomAccessStream;
45
use windows_capture::capture::{Context, GraphicsCaptureApiHandler};
56
use windows_capture::encoder::{AudioSettingsBuilder, ContainerSettingsBuilder, VideoEncoder, VideoSettingsBuilder};
67
use windows_capture::frame::Frame;

examples/stream_capture.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::io::{self, Write};
2+
use std::time::Instant;
3+
4+
use windows::Storage::Streams::InMemoryRandomAccessStream;
5+
use windows::core::Interface;
6+
use windows_capture::capture::{Context, GraphicsCaptureApiHandler};
7+
use windows_capture::encoder::{
8+
AudioSettingsBuilder, ContainerSettingsBuilder, VideoEncoder, VideoSettingsBuilder,
9+
};
10+
use windows_capture::frame::Frame;
11+
use windows_capture::graphics_capture_api::InternalCaptureControl;
12+
use windows_capture::graphics_capture_picker::GraphicsCapturePicker;
13+
use windows_capture::settings::{
14+
ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings,
15+
MinimumUpdateIntervalSettings, SecondaryWindowSettings, Settings,
16+
};
17+
18+
struct StreamCapture {
19+
encoder: Option<VideoEncoder>,
20+
stream: InMemoryRandomAccessStream,
21+
start: Instant,
22+
}
23+
24+
impl GraphicsCaptureApiHandler for StreamCapture {
25+
type Flags = (i32, i32);
26+
type Error = Box<dyn std::error::Error + Send + Sync>;
27+
28+
fn new(ctx: Context<Self::Flags>) -> Result<Self, Self::Error> {
29+
// Create an in-memory stream that the encoder will write into.
30+
// This is useful for scenarios where you want to process the encoded
31+
// video in memory (e.g., sending it over a network, piping to another
32+
// process, or performing further transformations) instead of writing
33+
// directly to a file.
34+
let stream = InMemoryRandomAccessStream::new()?;
35+
36+
let encoder = VideoEncoder::new_from_stream(
37+
VideoSettingsBuilder::new(ctx.flags.0 as u32, ctx.flags.1 as u32),
38+
AudioSettingsBuilder::default().disabled(true),
39+
ContainerSettingsBuilder::default(),
40+
stream.cast()?,
41+
)?;
42+
43+
Ok(Self {
44+
encoder: Some(encoder),
45+
stream,
46+
start: Instant::now(),
47+
})
48+
}
49+
50+
fn on_frame_arrived(
51+
&mut self,
52+
frame: &mut Frame,
53+
capture_control: InternalCaptureControl,
54+
) -> Result<(), Self::Error> {
55+
print!(
56+
"\rStreaming for: {} seconds | Buffer size: {} bytes",
57+
self.start.elapsed().as_secs(),
58+
self.stream.Size()?
59+
);
60+
io::stdout().flush()?;
61+
62+
self.encoder.as_mut().unwrap().send_frame(frame)?;
63+
64+
// Stop after 6 seconds and dump the in-memory buffer to a file to prove
65+
// the stream-based encoder works. In a real application you would read
66+
// from the stream continuously and forward the bytes elsewhere.
67+
if self.start.elapsed().as_secs() >= 6 {
68+
self.encoder.take().unwrap().finish()?;
69+
70+
let size = self.stream.Size()?;
71+
println!("\nCapture finished. Stream contains {size} bytes.");
72+
73+
// Write the in-memory stream to a file as a demonstration.
74+
let reader =
75+
windows::Storage::Streams::DataReader::CreateDataReader(&self.stream.GetInputStreamAt(0)?)?;
76+
reader.LoadAsync(size as u32)?.join()?;
77+
78+
let mut bytes = vec![0u8; size as usize];
79+
reader.ReadBytes(&mut bytes)?;
80+
std::fs::write("stream_output.mp4", &bytes)?;
81+
82+
println!("Saved stream to stream_output.mp4");
83+
84+
capture_control.stop();
85+
}
86+
87+
Ok(())
88+
}
89+
90+
fn on_closed(&mut self) -> Result<(), Self::Error> {
91+
println!("Capture session ended");
92+
Ok(())
93+
}
94+
}
95+
96+
fn main() {
97+
let item = GraphicsCapturePicker::pick_item().expect("Failed to pick item");
98+
99+
let Some(item) = item else {
100+
println!("No item selected");
101+
return;
102+
};
103+
104+
let size = item.size().expect("Failed to get item size");
105+
106+
let settings = Settings::new(
107+
item,
108+
CursorCaptureSettings::Default,
109+
DrawBorderSettings::Default,
110+
SecondaryWindowSettings::Default,
111+
MinimumUpdateIntervalSettings::Default,
112+
DirtyRegionSettings::Default,
113+
ColorFormat::Rgba8,
114+
size,
115+
);
116+
117+
StreamCapture::start(settings).expect("Stream capture failed");
118+
}

src/encoder.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,29 @@ impl VideoEncoder {
981981
}
982982

983983
/// Constructs a new `VideoEncoder` that writes to the given stream.
984+
///
985+
/// Unlike [`VideoEncoder::new`], which writes directly to a file, this constructor writes
986+
/// encoded output into any [`IRandomAccessStream`]. Use [`InMemoryRandomAccessStream`] to
987+
/// keep the encoded video in memory (e.g., for network streaming or further processing).
988+
///
989+
/// # Example
990+
/// ```no_run
991+
/// use windows::Storage::Streams::InMemoryRandomAccessStream;
992+
/// use windows::core::Interface;
993+
/// use windows_capture::encoder::{
994+
/// AudioSettingsBuilder, ContainerSettingsBuilder, VideoEncoder, VideoSettingsBuilder,
995+
/// };
996+
///
997+
/// let stream = InMemoryRandomAccessStream::new().unwrap();
998+
///
999+
/// let encoder = VideoEncoder::new_from_stream(
1000+
/// VideoSettingsBuilder::new(1920, 1080),
1001+
/// AudioSettingsBuilder::new().disabled(true),
1002+
/// ContainerSettingsBuilder::new(),
1003+
/// stream.cast().unwrap(),
1004+
/// )
1005+
/// .unwrap();
1006+
/// ```
9841007
#[inline]
9851008
pub fn new_from_stream(
9861009
video_settings: VideoSettingsBuilder,

0 commit comments

Comments
 (0)