赞
踩
协议圣经一是基础,还没有写,先出2
RTP为半应用层,半传输层协议,可以使用tcp,也可以使用udp,组播为D类地址,为何要使用组播,组播对什么有效,如224.3.4.5,端口选择一个9200,组播的好处是:
1 由交换机和路由器来确定发包
2 不用服务器转发
3 在udp基础上发送方便
缺点:不能跨网,不能到外网上去组播,也就是一旦确定组播方式,一般也就确定了是局域网程序,这里说一般,是因为是有mbone这种internet组播的,但是是实验爱好者组成的网,并不适合实际情况。
h264编码和h265编码使用libx264 libx265 或者直接ffmpeg来编码,ffmpeg作为世界一等一的开源项目,封装了各类硬件和软件音视频编码和解码。
aac,opus 相对视频简单,不用分包,都小于MTU的大小
使用MFC c++来编写实例代码,这个相对简陋,有能力的可以使用任何界面,甚至跨个平台去编写,这里是示例,以方便调试展示为主。
使用directshow去采集音频和视频,这个在windows上已经成熟彻底,也可以使用桌面来作为视频源,这个不做要求,主要是讲原理
设置相对简单,后期示例需要加上窗口来写入码率,帧率设置,音频的一些设置
其实播放是比较难做的,但是我们并不是为了制作播放器,我们是为了说明协议的发送,所以播放我们使用了一个非常简单的方法,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是,不用服务器就可以让跟多网内的人接收
m=video 9200 RTP/AVP 96 指明视频端口9200
a= framerate:20 指明帧率20
c=IN IP4 234.5.6.7 指明组播地址234.5.6.7
毋庸置疑,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; };
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); };
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++; } } } }
以上使用h264,rtp,asio写的组播程序来描述协议,实际上,要写一个完整的代码,还是需要一定的时间的,如果读者有什么需求,可以随时和作者交流,未完待续。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。