项目地址: https://github.com/mobinsheng/toy_rtc
介绍
本项目使用的是自定义协议。没有使用RTP、RTCP协议的原因是toy_rtc本身就不是按照标准RTC去开发的,因此为了方便扩展,自己实现了传输协议。这带来了一个坏处,包头占比比较大,会浪费带宽,在低带宽场景下比较受限。
说实话,传输协议这一块设计的非常垃圾,就应该直接使用rtp、rtcp协议,这是设计上的失误,应该引以为戒!
协议的定义
总览
视频数据协议:
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+
|网络包头 |媒体包头 |视频包头 |视频数据 |
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+
音频频数据协议:
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+
|网络包头 |媒体包头 |音频包头 |音频数据 |
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+
信令协议:
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+
|网络包头 |信令包头 |信令内容 |
+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+
网络包头
+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V|E|R| type | user_id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+
每一个媒体包或者信令包的最前面都会带一个网络包头,长度是5 字节,内容如下:
- V:版本号,2bit
- E:扩展标识符:1bit
- R:保留标识符:1bit
- type:类型:4bit
- user_id:用户ID,32bit
///< 网络包头部
struct NetPacketHead {
NetPacketHead();
NetPacketHead(const uint8_t* buf, size_t len);
// 序列化
void pack(uint8_t* data, size_t* size);
// 反序列化
void unpack();
enum {
kHeadSize = 11, // 包头大小
};
uint8_t version; // 2b,版本
uint8_t reserved;// 2b,保留字段/扩展字段
uint8_t type; // 4b,包类型 app,audio,video,rtx
uint32_t src_uid; // 4B,本地用户id
uint32_t dst_uid; // 4B,远端用户id
uint16_t payload_size; // 2B,载荷长度,信令包需要
private:
Marshallable marsh; // 序列化、反序列工具类
};
媒体包头
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V |enable_redundancy |p_flag |r_flag |type|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|media_pkt_id |network_pkt_id |E |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|redundancy_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fec_group_id |fec_k |fec_n | fec_index |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|red_packet_num |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|red_packet_size_array |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
每一个媒体数据的前面都会带一个媒体包头,它包含了媒体数据的包序号、网络序号、媒体类型以及FEC、RED、padding、nack相关的一些信息:
- V:版本号,2bit
- enable_redundancy :是否开启FEC、RED功能,注意它并不表示当前包 是否为冗余包,仅仅指示冗余功能是否开启,1bit
- p_flag :padding标志,padding数据需要设置它,1bit
- r_flag :重传标志,重传包需要设置它,1bit
- type:媒体类型,2b
- media_pkt_id:媒体序号,audio和video都有独立的媒体序号,32bit
- network_pkt_id :网络序号,所有的媒体数据包括rtx和padding都共用同一个网络序号,32bit
- E:扩展标志,1bit
- redundancy_type:冗余类型,FEC或者RED,8bit
- fec_group_id:fec组的序号,32bit
- fec_k:fec的k,原始包数量,8bit
- fec_n:fec的n,原始包+冗余包的数量,8bit
- fec_index:当前包在fec组内的序号,8bit
- red_packet_num:red包内原始包的数量,8bit
- red_packet_size_array:red包内每个原始包的长度,可变长度,最长是8*16bit
///< 媒体包头:音频包、视频包,冗余包的前面都要加上一个媒体包头
class MediaHead {
public:
enum {
kMaxRedNum = 8,
};
MediaHead();
MediaHead(const uint8_t* buf, size_t len);
MediaHead(const MediaHead& v);
MediaHead& operator =(const MediaHead& v);
// 序列化
void pack(uint8_t* data, size_t* size);
// 反序列化
void unpack();
// 基本头部 --begin
uint8_t version; //3b
uint8_t enable_redundancy; // 1b,请注意这个标志:它表示是否开启FEC、RED功能,而不代表当前是否为冗余包
uint8_t padding_flag; // 1b,请注意这个标志:padding数据需要设置它
uint8_t retransmit_flag; // 1b,请注意这个标志,重传包需要设置它
uint8_t media_type; // 2b,audio,video,媒体类型
uint32_t media_pkt_id; // 4B , 媒体序号,audio、video有自己独立的媒体序号
uint32_t network_pkt_id; // 4B ,网络序号,请注意这个字段:所有媒体数据包括rtx都共用这个网络序号
uint8_t reserved; // 1B,保留字段
// 基本头部 --end
// 保护类型扩展字段,当enable_redundancy是1的时候才存在 --begin
uint8_t redundancy_type; // fec or red // 1B,冗余类型
// fec相关字段
uint32_t fec_group_id; // 4B,fec组序号
uint8_t fec_k; // 1B,fec的k,原始包数量
uint8_t fec_n; // 1B,fec的n,原始包+冗余包的数量
uint8_t fec_index; // 1B,当前包在fec组内的序号
// red相关字段
uint8_t red_packet_num; // 1B,red包内原始包的数量
uint16_t red_packet_size_array[kMaxRedNum]; // 每个原始包的长度
// fec扩展字段,当enable_redundancy是1的时候才存在 --end
size_t GetHeadSize();
static size_t GetHeadSize(uint8_t* data);
private:
enum {
kBasicHeadSize = 10,
kRetransmitPartSize = 0,
kFecFixedSize = 1,
kFecPartSize = 7,
};
Marshallable marsh;
};
视频包头
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|codec_type |frame_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fragment_count |fragment_id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|frame_id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
视频包头包含了视频帧的信息:
- V:版本号,8bit
- codec_type,编码类型,4bit
- frame_type,帧类型,4bit
- fragment_count,一个视频帧的分包数量,16bit
- fragment_id,一个视频帧内包的索引,16bit
- frame_id,帧序号,32bit
- timestamp,时间戳,32bit
///< 视频包头部
struct VideoPacketHead {
VideoPacketHead();
VideoPacketHead(const uint8_t* data, const size_t len);
// 序列化
void pack(uint8_t* data, size_t* size);
// 反序列化
void unpack();
enum {
kHeadSize = 14,
};
uint8_t version; // 1B,版本
uint8_t codec_type; // 4 bit,编码器类型
uint8_t frame_type; // 4 bit,帧类型
uint16_t fragment_count; // 2B,分片的数量(一个视频帧可能分成多个分片)
uint16_t fragment_id; // 2B,分片的索引
uint32_t frame_id; // 4B ,帧的序号
uint32_t timestamp; // 4B,时间戳
private:
Marshallable marsh;
};
音频包
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|frame_id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|sample_rate_type |frame_size_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|channel_type |frame_type |codec_type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
音频包头包含了音频相关的信息:
- V:版本号,8bit
- frame_id:帧序号,32bit
- timestamp:时间戳,32bit
- sample_rate_type:采样率类型,8bit
- frame_size_type:帧长类型,3bit
- channel_type:通道数类型,1bit
- frame_type:音频帧类型,普通或者静音,4bit
- codec_type:编码类型,4bit
///< 音频包头部 TODO
struct AudioPacketHead {
AudioPacketHead();
AudioPacketHead(const uint8_t* data, const size_t len);
// 序列化
void pack(uint8_t* data, size_t* size);
// 反序列化
void unpack();
enum {
kHeadSize = 11,
};
uint8_t version; // 1B,版本
uint32_t frame_id; // packet id // 4B,帧序号
uint32_t timestamp; // 4B,时间戳
uint8_t sample_rate_type; // 4bit,采样率类型
uint8_t frame_size_type; // 3bit,帧长类型
uint8_t channel_type; // 1bit,通道数类型
uint8_t frame_type; // 4bit,帧类型(静音or普通)
uint8_t codec_type; // 4bit,编码器类型
private:
Marshallable marsh;
};
信令包
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V |command |payload_size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
它包含了信令的通用信息:
- V:版本,8bit
- command:信令,8bit
- payload_size:信令载荷长度,16bit
///< app(信令)包头部
struct AppPacketHead {
AppPacketHead();
AppPacketHead(const uint8_t* data, const size_t len);
// 序列化
void pack(uint8_t* data, size_t* size);
// 反序列化
void unpack();
enum {
kHeadSize = 4,
};
uint8_t version; // 1B,版本
uint8_t command; //1B,信令类型
uint16_t payload_size; // 2B,载荷长度
private:
Marshallable marsh;
};