任务
OBS推流工具推送RTMP流码,解析该流码,汇总成Message流码
知识补充
1、源码以及其他知识补充:手工开发RTMP-HLS简易服务器_1_知识梳理
2、OBS推流的RTMP流码是基于TCP传输模式传输的。
3、由于当时C++能力有限,所以选择Java开发,建议使用C++,利用C++的【位域】可以加快开发速度。
4、官方RTMP文档:REAL-TIME MESSAGING PROTOCOL (RTMP) SPECIFICATION
5、Wireshark,如果你是初学者,使用wireshark可以检查你发送或接收到的流码是否正确。
RTMP解析
1、握手流程
1.1 C0、S0包格式
1.2 C1 和 S1消息格式
1.3 C2 和 S2 消息格式
2、块格式
这是整个块的样子,接下来将依次介绍。
2.1 块基本头(Basic Header)
fmt格式代表 块消息头的格式(2.2 会具体介绍)
csid是当前块的id,OBS推流信息中 ,csid 为2 的为控制信息,4为视频信息,5为音频信息(4 和 5貌似反了,忘记了。)。
【注】:理解这里有个坑,一般人会认为,既然是块的ID,按理说,不应该每个块的id都不一样吗?一个块可以装下视频信息吗?,答:个人猜测,在设计RTMP之初,考虑到丢包情况,出现了csid,且相互都不同,但是OBS推流的RTMP流码基于TCP,所以丢包情况在链路层以及得以解决,所以CSID可以固定下来。也有人说,是CSID传输完成一个块后,可以复用该块的CSID,所以也固定了下来。
【注】:块基本头还有其他格式,由于OBS推流的块类别少,所以这一种最简单块基本头就可以了。
2.2 块消息头(Message Header)
1)fmt == 0
2)fmt == 1
3)fmt == 2
4)fmt == 3
全部省略,同理继承上一个chunk。
3、关键代码
public void builder(BufferedInputStream bis,MessageFactory msgFactory) throws IOException, InterruptedException{
int contentLen = bis.available();
Map<byte[],MsgBean> saveChunk = msgFactory.getSaveChunk();
do{
BasicChunk bc = new BasicChunk();
byte b = (byte)bis.read();
byte fmt = ChunkBuilder.makeFmt(b);
bc.setFmt(fmt);
bc.setCsid(ChunkBuilder.makeCSID(b,bis));
byte[] headerbuffer= ChunkBuilder.makeMsgHeader(fmt, bis);
try{
makeMsgHeader(bc,headerbuffer);
}catch (NullPointerException e){
System.out.println(ByteUtil.bytes2Str(headerbuffer == null ? new byte[]{0} : headerbuffer));
System.out.println(fmt);
System.out.println(ByteUtil.bytes2Str(bc.getCsid()));
msgFactory.close();
return ;
}
int extendTimeLen = ByteUtil.bytesEquals(bc.getMsgHeader_time(), new byte[]{-128,-128,-128})?4:0;
if(extendTimeLen > 0){
byte[] bArr = new byte[extendTimeLen];
bis.read(bArr);
bc.setExtendTime(bArr);
}
int msgLen = ByteUtil.bytesToInt(bc.getMsgHeader_length());
int nowLen = saveChunk.containsKey(bc.getMsgHeader_stream())?saveChunk.get(bc.getMsgHeader_stream()).getNowLen():0;
int deLen = msgLen - nowLen;
//int dataLen = msgLen < realDataLen ? msgLen : realDataLen > deLen ? deLen : realDataLen;
int dataLen = realDataLen < deLen ? realDataLen : deLen;
dataLen = dataLen < 0 ? 0 : dataLen;
if(bc.getFmt() == 0){
nowTimestamp = ByteUtil.bytesToInt(bc.getMsgHeader_time());
}else{
nowTimestamp += ByteUtil.bytesToInt(bc.getMsgHeader_time());
}
bc.setMsgHeader_time(ByteUtil.intToBytes(nowTimestamp));
//dataLen = msgLen;
Log.append("{realDataLen = " + realDataLen + "\n msgLen = "+msgLen+"\n nowLen =" + nowLen +"\n deLen = " + deLen + "\n dataLen = " + dataLen +"}\n");
//byte[] b2Arr = new byte[dataLen];
//bis.read(b2Arr);
bc.setData(bigRead(bis,dataLen));
//System.out.println(bc.toString());
Log.append(bc.toString() + "\n");
if(ByteUtil.bytesToInt(bc.getCsid()) != 2){
if(saveChunk.containsKey(bc.getMsgHeader_stream())){
saveChunk.get(bc.getMsgHeader_stream()).add(bc);
}else{
saveChunk.put(bc.getMsgHeader_stream(), new MsgBean(bc));
}
msgFactory.checkFull();
}else{
ControlChunk cc = new ControlChunk(bc,this,msgFactory);
cc.control();
}
contentLen = bis.available();
}while(contentLen > 0);
}
代码思路,大致为:构建一个Map<byte[],list<BasicChunk>>,通过Socket接收TCP推流来的流码,将流码依次读取,按照之前的格式建立BasicChunk对象,根据BasicChunk的msg stream id,存储在map中。(注:Map<msg_stream_id,list<BasicChunk>>),如果BaiscChunk的CSID=2,先执行该Chunk,且不存储该Chunk。以下会介绍Chunk的CSID=2 时,会有哪些控制信息。
至此:你已经能够将OBS推流的RTMP流码拼接为Message了。
4、块消息补充(CSID==2)
4.1 设置块大小
4.2 取消消息
5、示例
5.1. 例 1
Message Stream ID
|
Message T
y
pe ID
|
Time
|
Length
| |
Msg # 1
|
12345
|
8
|
1000
|
32
|
Msg # 2
|
12345
|
8
|
1020
|
32
|
Msg # 3
|
12345
|
8
|
1040
|
32
|
Msg # 4
|
12345
|
8
|
1060
|
32
|
Chunk
Stream ID
|
Chunk
Type
|
Header Data
|
No.of Bytes After
Header
|
Total No.of
Bytes in the
Chunk
| |
Chunk#1
|
3
|
0
|
delta: 1000
length: 32
type: 8
stream ID
:1234
(11bytes)
|
32
|
44
|
Chunk#2
|
3
|
2
|
20 (3 bytes)
|
32
|
36
|
Chunk#3
|
3
|
3
|
none(0
bytes)
|
32
|
33
|
Chunk#4
|
3
|
3
|
none(0
bytes)
|
32
|
33
|
5.2 例2
Message Stream ID
|
Message TYpe ID
|
Time
|
Length
| |
Msg # 1
|
12346
|
9 (video)
|
1000
|
307
|
Chunk
Stream
ID
|
Chunk
Type
|
Header
Data
|
No. of
Bytes after
Header
|
Total No. of
bytes in the chunk
| |
Chunk#1
|
4
|
0
|
delta: 1000
length: 307
type: 9
streamID: 12346
(11
bytes)
|
128
|
140
|
Chunk#2
|
4
|
3
|
none (0
bytes)
|
128
|
129
|
Chunk#3
|
4
|
3
|
none (0
bytes)
|
51
|
52
|
感谢
1、官方文档翻译