协议圣经

协议圣经一是基础,还没有写,先出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++来编写实例代码,这个相对简陋,有能力的可以使用任何界面,甚至跨个平台去编写,这里是示例,以方便调试展示为主。

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是,不用服务器就可以让跟多网内的人接收

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;
};

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 0unsigned char TYPE : 5;unsigned char NRI : 2;unsigned char F : 1;} NALU_HEADER; /**//* 1 BYTES */typedef struct {//byte 0unsigned char TYPE : 5;unsigned char NRI : 2;unsigned char F : 1;} FU_INDICATOR; /**//* 1 BYTES */typedef struct {//byte 0unsigned 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 datavoid 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 bitint NRI              = FirstByte & 0x60; // 2 bitint 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)/1400k=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_payloadmemcpy(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写的组播程序来描述协议,实际上,要写一个完整的代码,还是需要一定的时间的,如果读者有什么需求,可以随时和作者交流,未完待续。

协议圣经(二) RTP组播音视频技巧相关推荐

  1. RTP协议解析及H264/H265 音视频RTP打包分析

    一 概述 实时传输协议(Real-time Transport Protocol或简写RTP)是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的. RTP协议详 ...

  2. 怎样在线制作视频二维码?多种类型二维码一键生成技巧

    二维码作为现在一种常用的内容载体工具,可以用来储存不同的内容,比如文件.视频.音频.图片等等都可以生成二维码.那么视频文件是如何生成二维码的呢?下面教大家使用二维码生成器(二维码在线制作-二维码生成器 ...

  3. 基于MPEG-4和RTP的网络视频监控系统研究

    基于MPEG-4和RTP的网络视频监控系统研究 文/北京邮电大学通信网络综合技术研究所  龚猷龙 刘勇 摘  要:随着计算机.网络及多媒体通信技术的发展,视频监控在业界得到了广泛的应用,许多先进的技术 ...

  4. 一篇文章带你熟悉 TCP/IP 协议(网络协议篇二)

    涤生_Woo 2017年11月11日阅读 15544 关注 一篇文章带你熟悉 TCP/IP 协议(网络协议篇二) 同样的,本文篇幅也比较长,先来一张思维导图,带大家过一遍. 一图看完本文 一. 计算机 ...

  5. 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)

    RTP     参考文档 RFC3550/RFC3551     Real-time Transport Protocol)是用于Internet上针对多媒体数据流的一种传输层协议.RTP协议详细说明 ...

  6. Java做rtp解包封包_基于RTP的H视频数据打包解包类DoubleLi博客园.pdf

    基于RTP的H视频数据打包解包类DoubleLi博客园 15- 10-30 基于RTP的H264视频数据打包解包类 - DoubleLi - 博客园 DoubleLi 博客园 :: 首页 :: 博问 ...

  7. 流媒体传输协议系列之----RTP/RTCP协议解析

    https://blog.csdn.net/davidsguo008/article/details/73658422?utm_medium=distribute.pc_relevant_downlo ...

  8. 影像组学视频学习笔记(42)-影像组学特征提取问题解决过程复现、Li‘s have a solution and plan.

    作者:北欧森林 链接:https://www.jianshu.com/p/c3e6de2f79b3 来源:简书,已获转载授权 本笔记来源于B站Up主: 有Li 的影像组学系列教学视频 本节(42)主要 ...

  9. 趣谈网络协议笔记-二(第十六讲上)

    趣谈网络协议笔记-二(第十六讲上) 流媒体协议:如何在直播里看到美女帅哥? 自勉 给岁月以文明,而不是给文明以岁月!--<三体> 在触不到的獠牙上点火--就像不必仰望那星星就能够解决--就 ...

最新文章

  1. django 框架中应用 redis 集群
  2. ASP.NET Web API 特性
  3. python field readonly_Python serializers.ReadOnlyField方法代码示例
  4. Prometheus之kubernetes-sd自动发现
  5. 怎么解除airpods定位_华强北airpods
  6. 【数论想法题】小C的问题 @科林明伦杯哈尔滨理工大学第八届程序设计竞赛...
  7. zttp php,php常用插件
  8. 使用redis作为缓存,数据还需要存入数据库中吗?
  9. 转 lucene3搜索引擎,索引建立搜索排序分页高亮显示, IKAnalyzer分词
  10. utuntu 12.04 安装 配置 JAVA JDK
  11. python random 生成随机数
  12. qq刷屏代码可复制_QQ小程序「神奇字体」从注册到发布
  13. xmind怎样画流程图_【工作流程图】如何用xmind做流程图
  14. seo如何优化一个网站
  15. 韩国研发AI武器遭抵制,武器自带“头脑”将多可怕
  16. OSG中使用png图片显示透明效果
  17. 互联网金融数据分析应用
  18. ae软件安装计算机丢失,安装adobe ae等软件提示找不到msvcp110.dll、msvcp
  19. 海阳顶端网php,海阳顶端网的ASP木马的一个漏洞和利用
  20. 谨以此篇文章开启我的博客生涯

热门文章

  1. android 解压zip报错_Android 使用 Batterystats 和 Battery Historian 分析电池用量
  2. vivo X Fold跑分曝光:搭载骁龙8+12GB内存
  3. 疑似Redmi K40S工信部入网:主打双十一 最高支持120W快充
  4. 100家店干翻17000家药店!刘强东最恐惧的对手来了!
  5. 真降价还是假环保?华为客服回应手机取消充电器:不清楚
  6. 再延期!曝华为P50六月发布
  7. 京东大数据研究院:智能马桶四年销量翻10倍
  8. 格力:核心科技有时也是高利贷
  9. 黄光裕:力争用未来18个月的时间 使企业恢复原有的市场地位
  10. 倪飞曝腾讯红魔6更多细节:搭载业内顶级散热技术