Skip to content

Commit 9c96e9f

Browse files
committed
[+] feat: rtmp can push h.264 stream
1 parent 1d2b268 commit 9c96e9f

File tree

15 files changed

+1676
-37
lines changed

15 files changed

+1676
-37
lines changed

Cargo.lock

Lines changed: 187 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ x264 = "0.5"
9191
http = "1.4"
9292
rdev = "0.5"
9393
open = "5.3"
94-
cpal = "0.16"
94+
cpal = "0.17"
9595
hound = "3.5"
9696
which = "8.0"
9797
ctrlc = "3.5"
@@ -126,8 +126,11 @@ rodio = { version = "0.21", default-features = false }
126126
rustls = { version = "0.23", default-features = false }
127127
tokio-rustls = { version = "0.26", default-features = false }
128128

129+
rml_rtmp = "0.8"
130+
129131
mp4m = { path = "lib/mp4m" }
130132
wrtc = { path = "lib/wrtc" }
133+
srtmp = { path = "lib/srtmp" }
131134
cutil = { path = "lib/cutil" }
132135
sqldb = { path = "lib/sqldb" }
133136
pmacro = { path = "lib/pmacro" }

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ clippy:
107107
cargo clippy $(proj-features)
108108

109109
check:
110-
cargo check --no-default-features $(proj-features) --bin ${app-name}
110+
$(desktop-build-env) cargo check --no-default-features $(proj-features) --bin ${app-name}
111111

112112
clean:
113113
cargo clean

lib/mp4m/src/mp4_processor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ impl Mp4Processor {
426426
let duration = VIDEO_TIMESCALE / self.config.video_config.fps;
427427

428428
// Detect if this is a keyframe (I-frame) by checking for SPS/PPS or start code
429-
let is_sync = self.is_keyframe(&data);
429+
let is_sync = Self::is_keyframe(&data);
430430

431431
let sample = Mp4Sample {
432432
start_time: *video_timestamp,
@@ -443,7 +443,7 @@ impl Mp4Processor {
443443
*video_timestamp += duration as u64;
444444
}
445445

446-
fn is_keyframe(&self, data: &[u8]) -> bool {
446+
pub fn is_keyframe(data: &[u8]) -> bool {
447447
// Since we're using length-prefixed NAL units (not Annex B),
448448
// we need to parse the NAL units differently
449449
let mut i = 0;

lib/recorder/src/audio_recorder.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl AudioRecorder {
9898

9999
fn get_device_info(&self, device: &Device) -> Result<AudioDeviceInfo, AudioRecorderError> {
100100
let name = device
101-
.name()
101+
.id()
102102
.map_err(|e| AudioRecorderError::DeviceError(e.to_string()))?;
103103

104104
let default_config = device
@@ -112,7 +112,7 @@ impl AudioRecorder {
112112
.unwrap_or_else(|_| vec![]);
113113

114114
Ok(AudioDeviceInfo {
115-
name,
115+
name: name.to_string(),
116116
default_config,
117117
supported_formats,
118118
})
@@ -157,7 +157,11 @@ impl AudioRecorder {
157157
.host
158158
.input_devices()
159159
.map_err(|e| AudioRecorderError::HostError(e.to_string()))?
160-
.find(|d| d.name().map(|name| name == device_name).unwrap_or(false))
160+
.find(|d| {
161+
d.id()
162+
.map(|name| name.to_string() == device_name)
163+
.unwrap_or(false)
164+
})
161165
.ok_or_else(|| {
162166
AudioRecorderError::DeviceError(format!("Device '{}' not found", device_name))
163167
})?;
@@ -183,7 +187,7 @@ impl AudioRecorder {
183187

184188
Ok(WavSpec {
185189
channels: stream_config.channels,
186-
sample_rate: stream_config.sample_rate.0,
190+
sample_rate: stream_config.sample_rate,
187191
bits_per_sample: 32,
188192
sample_format: hound::SampleFormat::Float,
189193
})

lib/srtmp/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/target
2+
/tmp
3+
/.claude
4+
/examples/live
5+
/live
6+
7+
.mcp.json

lib/srtmp/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "srtmp"
3+
license.workspace = true
4+
edition.workspace = true
5+
version.workspace = true
6+
readme.workspace = true
7+
authors.workspace = true
8+
keywords.workspace = true
9+
homepage.workspace = true
10+
repository.workspace = true
11+
description.workspace = true
12+
13+
[dependencies]
14+
log.workspace = true
15+
mp4m.workspace = true
16+
bytes.workspace = true
17+
fdk-aac.workspace = true
18+
rml_rtmp.workspace = true
19+
thiserror.workspace = true
20+
crossbeam.workspace = true
21+
derivative.workspace = true
22+
derive_setters.workspace = true
23+
24+
[dev-dependencies]
25+
image.workspace = true
26+
anyhow.workspace = true
27+
env_logger.workspace = true
28+
spin_sleep.workspace = true
29+
30+
[target.'cfg(target_os = "linux")'.dev-dependencies]
31+
video-encoder = { workspace = true, features = ["x264"] }
32+
33+
[target.'cfg(target_os = "windows")'.dev-dependencies]
34+
video-encoder = { workspace = true, features = ["ffmpeg"] }
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use srtmp::{AacEncoder, AacEncoderConfig};
2+
3+
fn main() -> Result<(), Box<dyn std::error::Error>> {
4+
env_logger::init();
5+
6+
let config = AacEncoderConfig::new(44100, 2)?;
7+
println!(
8+
"AAC encoder config: {}Hz, {} channels",
9+
config.sample_rate, config.channels
10+
);
11+
12+
let mut encoder = AacEncoder::new(config)?;
13+
14+
let sample_rate = 44100;
15+
let frequency = 440.0;
16+
let frame_samples = 1024; // AAC frame size (samples per channel)
17+
let mut pcm_data = Vec::with_capacity(frame_samples * 2);
18+
19+
for i in 0..frame_samples {
20+
let t = i as f32 / sample_rate as f32;
21+
let sample_value = (2.0 * std::f32::consts::PI * frequency * t).sin() * 0.3;
22+
pcm_data.push(sample_value);
23+
pcm_data.push(sample_value);
24+
}
25+
26+
println!("Generated {} PCM samples (stereo)", pcm_data.len());
27+
28+
// Encode PCM to AAC
29+
let aac_data = encoder.encode(&pcm_data)?;
30+
println!("Encoded to {} bytes of AAC data", aac_data.len());
31+
println!(
32+
"Compression ratio: {:.2}%",
33+
(aac_data.len() * 100) / (pcm_data.len() * 4)
34+
);
35+
36+
println!(
37+
"Input frame size: {} samples per channel",
38+
encoder.input_frame_size()
39+
);
40+
println!("Channels: {}", encoder.channels());
41+
42+
Ok(())
43+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
ffmpeg -re -f lavfi \
4+
-i "testsrc=size=1280x720:rate=30" \
5+
-f lavfi -i "sine=frequency=1000:duration=3600" \
6+
-c:v libx264 -preset veryfast \
7+
-c:a aac -b:a 128k \
8+
-f flv "rtmp://localhost/live/stream"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
ffplay -fflags nobuffer -flags low_delay -max_delay 100000 \
4+
-i "rtmp://localhost:1935/live/stream"

0 commit comments

Comments
 (0)