Skip to content

Commit 929ff8c

Browse files
committed
feat: 使用Jmuxer在浏览器中播放h264 nalu stream
1 parent fd9c132 commit 929ff8c

10 files changed

Lines changed: 705 additions & 140 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ num-traits = "0.2"
2121
anyhow = "1.0.33"
2222
dashmap = "4.0.0-rc6"
2323
crossbeam-utils = "0.8"
24-
once_cell = "1.4.1"
24+
once_cell = "1.4.1"
25+
async-tungstenite = "0.10.0"
26+
futures = "0.3"

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@ OBS
77

88
## Play
99

10+
### ffplay
1011
```shell
1112
ffplay -fflags nobuffer -analyzeduration 100000 rtmp://localhost:11935/channel/token
1213
```
13-
1414
使用`-fflags nobuffer -analyzeduration 100000`可以有效降低播放的延迟,目前本地测试延迟大概为1秒
1515

16+
### JMuxer(video only)
17+
使用[Jmuxer](https://github.com/samirkumardas/jmuxer)在浏览器中播放,详见`example/h264-nalu-stream`目录。
18+
19+
如果是使用x264编码推流,建议profile=baseline,可以避免视频频繁抖动,目前本地测试延迟大概为1秒
20+
1621
## Completed
1722
- [x] 支持不同分辨率的推流和拉流(之前默认1028x720)
1823
- [x] 支持音频传输
1924
- [x] 支持HTTP-FLV输出
25+
- [x] 输出H264流,使用[Jmuxer](https://github.com/samirkumardas/jmuxer)在浏览器中播放
2026

2127
## TODO
2228
- [ ] Web GUI 播放界面(FLV)
23-
- [ ] 输出H264流,使用[Jmuxer](https://github.com/samirkumardas/jmuxer)在浏览器中播放
29+
2430

2531
## 参考资料
2632
- [RTMP推送AAC ADTS音频流](https://www.jianshu.com/p/1a6f195863c7)

src/http_flv.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::util::spawn_and_log_error;
22
use smol::io::{AsyncReadExt, AsyncWriteExt};
33
use smol::net::{TcpListener, TcpStream};
44
use smol::stream::StreamExt;
5-
use crate::rtmp_server::{eventbus_map, video_header_map, audio_header_map, meta_data_map};
6-
use crate::protocol::flv::{FLV_HEADER_WITH_TAG0, FLV_HEADER_ONLY_VIDEO_WITH_TAG0};
5+
use crate::rtmp_server::{eventbus_map, video_header_map};
6+
use crate::protocol::flv::{FLV_HEADER_ONLY_VIDEO_WITH_TAG0};
77
use crate::protocol::flv::FlvTag;
88
use chrono::Local;
99
use std::convert::TryFrom;

src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#[macro_use]
22
extern crate num_derive;
33

4-
use crate::http_flv::run_server;
54
use crate::rtmp_server::accept_loop;
65
use crate::util::spawn_and_log_error;
76

@@ -10,11 +9,13 @@ mod http_flv;
109
mod protocol;
1110
mod rtmp_server;
1211
mod util;
12+
mod ws_h264;
1313

1414
fn main() -> anyhow::Result<()> {
1515
util::init_logger();
1616

17-
spawn_and_log_error(run_server("0.0.0.0:8080"));
17+
spawn_and_log_error(http_flv::run_server("0.0.0.0:8080"));
18+
spawn_and_log_error(ws_h264::run_server("0.0.0.0:8081"));
1819
let server = "0.0.0.0:11935";
1920
smol::block_on(accept_loop(server))
2021
}

src/protocol/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pub mod flv;
2-
pub mod h264;
32
pub mod rtmp;
3+
pub mod nalu;

src/protocol/nalu.rs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
use byteorder::{BigEndian, ByteOrder};
2+
3+
use crate::protocol::rtmp::{RtmpContext, RtmpMessage};
4+
5+
/// H264编码数据存储或传输的基本单元
6+
pub struct Nalu {
7+
inner: Vec<u8>,
8+
pub is_key_frame: bool,
9+
}
10+
11+
impl Nalu {
12+
/// RtmpMessage to Nalus
13+
pub fn from_rtmp_message(msg: &RtmpMessage) -> Vec<Nalu> {
14+
let bytes = &msg.body;
15+
let mut nalus = vec![];
16+
17+
let frame_type = bytes[0];
18+
let is_key_frame = frame_type == 0x17;
19+
let mut read_index = 1;
20+
let acv_packet_type = bytes[read_index];
21+
read_index += 4;
22+
23+
// AVCDecoderConfigurationRecord(AVC sequence header)
24+
if acv_packet_type == 0 {
25+
read_index += 5;
26+
let sps_num = &bytes[read_index] & 0x1F;
27+
read_index += 1;
28+
for _ in 0..sps_num as usize {
29+
let data_len = BigEndian::read_u16(&bytes[read_index..]);
30+
read_index += 2;
31+
let data = &bytes[read_index..(read_index + data_len as usize)];
32+
read_index += data_len as usize;
33+
34+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01];
35+
nalu_bytes.extend_from_slice(data);
36+
nalus.push(Self { inner: nalu_bytes, is_key_frame });
37+
}
38+
let num_of_pps = &bytes[read_index] & 0x1F;
39+
read_index += 1;
40+
for _ in 0..num_of_pps as usize {
41+
let data_len = BigEndian::read_u16(&bytes[read_index..]);
42+
read_index += 2;
43+
let data = &bytes[read_index..(read_index + data_len as usize)];
44+
read_index += data_len as usize;
45+
46+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01];
47+
nalu_bytes.extend_from_slice(data);
48+
nalus.push(Self { inner: nalu_bytes, is_key_frame });
49+
}
50+
}
51+
// One or more NALUs (Full frames are required)
52+
else if acv_packet_type == 1 {
53+
loop {
54+
if read_index >= bytes.len() {
55+
break;
56+
}
57+
let data_len = BigEndian::read_u32(&bytes[read_index..]);
58+
read_index += 4;
59+
let data = &bytes[read_index..(read_index + data_len as usize)];
60+
read_index += data_len as usize;
61+
62+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01];
63+
nalu_bytes.extend_from_slice(data);
64+
nalus.push(Self { inner: nalu_bytes, is_key_frame });
65+
}
66+
} else {
67+
log::warn!("unknown acv packet type");
68+
};
69+
nalus
70+
}
71+
72+
/// 帧优先级
73+
#[allow(unused)]
74+
pub fn get_nal_ref_idc(&self) -> u8 {
75+
self.inner[0] >> 5
76+
}
77+
78+
/// 帧类型
79+
#[allow(unused)]
80+
pub fn get_nal_unit_type(&self) -> u8 {
81+
self.inner[0] & 0x1F
82+
}
83+
84+
#[allow(unused)]
85+
pub fn nalu_type_desc(&self) -> String {
86+
let priority: String = match self.get_nal_ref_idc() {
87+
0 => "DISPOSABLE".into(),
88+
1 => "LOW".into(),
89+
2 => "HIGH".into(),
90+
3 => "HIGHEST".into(),
91+
_ => "UNKNOWN".into(),
92+
};
93+
94+
let t: String = match self.get_nal_unit_type() {
95+
1 => "SLICE".into(),
96+
2 => "DPA".into(),
97+
3 => "DPB".into(),
98+
4 => "DPC".into(),
99+
5 => "IDR".into(),
100+
6 => "SEI".into(),
101+
7 => "SPS".into(),
102+
8 => "PPS".into(),
103+
9 => "AUD".into(),
104+
10 => "EOSEQ".into(),
105+
11 => "EOSTREAM".into(),
106+
12 => "FILL".into(),
107+
_ => "UNKNOWN".into(),
108+
};
109+
110+
format!("{}::{}", priority, t)
111+
}
112+
}
113+
114+
impl AsRef<[u8]> for Nalu {
115+
fn as_ref(&self) -> &[u8] {
116+
self.inner.as_ref()
117+
}
118+
}
119+
120+
/// # VideoTagHeader
121+
///
122+
/// ## Frame Type
123+
///
124+
/// Type: UB [4]
125+
///
126+
/// Type of video frame. The following values are defined:
127+
/// 1 = key frame (for AVC, a seekable frame)
128+
/// 2 = inter frame (for AVC, a non-seekable frame)
129+
/// 3 = disposable inter frame (H.263 only)
130+
/// 4 = generated key frame (reserved for server use only)
131+
/// 5 = video info/command frame
132+
///
133+
/// ## CodecID
134+
///
135+
/// Type: UB [4]
136+
///
137+
/// Codec Identifier. The following values are defined:
138+
/// 2 = Sorenson H.263
139+
/// 3 = Screen video
140+
/// 4 = On2 VP6
141+
/// 5 = On2 VP6 with alpha channel
142+
/// 6 = Screen video version 2
143+
/// 7 = AVC
144+
///
145+
/// ## AVCPacketType
146+
///
147+
/// Type: IF CodecID == 7, UI8
148+
///
149+
/// The following values are defined:
150+
/// 0 = AVC sequence header
151+
/// 1 = AVC NALU
152+
/// 2 = AVC end of sequence (lower level NALU sequence ender is not required or supported)
153+
///
154+
/// ## CompositionTime
155+
///
156+
/// Type: IF CodecID == 7, SI24
157+
///
158+
/// IF AVCPacketType == 1
159+
/// Composition time offset
160+
/// ELSE
161+
/// 0
162+
/// See ISO 14496-12, 8.15.3 for an explanation of composition
163+
/// times. The offset in an FLV file is always in milliseconds.
164+
#[allow(unused)]
165+
pub fn handle_video_data(bytes: &[u8], ctx: &RtmpContext) {
166+
let frame_type = bytes[0];
167+
let mut read_index = 1;
168+
let acv_packet_type = bytes[read_index];
169+
read_index += 1;
170+
171+
// AVC时,全0,无意义(作业时间)
172+
let _composition_time_offset = &bytes[read_index..read_index + 3];
173+
read_index += 3;
174+
175+
log::debug!(
176+
"[peer={}] video frame type = {:#04X}, acv_packet_type={:#04X}",
177+
&ctx.peer_addr,
178+
frame_type,
179+
acv_packet_type
180+
);
181+
182+
// AVCDecoderConfigurationRecord(AVC sequence header)
183+
if acv_packet_type == 0 {
184+
// let config_version = &bytes[read_index];
185+
// let avc_profile_indication = &bytes[read_index + 1];
186+
// let profile_compatibility = &bytes[read_index + 2];
187+
// let avc_level_indication = &bytes[read_index + 3];
188+
// let length_size_minus_one = &bytes[read_index + 4];
189+
read_index += 5;
190+
let sps_num = &bytes[read_index] & 0x1F;
191+
read_index += 1;
192+
// println!("sps_num={}", sps_num);
193+
// println!("config_version={:#04X}", config_version);
194+
// println!("avc_profile_indication={:#04X}", avc_profile_indication);
195+
// println!("profile_compatibility={:#04X}", profile_compatibility);
196+
// println!("avc_level_indication={:#04X}", avc_level_indication);
197+
// println!("length_size_minus_one={:#04X}", length_size_minus_one);
198+
for _ in 0..sps_num as usize {
199+
let data_len = BigEndian::read_u16(&bytes[read_index..]);
200+
read_index += 2;
201+
let data = &bytes[read_index..(read_index + data_len as usize)];
202+
read_index += data_len as usize;
203+
// println!("len={}, sps data:\n{}", data_len, bytes_hex_format(data));
204+
205+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01];
206+
nalu_bytes.extend_from_slice(data);
207+
handle_nalu(nalu_bytes);
208+
}
209+
let num_of_pps = &bytes[read_index] & 0x1F;
210+
read_index += 1;
211+
// println!("pps num = {}", num_of_pps);
212+
for _ in 0..num_of_pps as usize {
213+
let data_len = BigEndian::read_u16(&bytes[read_index..]);
214+
read_index += 2;
215+
let data = &bytes[read_index..(read_index + data_len as usize)];
216+
read_index += data_len as usize;
217+
// println!("len={}, pps data:\n{}", data_len, bytes_hex_format(data));
218+
219+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x00, 0x01];
220+
nalu_bytes.extend_from_slice(data);
221+
handle_nalu(nalu_bytes);
222+
}
223+
}
224+
// One or more NALUs (Full frames are required)
225+
else if acv_packet_type == 1 {
226+
loop {
227+
if read_index >= bytes.len() {
228+
break;
229+
}
230+
let data_len = BigEndian::read_u32(&bytes[read_index..]);
231+
read_index += 4;
232+
let data = &bytes[read_index..(read_index + data_len as usize)];
233+
read_index += data_len as usize;
234+
// println!("NALU Type: {}, len={}", nalu_type_desc(&data[0]), data_len);
235+
// println!("len={}, nalu data:\n{}", data_len, bytes_hex_format(data));
236+
237+
let mut nalu_bytes: Vec<u8> = vec![0x00, 0x00, 0x01];
238+
nalu_bytes.extend_from_slice(data);
239+
handle_nalu(nalu_bytes);
240+
}
241+
} else {
242+
unreachable!("unknown acv packet type")
243+
};
244+
245+
fn handle_nalu(nalu_bytes: Vec<u8>) {}
246+
}

0 commit comments

Comments
 (0)