实时音视频之toy_rtc(4)——定义传输协议

项目地址: 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;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值