Skip to content

Commit ca60181

Browse files
committed
feat: 将rtmp流直接保存成FLV文件,但视频存在一卡一卡的问题
1 parent 43c28f1 commit ca60181

4 files changed

Lines changed: 131 additions & 15 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ ffplay -fflags nobuffer -analyzeduration 100000 rtmp://localhost:11935/channel/t
1313

1414
使用`-fflags nobuffer -analyzeduration 100000`可以有效降低播放的延迟,目前本地测试延迟大概为1秒
1515

16-
## TODO
17-
16+
## Completed
1817
- [x] 支持不同分辨率的推流和拉流(之前默认1028x720)
1918
- [x] 支持音频传输
2019

20+
21+
## TODO
22+
- [x] 输出FLV格式视频
23+
2124
## 参考资料
2225
- [RTMP推送AAC ADTS音频流](https://www.jianshu.com/p/1a6f195863c7)
2326
- [视音频数据处理入门](https://blog.csdn.net/leixiaohua1020/article/details/50534369)
24-
- [rtmp数据封装](https://blog.csdn.net/Jacob_job/article/details/81880445)
27+
- [rtmp数据封装](https://blog.csdn.net/Jacob_job/article/details/81880445)
28+
- [视音频编解码学习工程:FLV封装格式分析器](https://blog.csdn.net/leixiaohua1020/article/details/17934487)

src/protocol/flv.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use byteorder::{BigEndian, ByteOrder};
2+
3+
use crate::protocol::rtmp::{ChunkMessageType, RtmpMessage};
4+
5+
pub const FLV_HEADER_WITH_TAG0: [u8; 13] = [
6+
0x46, 0x4c, 0x56, // signature
7+
0x01, // version
8+
0x05, // audio and video flag
9+
0x00, 0x00, 0x00, 0x09, // header length
10+
0x00, 0x00, 0x00, 0x00, // tag0 length
11+
];
12+
13+
pub struct FlvTag {
14+
raw_data: Vec<u8>,
15+
}
16+
17+
#[allow(unused)]
18+
impl FlvTag {
19+
/// 当RtmpMessage类型不是音频或视频的时候,会返回Error
20+
pub fn from_rtmp_message(mut msg: RtmpMessage) -> anyhow::Result<Self> {
21+
let mut raw_data = vec![];
22+
// data type
23+
match msg.header.message_type {
24+
ChunkMessageType::AudioMessage => raw_data.push(0x08),
25+
ChunkMessageType::VideoMessage => raw_data.push(0x09),
26+
_ => Err(anyhow::anyhow!("[FlvTag] invalid message type, {:?}", msg.header.message_type))?,
27+
}
28+
// data size
29+
raw_data.extend_from_slice(&(msg.body.len() as u32).to_be_bytes()[1..4]);
30+
// timestamp
31+
raw_data.extend_from_slice(&(msg.header.timestamp & 0xFFFFFF).to_be_bytes()[1..4]);
32+
// timestamp extended
33+
raw_data.push((msg.header.timestamp >> 24) as u8);
34+
// stream id
35+
raw_data.extend_from_slice(&0u32.to_be_bytes()[1..4]);
36+
// body
37+
raw_data.append(&mut msg.body);
38+
39+
Ok(FlvTag { raw_data })
40+
}
41+
42+
/// 0x08=audio, 0x09=video, 0x12=script
43+
pub fn tag_type(&self) -> u8 {
44+
self.raw_data[0]
45+
}
46+
47+
/// 0x08=audio, 0x09=video, 0x12=script
48+
pub fn data_size(&self) -> u32 {
49+
BigEndian::read_u24(&self.raw_data[1..4])
50+
}
51+
52+
/// ms timestamp
53+
pub fn timestamp(&self) -> u32 {
54+
let timestamp_u24 = BigEndian::read_u24(&self.raw_data[4..7]);
55+
timestamp_u24 | (self.raw_data[7] as u32) << 24
56+
}
57+
58+
/// audio/video/script data
59+
pub fn body(&self) -> &[u8] {
60+
&self.raw_data[11..]
61+
}
62+
}
63+
64+
impl AsRef<[u8]> for FlvTag {
65+
fn as_ref(&self) -> &[u8] {
66+
self.raw_data.as_ref()
67+
}
68+
}
69+
70+
71+

src/protocol/mod.rs

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

src/protocol/rtmp.rs

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
use std::fmt::{Debug, Formatter};
2+
use std::sync::{Arc, Weak};
3+
use std::time::Duration;
24

35
use amf::amf0;
46
use amf::amf0::Value;
57
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
68
use chrono::Local;
79
use num::FromPrimitive;
10+
use smol::channel::Receiver;
811
use smol::io::{AsyncReadExt, AsyncWriteExt};
912
use smol::net::TcpStream;
13+
use smol_timeout::TimeoutExt;
1014

15+
use crate::{nalu_eventbus, rtmp_msg_eventbus};
16+
use crate::protocol::flv::FlvTag;
1117
use crate::util::{bytes_hex_format, spawn_and_log_error};
12-
use smol::channel::{Receiver};
13-
use crate::nalu_eventbus;
14-
use std::sync::{Arc, Weak};
15-
use smol_timeout::TimeoutExt;
16-
use std::time::Duration;
18+
use crate::protocol::flv;
1719

1820
#[derive(Clone, Debug)]
1921
pub struct Handshake0 {
@@ -106,16 +108,22 @@ pub struct RtmpContext {
106108
pub chunk_size: u32,
107109
pub remain_message_length: u32,
108110
pub recv_bytes_num: u32,
109-
pub arc_receiver: Arc<Receiver<Vec<u8>>>,
111+
pub nalu_rx: Arc<Receiver<Vec<u8>>>,
110112
pub peer_addr: String,
111113
pub stream_name: String,
114+
pub flv_rx: Arc<Receiver<RtmpMessage>>
112115
}
113116

114117
impl RtmpContext {
115118
pub fn new(stream: TcpStream) -> Self {
116-
let receiver = nalu_eventbus().register_receiver();
117-
let receiver = Arc::new(receiver);
118-
let nalu_rx_weak = Arc::downgrade(&receiver);
119+
let nalu_rx = nalu_eventbus().register_receiver();
120+
let nalu_rx = Arc::new(nalu_rx);
121+
let nalu_rx_weak = Arc::downgrade(&nalu_rx);
122+
123+
let flv_rx = rtmp_msg_eventbus().register_receiver();
124+
let flv_rx = Arc::new(flv_rx);
125+
let flv_rx_weak = Arc::downgrade(&flv_rx);
126+
119127
let peer_addr = stream.peer_addr().map(|a| a.to_string()).unwrap_or_default();
120128

121129
async fn handle_nalu_rx(nalu_rx: Weak<Receiver<Vec<u8>>>, peer_addr: String) -> anyhow::Result<()> {
@@ -144,7 +152,39 @@ impl RtmpContext {
144152
Ok(())
145153
}
146154

155+
async fn handle_flv_rx(flv_rx: Weak<Receiver<RtmpMessage>>, peer_addr: String) -> anyhow::Result<()> {
156+
let tmp_dir = "tmp";
157+
if smol::fs::read_dir(tmp_dir).await.is_err() {
158+
smol::fs::create_dir_all(tmp_dir).await?;
159+
}
160+
161+
let mut file = smol::fs::OpenOptions::new()
162+
.create(true)
163+
.write(true)
164+
.truncate(true)
165+
.open("tmp/output.flv")
166+
.await?;
167+
168+
// write header
169+
file.write_all(&flv::FLV_HEADER_WITH_TAG0).await?;
170+
171+
while let Some(rx) = flv_rx.upgrade() {
172+
if let Some(rs) = rx.recv().timeout(Duration::from_secs(1)).await {
173+
if let Ok(msg) = rs {
174+
let flv_tag = FlvTag::from_rtmp_message(msg)?;
175+
file.write_all(flv_tag.as_ref()).await?;
176+
file.write_all(&(flv_tag.as_ref().len() as u32).to_be_bytes()).await?;
177+
} else {
178+
break;
179+
}
180+
}
181+
}
182+
log::warn!("[peer={}][handle_flv_rx] closed", peer_addr);
183+
Ok(())
184+
}
185+
147186
spawn_and_log_error(handle_nalu_rx(nalu_rx_weak, peer_addr.clone()));
187+
spawn_and_log_error(handle_flv_rx(flv_rx_weak, peer_addr.clone()));
148188

149189
RtmpContext {
150190
stream,
@@ -157,7 +197,8 @@ impl RtmpContext {
157197
chunk_size: 128,
158198
remain_message_length: 0,
159199
recv_bytes_num: 0,
160-
arc_receiver: receiver,
200+
nalu_rx,
201+
flv_rx,
161202
peer_addr,
162203
stream_name: Default::default(),
163204
}
@@ -178,7 +219,6 @@ impl RtmpContext {
178219
#[derive(Debug, Clone)]
179220
pub struct RtmpMessageHeader {
180221
/// chunk stream id
181-
/// 2 (low level), 3 (high level), 4 (control stream), 5 (video) and 6 (audio).
182222
pub csid: u8,
183223
pub timestamp: u32,
184224
pub message_length: u32,

0 commit comments

Comments
 (0)