前段时间写完了RTMP的直播方案,因为是基于librtmp的库来实现的,所以比较简单。之后花了一个月吧,参照海思的rtsp推流框架,慢慢的写了一个基于RealTek为底层的网络摄像头Rtsp直播功能的demo。这个不带任何库,纯C写的推流功能,学到了蛮多东西的,都写下来以后忘了还能回来看看,同时也希望给刚刚起步做rtsp直播的小伙伴一点参考。
一时间也不知道从什么地方讲起,我还是顺着我的代码一步一步讲吧。首先要确定一个事情就是,在网络摄像头RTSP直播的方案中,摄像头是作为服务器端的,连接摄像头请求码流数据的都是客户端。

int main(int argc, char* argv[])
{int s32MainFd;pthread_t framesource_id;struct timespec ts = { 0, 200000000 };ringmalloc(720*576);//64个大小为720*576的环形缓冲区printf("RTSP server START\n");PrefsInit();//设置服务器信息全局变量 端口等printf("listen for client connecting...\n");signal(SIGINT, IntHandl);//捕捉信号用来终止程序s32MainFd = tcp_listen(SERVER_RTSP_PORT_DEFAULT);//554if (ScheduleInit() == ERR_FATAL)//推送视频数据流线程入口{fprintf(stderr,"Fatal: Can't start scheduler %s, %i \nServer is aborting.\n", __FILE__, __LINE__);return 0;}RTP_port_pool_init(RTP_DEFAULT_PORT);//初始化端口集合,用以给多个RTP,RTCP分配端口pthread_create(&framesource_id,NULL,FRAME_SOURCE_THREAD,NULL);//底层数据存取入口while (!g_s32Quit){nanosleep(&ts, NULL);EventLoop(s32MainFd);//RTSP连接处理函数入口}sleep(2);Camera_free();ringfree();//缓冲区释放printf("The Server quit!\n");return 0;
}

这个是我的主函数,无关紧要的东西不说了,说一些重要的部分,第一个引出要说的就是Socket。

Socket

socket据我的了解,其实就是一个接口,它屏蔽了复杂的TCP/IP协议族,合理使用这些接口就可以完成基于TCP/UDP的数据传输。

上图是TCP通讯的步骤,编程按照这个模型来就行了。下面放一下各个函数的简单介绍。

1. socket()函数创建套接字

int socket(int af, int type, int protocol);
  1. af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B
  2. type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM
  3. protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

2.bind()和connect()函数
socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;而客户端要用 connect() 函数建立连接。

bind() 函数的原型为:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);

sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。下面的代码与本机IP,指定端口进行绑定

   /*创建套接字*/if((f = socket(AF_INET, SOCK_STREAM, 0))<0)//TCP{fprintf(stderr, "socket() error in tcp_listen.\n");return -1;}/*设置socket的可选参数*/setsockopt(f, SOL_SOCKET, SO_REUSEADDR, (char *) &v, sizeof(int));s.sin_family = AF_INET;//使用IPv4地址s.sin_addr.s_addr = BigLittleSwap32(INADDR_ANY);//具体的IP地址(本机所有IP)s.sin_port = BigLittleSwap16(port);//端口/*绑定socket*/if(bind(f, (struct sockaddr *)&s, sizeof(s))){fprintf(stderr, "bind() error in tcp_listen");return -1;}

connect() 函数
connect() 函数用来建立连接,它的原型为:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

3.listen()和accept()函数
对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。
listen() 函数
通过 listen() 函数可以让套接字进入被动监听状态,它的原型为:

int listen(int sock, int backlog);

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

请求队列
当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。

如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。

当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误

注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。
accept() 函数
当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

4. Socket 服务端与客户端数据交互方式
Socket 的服务端和客户端的数据交互是通过读写缓冲区来完成的。
如图可以看到通过write()send()函数写缓冲区,read()recv()函数读缓冲区。而数据何时发送是不受程序员控制的,这取决于当时的网络情况、当前线程是否空闲等诸多因素。我们只要把要写的数据写入缓冲区,从缓冲区里读取我们想要的数据就行了。

注意这里有两种情况,一种是阻塞模式(默认)
在向缓冲区写数据时,如果缓冲区剩余空间长度小于你要写入的数据长度,那么写入操作就会被阻塞(暂停执行),直到缓冲区数据发送出去了有足够的空间后会唤醒写入操作。
TCP在进行网络传输数据时,输入缓冲区也会被锁定无法写入数据,直到发送完成解锁缓冲区。
在接收方,会检测缓冲区,如果有数据,就会取数据,如果没有,程序会被阻塞,直到有数据之后才会往下跑。
如果要取的数据长度小于缓冲区中有效数据的长度,也会被阻塞,直到缓冲区中数据积累达到要取的数据长度后才会读取返回。

非阻塞模式
非阻塞模式要配合select()函数一起使用。

int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

对于fd_set类型通过下面四个宏来操作:
FD_ZERO(fd_set *fdset) 将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET(fd_set *fdset) 用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset) 用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset) 用于测试指定的文件描述符是否在该集合中。

理解select的用法主要是要理解fd_set,这是一个套接字集合(socket创建的那个套接字),为了说明方便,假设取fd_set长度为一字节就是8位,这个8位的套接字集合fd_set每一位都可以表示一个套接字,这个套接字集合就可以对应8个套接字了。

  1. 执行FD_ZERO(fd_set *fdset)将套接字集合清空,即8位为0000 0000
  2. 如果要加入的套接字fd = 5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
  3. 若再加入fd=2,fd=1,调用FD_SET(fd,&set);后则set变为0001,0011
  4. 执行select(6,&set,0,0,timeout)(注意最后的struct timeval *timeout这个参数如果设置是NULL的话就是阻塞等待)
  5. 若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空即第五位被清空。
  6. 调用FD_ISSET(int fd,fd_set *fdset)确定指定的套接字是否被清空,如果关心的fd为2则这个函数的作用就是查看fd_set的第二位是否被清空的,如果没清空则返回非零值,所以这个可以拿来做判断条件。若是返回非零值即是这个套接字对应的缓冲区有数据可读,就可以调用read()/recv()函数进行接收。

一般的实现过程是:

fd_set rset;
struct timeval t;
FD_ZERO(&rset);
t.tv_sec=0;                /*select 时间间隔*/
t.tv_usec=100000;
FD_SET(rtsp->fd,&rset);/*调用select等待对应描述符变化*/
if (select(g_s32Maxfd+1,&rset,0,0,&t)<0)
{fprintf(stderr,"select error %s %d\n", __FILE__, __LINE__);send_reply(500, NULL, rtsp);return ERR_GENERIC; //errore interno al server
}
/*有可供读进的rtsp包*/if (FD_ISSET(rtsp->fd,&rset)){recv(s,...);    }

OK,Socket这部分讲完,先告一段落,下一部分讲下RTSP的交互过程,因为RTSP是TCP/IP协议体系中的一个应用层协议,所以我先讲讲socket通讯,打个基础。

网络摄像头Rtsp直播方案(一)相关推荐

  1. 网络摄像头RTSP直播方案(三)

    前面的部分讲了关于RTSP连接的交互过程,在RTSP推流的过程中,RTSP协议只是做一个控制作用,底层真正进行传输的流媒体协议还是RTP协议.做这一部分主要是要先了解RTP协议的封装格式,这里我不详细 ...

  2. 网络摄像头Rtsp直播方案(二)

    上一部分说了Socket通讯的一些东西,这部分就结合代码来说说RTSP交互的过程. 在放实现代码之前先说说概念上的东西吧,关于RTSP这个流媒体网络协议.RTSP作为一个应用层协议,提供了一个可供扩展 ...

  3. ffmpeg api推流,谷歌浏览器播放大华、海康威视网络摄像头rtsp视频流方案(hls、m3u8、flv、webrtc、srs、nginx、nginx-rtmp、rtmp)比较

    ffmpeg api推流,谷歌浏览器播放大华.海康威视网络摄像头rtsp视频流方案(hls.m3u8.flv.webrtc.srs.nginx.nginx-rtmp.rtmp)比较 将网络摄像头视频流 ...

  4. 摄像头网页服务器,网络摄像头实现直播的方法 在网页浏览器播放等于可以在网页传播...

    网络摄像头实现直播的方法,可以在网页浏览器播放,可以发送给你的朋友,可以放到你的官网去增加一条播放链接,可以在网页文章里增加一条播放链接.怎么实现呢? 需要的准备如下: 1.网络摄像头一个 2.电脑一 ...

  5. 安防摄像头互联网直播方案LiveGBS设计文档

    LiveGBS设计文档 一.介绍 28181协议全称为GB/T28181<安全防范视频监控联网系统信息传输.交换.控制技术要求>,是由公安部科技信息化局提出,由全国安全防范报警系统标准化技 ...

  6. 网络摄像头RTSP视频流WEB端实时播放实现方案

    IPC视频流怎么实时在WEB浏览器播放,视频流格式是RTSP. 下面我整理了自己实现的方案以及网上看到的一些方案 一.FFmpeg + nginx 将转 hls 通过 video.js 在支持h5浏览 ...

  7. 网络摄像头RTSP拉流协议网页无插件视频直播平台EasyNVR为什么无法获取通道接口数据?

    TSINGSEE青犀视频的技术支持最近给我反馈了一个问题,关于代理EasyNVR获取通道接口返回为空的问题.代理EasyNVR的过程也是将EasyNVR集成进其他平台的过程,这个问题在集成过程中还是比 ...

  8. 用ffmpeg+nginx+海康威视网络摄像头rtsp在手机端和电脑端实现直播

    原料:海康威视摄像头,nginx服务器,ffmpeg. 首先海康威视摄像头, 它的rtsp数据流的地址为:rtsp://[username]:[password]@[ip]:[port]/[codec ...

  9. 海康网络摄像头rtsp转hls生成 m3u8,浏览器直播播放。

    1.ffmpeg ffmpeg 关于hls方面的指令说明: -hls_time n: 设置每片的长度,默认值为2.单位为秒 -hls_list_size n:设置播放列表保存的最多条目,设置为0会保存 ...

最新文章

  1. python【力扣LeetCode算法题库】460- LFU缓存
  2. python struct.calcsize()函数(返回格式字符串fmt描述的结构的字节大小)
  3. 非刚性人脸跟踪 —— 实用工具
  4. 图形系统中的仿射变换
  5. 手把手教你用EVO工具评估SLAM数据集TUM、KITTI、EuRoC(附代码)
  6. win7下 apache2.2 +php5.4 环境搭建
  7. postman连接mysql执行操作
  8. jsp文字上下居中显示_jsp中怎样让文字居中 ?
  9. Docker中常用的命令
  10. keras实现DeepDream
  11. 多因素方差分析:自由度
  12. 阿里春招Android面经
  13. Matlab程序——修正鲍威尔Powell法
  14. 创建和管理图书管理系统数据库
  15. 4.1.5 消费者获取记录
  16. mac为什么不支持ntfs,mac读取ntfs移动硬盘软件有哪些
  17. 零知识证明(zero-knowledge proof)
  18. 如何区分2G/3G/4G基站
  19. 汽车销售Spark数据处理和数据分析项目实战Dataframe
  20. idea前进和后退光标位置的快捷键和设置按钮的方法

热门文章

  1. 目标拦截问题—微分对策
  2. 计算机中的cad是什么意思是,cad是什么意思 cad是什么软件
  3. 学习VTK9笔记(三)打开stl文件
  4. C语言规定 在一个源程序中 main函数,C语言规定:在一个源程序中,main函数的位置()A.必须在程序的最开始B.必须在系统调用的库函数的_搜题易...
  5. 史上讲解最好的 Docker 教程,从入门到精通(建议收藏的教程)
  6. PHP实现附近的人、按距离排序之Redis GEO方案
  7. Matlab之在城市环境中基于动态占用网格图的的运动规划仿真(附源码)
  8. iOS Code Signing 学习笔记转写
  9. semantic_slam环境配置
  10. NIO核心设计与原理