基于RTP协议的H.264视频传输系统:实现
实现的原理:基于RTP协议的H.264视频传输系统:原理
相关文章:
【1】RTP协议分析
【2】jrtplib简介
【3】Qt调用jrtplib实现单播、多播和广播
【4】RTP 有效负载(载荷)类型,RTP Payload Type
【5】H.264(H264)视频文件的制作
【6】H.264格式分析
【7】H.264视频压缩标准
关于RTP Payload Format for H.264 Video一定要参考文档rfc6184,因为rfc3984已经被废弃了,rfc6184从下面两个链接都可以打开。
https://tools.ietf.org/html/rfc6184
https://datatracker.ietf.org/doc/rfc6184/?include_text=1
RTP负载为H.264定义了三种不同的基本的负载结构,接收端可能通过RTP负载的首字节来识别它们。这一个字节类似NALU头的格式,它的类型字段则指出了代表的是哪一种结构,这个字节的结构如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
Type定义如下:
0 没有定义
1-23 NAL单元 单个NAL单元包.
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
首字节的类型字段和H.264的NALU头中类型字段的区别是,当Type的值为24~31表示这是一个特别格式的NAL单元,而H.264中,只取1~23是有效的值。下面分别说明这三种负载结构。
一.Single NALU Packet(单一NAL单元模式)
即一个RTP负载仅由首字节和一个NALU负载组成,在本文中对于小于1400字节的NALU便采用这种打包方案。这种情况下首字节类型字段和原始的H.264的NALU头类型字段是一样的。也就是说,在这种情况下RTP的负载是一个完整的NALU。
二. Aggregation Packet(组合封包模式)
在一个RTP中封装多个NALU,对于较小的NALU可以采用这种打包方案,从而提高传输效率。即可能是由多个NALU组成一个RTP包。分别有4种组合方式,STAP-A、STAP-B、MTAP16和MTAP24。那么这里的RTP负载首字节类型值分别是24、25、26和27。本文未涉及这种模式。
三.Fragmentation Units(分片封包模式FUs)
一个NALU封装在多个RTP中,每个RTP负载由首字节(这里实际上是FU indicator,但是它和原首字节的结构一样,这里仍然称首字节)、FU header和NALU负载的一部分组成。在本文中,对于大于1400字节的NALU便采用这种方案进行拆包处理。存在两种类型FU-A和FU-B,类型值分别是28和29。
FU-A类型如下图所示:
本文使用的是FU-A类型。
FU-B类型如下图所示:
与FU-A相比,FU-B多了一个DON(decoding order number),DON使用的是网络字节序。FU-B只能用于隔行扫描封包模式,不能用于其他方面。
FU indicator字节结构如下所示:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
Type=28或29
FU header字节结构如下所示:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S(Start): 1 bit,当设置成1,该位指示分片NAL单元的开始。当随后的FU负载不是分片NAL单元的开始,该位设为0。
E(End): 1 bit,当设置成1, 该位指示分片NAL单元的结束,此时荷载的最后字节也是分片NAL单元的最后一个字节。当随后的FU荷载不是分片NAL单元的结束,该位设为0。
R(Reserved): 1 bit,保留位必须设置为0,且接收者必须忽略该位。
Type:与NALU头中的Type值相同
四.使用socket套接字发送RTP打包的H264
主要代码如下所示:
int main()
{OpenBitstreamFile("480320.264");NALU_t *n;char sendbuf[1500];unsigned short seq_num =0;int bytes=0;InitWinsock(); //初始化套接字库int sockfd;struct sockaddr_in addr_in;float framerate=25;unsigned int timestamp_increse=0,ts_current=0;timestamp_increse=(unsigned int)(90000.0 / framerate);addr_in.sin_family=AF_INET;addr_in.sin_port=htons(DEST_PORT);addr_in.sin_addr.s_addr=inet_addr(DEST_IP);sockfd=socket(AF_INET,SOCK_DGRAM,0);connect(sockfd, (const sockaddr *)&addr_in, sizeof(sockaddr_in)) ;//申请UDP套接字n = AllocNALU(8000000);//为结构体nalu_t及其成员buf分配空间。返回值为指向nalu_t存储空间的指针while(!feof(bits)){GetAnnexbNALU(n);//每执行一次,文件的指针指向本次找到的NALU的末尾,下一个位置即为下个NALU的起始码0x000001dump(n);//输出NALU长度和TYPEmemset(sendbuf,0,1500);//清空sendbuf;此时会将上次的时间戳清空,因此需要ts_current来保存上次的时间戳值//rtp固定包头,为12字节,该句将sendbuf[0]的地址赋给rtp_hdr,以后对rtp_hdr的写入操作将直接写入sendbuf。rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0];//设置RTP HEADERrtp_hdr->version = 2; //版本号,此版本固定为2rtp_hdr->marker = 0; //标志位,由具体协议规定其值。rtp_hdr->payload = H264;//负载类型号,rtp_hdr->ssrc = htonl(10);//随机指定为10,并且在本RTP会话中全局唯一//当一个NALU小于1400字节的时候,采用一个单RTP包发送if(n->len<=MAX_RTP_PKT_LENGTH){//设置rtp M 位;rtp_hdr->marker=1;rtp_hdr->seq_no = htons(seq_num ++); //序列号,每发送一个RTP包增1ts_current=ts_current+timestamp_increse;rtp_hdr->timestamp=htonl(ts_current);//设置NALU HEADER,并将这个HEADER填入sendbuf[12]nalu_hdr =(NALU_HEADER*)&sendbuf[12]; //将sendbuf[12]的地址赋给nalu_hdr,之后对nalu_hdr的写入就将写入sendbuf中;nalu_hdr->F=n->forbidden_bit;nalu_hdr->NRI=n->nal_reference_idc>>5;//有效数据在n->nal_reference_idc的第6,7位,需要右移5位才能将其值赋给nalu_hdr->NRI。nalu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[13],n->buf+1,n->len-1);//去掉nalu头的nalu剩余内容写入sendbuf[13]开始的字符串。bytes=n->len + 12 ; //获得sendbuf的长度,为nalu的长度(包含NALU头但除去起始前缀)加上rtp_header的固定长度12字节if(n->nal_unit_type==1 || n->nal_unit_type==5){send(sockfd,sendbuf,bytes,0);//发送RTP包}else{send(sockfd,sendbuf,bytes,0);//发送RTP包//如果是6,7类型的包,不应该延时;之前有停顿,原因这在这continue;}}else{int packetNum = n->len/MAX_RTP_PKT_LENGTH;if (n->len%MAX_RTP_PKT_LENGTH != 0)packetNum ++;int lastPackSize = n->len - (packetNum-1)*MAX_RTP_PKT_LENGTH;int packetIndex = 1 ;ts_current=ts_current+timestamp_increse;rtp_hdr->timestamp=htonl(ts_current);//发送第一个的FU,S=1,E=0,R=0rtp_hdr->seq_no = htons(seq_num ++); //序列号,每发送一个RTP包增1//设置rtp M 位;rtp_hdr->marker=0;//设置FU INDICATOR,并将这个HEADER填入sendbuf[12]fu_ind =(FU_INDICATOR*)&sendbuf[12]; //将sendbuf[12]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//使用FU-A//设置FU HEADER,并将这个HEADER填入sendbuf[13]fu_hdr =(FU_HEADER*)&sendbuf[13];fu_hdr->S=1;fu_hdr->E=0;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[14],n->buf+1,MAX_RTP_PKT_LENGTH);//去掉NALU头bytes=MAX_RTP_PKT_LENGTH+14;//获得sendbuf的长度,为nalu的长度(除去起始前缀和NALU头)加上rtp_header,fu_ind,fu_hdr的固定长度14字节send( sockfd, sendbuf, bytes, 0 );//发送RTP包//发送中间的FU,S=0,E=0,R=0for(packetIndex=2;packetIndex<packetNum;packetIndex++){rtp_hdr->seq_no = htons(seq_num ++); //序列号,每发送一个RTP包增1//设置rtp M 位;rtp_hdr->marker=0;//设置FU INDICATOR,并将这个HEADER填入sendbuf[12]fu_ind =(FU_INDICATOR*)&sendbuf[12]; //将sendbuf[12]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//设置FU HEADER,并将这个HEADER填入sendbuf[13]fu_hdr =(FU_HEADER*)&sendbuf[13];fu_hdr->S=0;fu_hdr->E=0;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[14],n->buf+(packetIndex-1)*MAX_RTP_PKT_LENGTH+1,MAX_RTP_PKT_LENGTH);//去掉起始前缀的nalu剩余内容写入sendbuf[14]开始的字符串。bytes=MAX_RTP_PKT_LENGTH+14;//获得sendbuf的长度,为nalu的长度(除去原NALU头)加上rtp_header,fu_ind,fu_hdr的固定长度14字节send( sockfd, sendbuf, bytes, 0 );//发送rtp包}//发送最后一个的FU,S=0,E=1,R=0rtp_hdr->seq_no = htons(seq_num ++);//设置rtp M 位;当前传输的是最后一个分片时该位置1rtp_hdr->marker=1;//设置FU INDICATOR,并将这个HEADER填入sendbuf[12]fu_ind =(FU_INDICATOR*)&sendbuf[12]; //将sendbuf[12]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//设置FU HEADER,并将这个HEADER填入sendbuf[13]fu_hdr =(FU_HEADER*)&sendbuf[13];fu_hdr->S=0;fu_hdr->E=1;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[14],n->buf+(packetIndex-1)*MAX_RTP_PKT_LENGTH+1,lastPackSize-1);//将nalu最后剩余的-1(去掉了一个字节的NALU头)字节内容写入sendbuf[14]开始的字符串。bytes=lastPackSize-1+14;//获得sendbuf的长度,为剩余nalu的长度减1加上rtp_header,FU_INDICATOR,FU_HEADER三个包头共14字节send( sockfd, sendbuf, bytes, 0 );//发送rtp包}Sleep(40);}FreeNALU(n);
#ifdef WIN32WSACleanup();
#endif // WIN32return 0;
}
五.使用jrtplib发送RTP打包的H264
使用jrtplib的好处是,不需要自己再添加RTP头了,而且jrtplib内部实现了RTCP协议。jrtplib的简介可以参考【2】,使用方法可以参考【3】。
主要代码如下所示。
int main()
{//初始化jrtplibuint8_t destIP[]={127,0,0,1};initialRTP(destIP);//打开264文件OpenBitstreamFile("480320.264");NALU_t *n;char sendbuf[1500];int bytes=0;n = AllocNALU(8000000);//为结构体nalu_t及其成员buf分配空间。返回值为指向nalu_t存储空间的指针while(!feof(bits)){GetAnnexbNALU(n);//每执行一次,文件的指针指向本次找到的NALU的末尾,下一个位置即为下个NALU的起始码0x000001dump(n);//输出NALU长度和TYPEmemset(sendbuf,0,1500);//清空sendbuf//当一个NALU小于1400字节的时候,采用一个单RTP包发送if(n->len<=MAX_RTP_PKT_LENGTH){//设置NALU HEADER,并将这个HEADER填入sendbuf[0]nalu_hdr =(NALU_HEADER*)&sendbuf[0]; //将sendbuf[0]的地址赋给nalu_hdr,之后对nalu_hdr的写入就将写入sendbuf中;nalu_hdr->F=n->forbidden_bit;nalu_hdr->NRI=n->nal_reference_idc>>5;//有效数据在n->nal_reference_idc的第6,7位,需要右移5位才能将其值赋给nalu_hdr->NRI。nalu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[1],n->buf+1,n->len-1);//去掉nalu头的nalu剩余内容写入sendbuf[1]开始的字符串。bytes=n->len;if(n->nal_unit_type==1 || n->nal_unit_type==5){//第三参数Mark即RTP头中的M位int status = m_session.SendPacket((void *)sendbuf,bytes,96,true,3600);if (status < 0){std::cout<<RTPGetErrorString(status)<<std::endl;}}else{//注意此处与使用socekt发送的不同,此时时间戳不增加int status = m_session.SendPacket((void *)sendbuf,bytes,96,true,0);if (status < 0){std::cout<<RTPGetErrorString(status)<<std::endl;}//如果是6,7类型的包,不应该延时;之前有停顿,原因这在这continue;}}else{int packetNum = n->len/MAX_RTP_PKT_LENGTH;if (n->len%MAX_RTP_PKT_LENGTH != 0)packetNum ++;int lastPackSize = n->len - (packetNum-1)*MAX_RTP_PKT_LENGTH;int packetIndex = 1 ;//发送第一个的FU,S=1,E=0,R=0fu_ind =(FU_INDICATOR*)&sendbuf[0]; //将sendbuf[0]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//使用FU-A//设置FU HEADER,并将这个HEADER填入sendbuf[1]fu_hdr =(FU_HEADER*)&sendbuf[1];fu_hdr->S=1;fu_hdr->E=0;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[2],n->buf+1,MAX_RTP_PKT_LENGTH);//去掉NALU头bytes=MAX_RTP_PKT_LENGTH+2;//获得sendbuf的长度,为nalu的长度(除去起始前缀和NALU头)加fu_ind,fu_hdr的固定长度2字节int status = m_session.SendPacket((void *)sendbuf,bytes,96,false,0);if (status < 0){std::cout<<RTPGetErrorString(status)<<std::endl;}//发送中间的FU,S=0,E=0,R=0for(packetIndex=2;packetIndex<packetNum;packetIndex++){//设置FU INDICATOR,并将这个HEADER填入sendbuf[0]fu_ind =(FU_INDICATOR*)&sendbuf[0]; //将sendbuf[0]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//设置FU HEADER,并将这个HEADER填入sendbuf[1]fu_hdr =(FU_HEADER*)&sendbuf[1];fu_hdr->S=0;fu_hdr->E=0;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[2],n->buf+(packetIndex-1)*MAX_RTP_PKT_LENGTH+1,MAX_RTP_PKT_LENGTH);//去掉起始前缀的nalu剩余内容写入sendbuf[24]开始的字符串。bytes=MAX_RTP_PKT_LENGTH+2;//获得sendbuf的长度,为nalu的长度(除去原NALU头)加上fu_ind,fu_hdr的固定长度2字节int status = m_session.SendPacket((void *)sendbuf,bytes,96,false,0);if (status < 0){std::cout<<RTPGetErrorString(status)<<std::endl;}}//设置FU INDICATOR,并将这个HEADER填入sendbuf[0]fu_ind =(FU_INDICATOR*)&sendbuf[0]; //将sendbuf[0]的地址赋给fu_ind,之后对fu_ind的写入就将写入sendbuf中;fu_ind->F=n->forbidden_bit;fu_ind->NRI=n->nal_reference_idc>>5;fu_ind->TYPE=28;//设置FU HEADER,并将这个HEADER填入sendbuf[1]fu_hdr =(FU_HEADER*)&sendbuf[1];fu_hdr->S=0;fu_hdr->E=1;fu_hdr->R=0;fu_hdr->TYPE=n->nal_unit_type;memcpy(&sendbuf[2],n->buf+(packetIndex-1)*MAX_RTP_PKT_LENGTH+1,lastPackSize-1);//将nalu最后剩余的-1(去掉了一个字节的NALU头)字节内容写入sendbuf[2]开始的字符串。bytes=lastPackSize-1+2;//获得sendbuf的长度,为剩余nalu的长度减1加上FU_INDICATOR,FU_HEADER两个包头共2字节status = m_session.SendPacket((void *)sendbuf,bytes,96,true,3600);if (status < 0){std::cout<<RTPGetErrorString(status)<<std::endl;}}Sleep(40);}FreeNALU(n);m_session.BYEDestroy(RTPTime(10,0),0,0);
#ifdef WIN32WSACleanup();
#endif // WIN32return 0;
}
六.测试
上述两种方法的测试效果相同。打开VLC或者MPlayer播放器(强烈推荐MPlayer,效果比VLC好),将w.sdp拖入到播放器中,播放器进入等待状态,然后运行程序发送RTP流,播放器开始播放。
测试使用了176144.264和480320.264两个H.264文件,第一个分辨率为176*144,NALU比较小,打包时不需要分段;第二个分辨率480*320,NALU大于1400,固存在分段打包的情况。
w.sdp
m=video 25000 RTP/AVP 96
a=rtpmap:96 H264
a=framerate:25
c=IN IP4 127.0.0.1
注意端口号25000要和程序中设置的相同。
测试效果如下所示:
七.小结
关于时间戳,需要注意的是h264的采样率为90000HZ,因此时间戳的单位为1(秒)/90000,因此如果当前视频帧率为25fps,那时间戳间隔或者说增量应该为3600,如果帧率为30fps,则增量为3000,以此类推。
关于h264拆包,按照FU-A方式说明:
1.第一个FU-A包的FU indicator:F应该为当前NALU头的F,而NRI应该为当前NALU头的NRI,Type则等于28,表明它是FU-A包。FU header生成方法:S = 1,E = 0,R = 0,Type则等于NALU头中的Type。
2.后续的N个FU-A包的FU indicator和第一个是完全一样的,如果不是最后一个包,则FU header应该为:S = 0,E = 0,R = 0,Type等于NALU头中的Type。
3.最后一个FU-A包FU header应该为:S = 0,E = 1,R = 0,Type等于NALU头中的Type。
因此总结就是:同一个NALU分包厚的FU indicator头是完全一致的,FU header只有S以及E位有区别,分别标记开始和结束,它们的RTP分包的序列号应该是依次递增的,并且它们的时间戳必须一致,而负载数据为NALU包去掉1个字节的NALU头后对剩余数据的拆分,这点很关键,你可以认为NALU头被拆分成了FU indicator和FU header,所以不再需要1字节的NALU头了。
关于SPS以及PPS,配置帧的传输我采用了先发SPS,再发送PPS,并使用同样的时间戳,或者按照正常时间戳增量再或者组包发送的形式处理貌似都可以,看播放器怎么解码了,另外提一下,如果我们使用vlc进行播放的话,可以在sdp文件中设置SPS以及PPS,这样就可以不用发送它们了。
使用VLC播放时,sdp文件中的分包模式选项:packetization-mode=1,即在文件末尾加入=fmtp:packetization-mode=1,否则有问题。
关于packetization-mode的详细说明如下:
当packetization-mode的值为0时或不存在时, 必须使用单一NALU单元模式。
当packetization-mode的值为1时必须使用非交错(non-interleaved)封包模式。
当packetization-mode的值为2时必须使用交错(interleaved)封包模式。
每个打包方式允许的NAL单元类型总结(yes = 允许, no = 不允许, ig = 忽略)
Type Packet Single NAL Non-Interleaved Interleaved
Unit Mode Mode Mode
-------------------------------------------------------------
0 undefined ig ig ig
1-23 NAL unit yes yes no
24 STAP-A no yes no
25 STAP-B no no yes
26 MTAP16 no no yes
27 MTAP24 no no yes
28 FU-A no yes yes
29 FU-B no no yes
30-31 undefined ig ig ig
源码链接:见http://blog.csdn.net/caoshangpa/article/details/53009604的评论
原创不易,转载请标明出处:https://blog.csdn.net/caoshangpa/article/details/53009604
基于RTP协议的H.264视频传输系统:实现相关推荐
- 基于RTP协议的H.264视频传输系统:原理
1.引言 随着信息产业的发展,人们对信息资源的要求已经逐渐由文字和图片过渡到音频和视频,并越来越强调获取资源的实时性和互动性.但人们又面临着另外一种不可避免的尴尬,就是在网络上看到生动清晰的 ...
- 实现RTP协议的H.264视频传输系统
1. 引言 随着信息产业的发展,人们对信息资源的要求已经逐渐由文字和图片过渡到音频和视频,并越来越强调获取资源的实时性和互动性.但人们又面临着另外一种不可避免的尴尬,就是在网络上看到生 ...
- 基于RTP的h.264视频传输系统(二)
Live555 是一个为跨平台的C++开源项目,它实现了RTP/RTCP.RTSP.SIP等的支持.并且相对于其他的流媒体服务器是完全开源并且免费的. 废话不多说,下面开始. http://blog. ...
- RTP协议介绍以及C语言实现具有发送H.264视频功能的RTP服务器
RTP封装H.264视频规范以及C语言实现 以前上学时间做嵌入式开发板Hi3516A的流媒体项目,现在又突然想起来,不想学过就忘了浪费了,所以又自己实现了一遍读取本地视频文件发送RTP视频流的程序,算 ...
- H.264视频的RTP有效负载格式 (RFC-3984)
RFC文档链接 本备忘录的状态 略 摘要 本备忘录描述了ITU-T建议的H.264视频编解码器和技术上相同的ISO/IEC国际标准14496-10视频编解码器的RTP有效载荷格式.RTP有效载荷格式允 ...
- H.264 视频的 RTP 载荷格式
本文是 IETF 的规范 RFC 6184 的一部分的翻译,该规范 地址.翻译这份文档,主要是为了编写一段用 RTP 传输 H.264 流的代码.本想在网上找一些文章完成任务了事的,但由于个人之前音视 ...
- C++实现RTMP协议发送H.264编码及AAC编码的音视频
C++实现RTMP协议发送H.264编码及AAC编码的音视频 RTMP(Real Time Messaging Protocol)是专门用来传输音视频数据的流媒体协议,最初由Macromedia 公司 ...
- RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播
RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播 摘要: RTMP协议发送H.264编码及AAC编码的音视频,实现摄像头直播 RTMP(Real Time Messaging Pro ...
- H.264 RTPpayload 格式------ H.264 视频 RTP 负载格式
H.264 RTPpayload 格式------ H.264 视频 RTP 负载格式 1. 网络抽象层单元类型 (NALU) NALU 头由一个字节组成, 它的语法如下: +------------ ...
最新文章
- 迪杰斯特拉算法c++_《算法图解》学习记录7--迪杰斯特拉算法
- fr4速度 微带线_【射频笔记5】传输线理论基础
- WinCE文件目录定制及内存调整
- [题解] [AHOI2009] 跳棋
- NYOJ_23_取石子(一)
- haproxy之安装与配置详解
- linux centOS可视化界面
- 【Paper】2015_Coordinated cruise control for high-speed train movements based on a multi-agent model
- 项管:配置管理、变更管理、文档管理、知识管理及其他
- 电影《功夫熊猫3》中的管理知识
- 当Analyzer 2007 遇上.Net 3.0时,可能会秀才爱上兵
- 好友返利网站开发 一
- 印象笔记,为知笔记和Effie哪个更适合商业机构提案人员?
- 仿淘宝商品界面(html div+css)
- matlab牛顿法求区间根程序,MATLAB用二分法、不动点迭代法及Newton迭代(切线)法求非线性方程的根...
- 从Word中批量提取数据到Excel中,Word导出到Excel的利器
- 建网站(为什么租服务器和购买域名)
- 2020年中国热交换器行业分类、发展历程、现状及主要生产厂商分析「图」
- VS+Qt无法打开源文件QWidget
- Timer定时器应用小例子
热门文章
- 「二分类算法」提供银行精准营销解决方案详解(随机森林)
- 大数据支持银行试水精准营销
- 视频号给谁带来价值?
- 怎样将pdf转换成jpg格式
- 计算机毕业设计Java“花园街道”社区医院服务系统(源码+系统+mysql数据库+lw文档)
- Final Cut Pro for Mac(中文fcpx视频剪辑)
- 数据结构——平衡二叉树(AVL树)之插入
- 提高网速软件测试工程师,提供3个立刻提高网速的方法
- 云服务器 那个便宜亿点点
- sae php 短信,中国电信翼聊短信PHP发送类实现详细代码