当前位置:   article > 正文

协议圣经(二) RTP组播音视频技巧

rtp组播

协议圣经

协议圣经一是基础,还没有写,先出2

RTP组播

RTP为半应用层,半传输层协议,可以使用tcp,也可以使用udp,组播为D类地址,为何要使用组播,组播对什么有效,如224.3.4.5,端口选择一个9200,组播的好处是:
1 由交换机和路由器来确定发包
2 不用服务器转发
3 在udp基础上发送方便

缺点:不能跨网,不能到外网上去组播,也就是一旦确定组播方式,一般也就确定了是局域网程序,这里说一般,是因为是有mbone这种internet组播的,但是是实验爱好者组成的网,并不适合实际情况。

1、h264 h265 视频组播

h264编码和h265编码使用libx264 libx265 或者直接ffmpeg来编码,ffmpeg作为世界一等一的开源项目,封装了各类硬件和软件音视频编码和解码。

2、aac opus 音频组播

aac,opus 相对视频简单,不用分包,都小于MTU的大小

3、界面编写:

使用MFC c++来编写实例代码,这个相对简陋,有能力的可以使用任何界面,甚至跨个平台去编写,这里是示例,以方便调试展示为主。

rtp组播发送

3.1 采集

使用directshow去采集音频和视频,这个在windows上已经成熟彻底,也可以使用桌面来作为视频源,这个不做要求,主要是讲原理

3.2 设置

设置相对简单,后期示例需要加上窗口来写入码率,帧率设置,音频的一些设置

播放

其实播放是比较难做的,但是我们并不是为了制作播放器,我们是为了说明协议的发送,所以播放我们使用了一个非常简单的方法,vlc,写一个sdp文件,这里暂时只用视频来说明

m=video 9200 RTP/AVP 96
a=rtpmap:96 H264/90000
a=framerate:20
c=IN IP4 234.5.6.7

就这么简单,很奇特吧,端口在9200上面,映射payload为96 ,时间戳基数为 90000,如果你不想为90000,没问题的,改程序就可以,比如帧率是整数的20帧,你完全可以用一个绝对时间戳来作为RTP时间戳。组播IP地址为234.5.6.7,ok,启动程序后,使用vlc打开这个sdp,视频出现,播放器省了。一个好的benifit是,不用服务器就可以让跟多网内的人接收

rtp组播接收

m=video 9200 RTP/AVP 96 指明视频端口9200
a= framerate:20 指明帧率20
c=IN IP4 234.5.6.7 指明组播地址234.5.6.7

rtp协议

毋庸置疑,rtp,real time protocol 实时传输协议是传输协议的基础,使用RTP over UDP,如webrtc等都是使用这些东西来做的,rtp协议RFC文档:RFC 3550,不过还有很多其他文档,一般我们按照一个基础来做。

一般我们使用12个字节头部来说明rtp,比较重要的是时间戳和ssrc,注意,rtp头部并不一定是12字节,可以扩展。ssrc代表着一个唯一的流号码,如果多路流使用同样一组ssrc,那图像就要错了。但是同一路音频和视频是同一个ssrc的,否则谁也不知道哪个视频和哪个音频是要做合流。注意不能靠IP地址,因为一个IP地址可以发出多路流。

组播程序

我们只使用发送程序,使用asio来制作发送程序,并且使用同步发送,UDP其实非常奇怪,如果没有接收,bind 以后使用send其实是会有问题的,缓冲区满会停滞,所以我们使用sendto,即使是block也是不会停滞的,使用者放心不用担心发送阻塞的问题。以下给出组播发送的程序,组播接收的设置是不一样的,组播接收必须绑定组播的端口。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <asio.hpp>
#include <string>

using asio::ip::udp;


class c_multisock
{
public:
	c_multisock():v_socket(v_service)
	{}
	void read()
	{
		asio::ip::udp::endpoint sender;
		std::vector<char> buffer;
		std::size_t bytes_readable = 0;

		asio::socket_base::bytes_readable num_of_bytes_readable(true);
		v_socket.io_control(num_of_bytes_readable);
		// Get the value from the command.
		bytes_readable = num_of_bytes_readable.get();
		// If there is no data available, then sleep.
		if (!bytes_readable)
		{
			return;
		}
		// Resize the buffer to store all available data.
		buffer.resize(bytes_readable);
		// Read available data.
		v_socket.receive_from(
			asio::buffer(buffer, bytes_readable),
			sender);

		// Extract data from the buffer.
		std::string message(buffer.begin(), buffer.end());

		// Output data.
		std::cout << "Received message: ";
		std::cout << message << std::endl;
	}

	void write(uint8_t *data, size_t len)
	{
		//char buffer[256];
		//sprintf(buffer, msearchmsgfmt, "upnp:rootdevice");
		/*socket.async_send_to(
			boost::asio::buffer(buffer, strlen(buffer)), endpoint_,
			boost::bind(handle_send_to, this,
				boost::asio::placeholders::error));*/

		v_socket.send_to(asio::buffer(data, len), v_destination);
	}
	int asio_init(const char * ip,uint16_t port, bool receiver)
	{
		asio::ip::address address =
			asio::ip::address::from_string(ip); //"239.255.255.250"
	
		//udp::socket socket(v_service);
		v_socket.open(asio::ip::udp::v4());

		// Allow other processes to reuse the address, permitting other processes on
		// the same machine to use the multicast address.
		v_socket.set_option(udp::socket::reuse_address(true));

		// Guarantee the loopback is enabled so that multiple processes on the same
		// machine can receive data that originates from the same socket.
		v_socket.set_option(asio::ip::multicast::enable_loopback(true));
		v_receiver = receiver;
		v_socket.bind(
			udp::endpoint(asio::ip::address_v4::any(),
				receiver ? port /* same as multicast port */
				: 16200/* any */));
		v_destination = udp::endpoint(address, port);

		// Join group.
		v_socket.set_option(asio::ip::multicast::join_group(address));

		// Start read or write loops based on command line options.
		//if (receiver) read(socket);
		//else          write(socket, destination);
		return 0;
	}
private:
	bool v_receiver;
	udp::endpoint v_destination;
	asio::io_context v_service;
	udp::socket v_socket;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

RTP协议

typedef void(*rtp_callback)(void *puser, uint8_t *data, int len);

#define PACKET_BUFFER_END            (unsigned int)0x00000000
#define MAX_RTP_PKT_LENGTH     1400
#define H264                    96
#define AAC                     97
#define TEXT_                   98
#define PACKET_URL_             177
typedef struct
{
	/**//* byte 0 */
	unsigned char csrc_len : 4;        
	unsigned char extension : 1;        
	unsigned char padding : 1;        
	unsigned char version : 2;       
	/**//* byte 1 */
	unsigned char payload : 7;       
	unsigned char marker : 1;       
	/**//* bytes 2, 3 */
	unsigned short seq_no;
	/**//* bytes 4-7 */
	unsigned  long timestamp;
	/**//* bytes 8-11 */
	unsigned long ssrc;            
} RTP_FIXED_HEADER;

typedef struct {
	//byte 0
	unsigned char TYPE : 5;
	unsigned char NRI : 2;
	unsigned char F : 1;

} NALU_HEADER; /**//* 1 BYTES */

typedef struct {
	//byte 0
	unsigned char TYPE : 5;
	unsigned char NRI : 2;
	unsigned char F : 1;

} FU_INDICATOR; /**//* 1 BYTES */

typedef struct {
	//byte 0
	unsigned char TYPE : 5;
	unsigned char R : 1;
	unsigned char E : 1;
	unsigned char S : 1;
} FU_HEADER; /**//* 1 BYTES */


//please do not over 1400 bytes,because we must portable with webrtc
#define RTP_LENGTH 1400
#define RTP_PADDING 20
enum { MAX_LENGTH = RTP_LENGTH + RTP_PADDING};

class H264_RTP 
{
	//enum{ total_msize =2500 };
private:
	void * v_puser = NULL;
	unsigned int _timestamp_increse;
	unsigned int _ts_current;
	unsigned short _seq_num;
	unsigned char sendbuf[MAX_LENGTH];
	unsigned int v_ssrc = 1001;//the default value must hash("live/1001")
	rtp_callback _callback = nullptr;
protected:
	void SendPacket(unsigned char * buffer, int len);
	void Close();
public:
	H264_RTP();
	~H264_RTP(void);
	void SetSSRC(unsigned int ssrc)
	{
		v_ssrc = ssrc;
	}
	//use callback to send data
	void RegisterCallback(void *puser, rtp_callback callback,const char *livessrc);
	void send(const uint8_t* data, int dsize, uint32_t ts);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

RTP h264 实现

#define HASH_PRIME_MIDDLE 10001531
static int hash_add(const char* key, int prime)
{
	int hash, i;
	int len = (int)strlen(key);
	for (hash = len, i = 0; i < len; i++)
		hash += key[i];
	return (hash % prime);
}
H264_RTP::H264_RTP(void)
{
	_seq_num = 0;
	_ts_current = 0;

}

H264_RTP::~H264_RTP(void)
{

}
void H264_RTP::RegisterCallback(void *puser,rtp_callback callback, const char *livessrc)
{
	v_puser = puser;
	_callback = callback;
	v_ssrc = hash_add(livessrc, HASH_PRIME_MIDDLE);
}
void H264_RTP::Close()
{

}

void H264_RTP::SendPacket(unsigned char * buffer, int len)
{
	if (_callback != NULL)
	{
		_callback(v_puser, buffer, len);
	}
}

//小端转大端
uint32_t little2big(uint32_t le) {

	return (le & 0xff) << 24
		| (le & 0xff00) << 8
		| (le & 0xff0000) >> 8
		| (le >> 24) & 0xff;
}

//大端转小端
uint32_t big2little(uint32_t be)
{
	return ((be >> 24) & 0xff)
		| ((be >> 8) & 0xFF00)
		| ((be << 8) & 0xFF0000)
		| ((be << 24));
}
//小端转大端
uint16_t little2bigs(uint16_t num)
{
	uint16_t swapped = (num >> 8) | (num << 8);
	return swapped;
}

//大端转小端
uint16_t big2littles(uint16_t be)
{
	short swapped = (be << 8) | (be >> 8);
	return swapped;
}
void H264_RTP::send(const uint8_t * data,int len,uint32_t ts)
{   
	//	static
	_ts_current = ts;
	memset(sendbuf,0,12);
	RTP_FIXED_HEADER        *rtp_hdr;  //RTP头部
	NALU_HEADER		        *nalu_hdr; //NALU头部

	FU_INDICATOR	*fu_ind;
	FU_HEADER		*fu_hdr;


	rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0]; 

	rtp_hdr->payload     = H264;  
	rtp_hdr->version     = 2;  
	rtp_hdr->marker      = 0;   
	rtp_hdr->ssrc        = little2big(v_ssrc);
	//	printf("rtp seqnum=%d\n",_seq_num);

	char FirstByte       = data[0];
	int F                = FirstByte & 0x80; //1 bit
	int NRI              = FirstByte & 0x60; // 2 bit
	int TYPE             = FirstByte & 0x1f;// 5 bit
	//当一个NALU小于1400字节的时候,采用一个单RTP包发送
	if(len<=1400)
	{	
		//设置rtp M 位;
		//	DEBUG("n<1400");
		rtp_hdr->marker=1;
		//	printf("rtp seqnum=%d\n",_seq_num);
		rtp_hdr->seq_no     = little2bigs(_seq_num ++); 
		nalu_hdr =(NALU_HEADER*)&sendbuf[12]; 
		nalu_hdr->F=   F;
		nalu_hdr->NRI=NRI>>5;。
		nalu_hdr->TYPE=TYPE;

		unsigned char *nalu_payload=&sendbuf[13];
		memcpy(nalu_payload,data+1,len-1);

		rtp_hdr->timestamp= little2big(_ts_current);
		int bytes=len + 12 ;						
		SendPacket(sendbuf, bytes);
	}

	else if(len>1400)
	{
		//得到该nalu需要用多少长度为1400字节的RTP包来发送
		int k=0,l=0; 
		 pagenum = (len+1400-1)/1400
		k=len/1400;
		l=len%1400;
		int t=0;
		rtp_hdr->timestamp= little2big(_ts_current);
		while(t<=k)
		{
			if(!t)
			{
				rtp_hdr->marker=0;

				fu_ind =(FU_INDICATOR*)&sendbuf[12]; 
				fu_ind->F=F;
				fu_ind->NRI=NRI>>5;
				fu_ind->TYPE=28;
				//设置FU HEADER,并将这个HEADER填入sendbuf[13]
				fu_hdr =(FU_HEADER*)&sendbuf[13];
				fu_hdr->E=0;
				fu_hdr->R=0;
				fu_hdr->S=1;
				fu_hdr->TYPE=TYPE;
				rtp_hdr->seq_no = little2bigs(_seq_num ++);
				//	printf("head nalu\n");
				unsigned char *nalu_payload=&sendbuf[14];//同理将sendbuf[14]赋给nalu_payload
				memcpy(nalu_payload, data +1,1400);//去掉NALU头
				int bytes=1400+14;						
				SendPacket(sendbuf, bytes);
				t++;

			}
			else if(k==t)
			{
				rtp_hdr->marker=1;
				fu_ind =(FU_INDICATOR*)&sendbuf[12]; 
				fu_ind->F=F;
				fu_ind->NRI=NRI>>5;
				fu_ind->TYPE=28;

				//设置FU HEADER,并将这个HEADER填入sendbuf[13]
				fu_hdr =(FU_HEADER*)&sendbuf[13];
				fu_hdr->R=0;
				fu_hdr->S=0;
				fu_hdr->TYPE=TYPE;
				fu_hdr->E=1;
				if(l-1>=0)
				{

					rtp_hdr->seq_no = little2bigs(_seq_num ++);
					unsigned char *nalu_payload=&sendbuf[14];
					memcpy(nalu_payload, data +t*1400+1,l-1);
					int bytes = l+14-1;
					SendPacket(sendbuf, bytes);
				}

				t++;

			}
			else if(t<k&&0!=t)
			{

				rtp_hdr->marker=0;

				fu_ind =(FU_INDICATOR*)&sendbuf[12]; 
				fu_ind->F=F;
				fu_ind->NRI=NRI>>5;
				fu_ind->TYPE=28;

				//设置FU HEADER,并将这个HEADER填入sendbuf[13]
				fu_hdr =(FU_HEADER*)&sendbuf[13];
				//fu_hdr->E=0;
				fu_hdr->R=0;
				fu_hdr->S=0;
				fu_hdr->E=0;
				fu_hdr->TYPE=TYPE;
				rtp_hdr->seq_no = little2bigs(_seq_num ++);
				unsigned char *nalu_payload=&sendbuf[14];
				//	printf("length - %u\n",t*1400+1);
				memcpy(nalu_payload, data +t*1400+1,1400);
				int bytes=1400+14;						
				SendPacket(sendbuf, bytes);
				t++;
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203

以上使用h264,rtp,asio写的组播程序来描述协议,实际上,要写一个完整的代码,还是需要一定的时间的,如果读者有什么需求,可以随时和作者交流,未完待续。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/195115
推荐阅读
相关标签
  

闽ICP备14008679号