监控视频采集与Web直播开发全流程分析
内容概要:
摄像头 => FFmpeg => Nginx服务器 => 浏览器
- 从摄像头拉取rtsp流
- 转码成rtmp流向推流服务器写入
- 利用html5播放
1.开发流程
1.1 通过FFmpeg视频采集和转码
在音视频处理领域,FFmpeg基本是一种通用的解决方案。虽然作为测试我们也可以借助OBS等其他工具,但是为了更接近项目实战我们采用前者。这里不会专门介绍如何使用FFmpeg,只提供演示代码。不熟悉FFmpeg的同学可以跳过这个部分直接使用工具推流,网上的资料很多请自行查阅。
// 注册解码器和初始化网络模块
av_register_all();
avformat_network_init(); char errorbuf[] = { }; // 异常信息
int errorcode = ; // 异常代码
AVFormatContext *ic = NULL; // 输入封装上下文
AVFormatContext *oc = NULL; // 输出封装上下文 char *inUrl = "rtsp://admin:SYhr_5000@192.168.8.107:554/H264"; // rtsp输入URL
char *outUrl = "rtmp://192.168.1.118/rtmp_live/1"; // rtmp输出URL AVDictionary *opts = NULL;
av_dict_set(&opts, "max_delay", "", );
av_dict_set(&opts, "rtsp_transport", "tcp", ); errorcode = avformat_open_input(&ic, inUrl, NULL, &opts);
if (errorcode != ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
} errorcode = avformat_find_stream_info(ic, NULL);
if (errorcode < ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
}
av_dump_format(ic, , inUrl, ); // 定义输出封装格式为FLV
errorcode = avformat_alloc_output_context2(&oc, NULL, "flv", outUrl);
if (!oc) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
}
// 遍历流信息初始化输出流
for (int i = ; i < ic->nb_streams; ++i) {
AVStream *os = avformat_new_stream(oc, ic->streams[i]->codec->codec);
if (!os) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
}
errorcode = avcodec_parameters_copy(os->codecpar, ic->streams[i]->codecpar);
if (errorcode != ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
}
os->codec->codec_tag = ;
}
av_dump_format(oc, , outUrl, ); errorcode = avio_open(&oc->pb, outUrl, AVIO_FLAG_WRITE);
if (errorcode < ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
}
errorcode = avformat_write_header(oc, NULL);
if (errorcode < ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
return -;
} AVPacket pkt; // 获取时间基数
AVRational itb = ic->streams[]->time_base;
AVRational otb = oc->streams[]->time_base;
while (true) {
errorcode = av_read_frame(ic, &pkt);
if (pkt.size <= ) {
continue;
}
// 重新计算AVPacket的时间基数
pkt.pts = av_rescale_q_rnd(pkt.pts, itb, otb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, itb, otb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q_rnd(pkt.duration, itb, otb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.pos = -;
errorcode = av_interleaved_write_frame(oc, &pkt);
if (errorcode < ) {
av_strerror(errorcode, errorbuf, sizeof(errorbuf));
cout << errorbuf << endl;
continue;
}
}
代码中的输入和输出URL替换为实际地址,上面的代码并没有做任何编码和解码的操作,只是把从摄像头读取到的AVPacket做了一次转封装并根据time_base重新计算了一下pts和dts。但是在实际运用中由于网络传输和带宽的限制,我们可能会对原始视频流做降率处理,这样就必须要加入解码编码的过程。
1.2 推流服务器配置
开源的直播软件解决方案有SRS(Simple-RTMP-Server)和nginx-rtmp-module,前者是国人发起的一个优秀的开源项目,目前国内很多公司都使用它作为直播解决方案,由C++编写;后者依赖Nginx,以第三方模块的方式提供直播功能,由C编写。资料显示SRS的负载效率和直播效果优于nginx-rtmp-module,并且后者已经有一年没有做任何更新了。不过考虑到实际需求我还是决定使用nginx-rtmp-module,并且为了方便后期与Web集成,我们使用基于它开发的nginx-http-flv-module。关于nginx-http-flv-module的内容大家可以访问《基于nginx-rtmp-module模块实现的HTTP-FLV直播模块nginx-http-flv-module》,安装和配置说明访问他的GitHub与中文说明,与nginx-rtmp-module有关的配置说明推荐访问官方wiki,当然Nginx下载的官方网址我也直接提供了吧。
下面跳过安装直接配置nginx.conf
#user nobody;
worker_processes 1; #error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info; #pid logs/nginx.pid; events {
worker_connections 1024;
}
rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp; rtmp {
timeout 10s;
out_queue 4096;
out_cork 8; log_interval 5s;
log_size 1m; server {
listen 1935;
chunk_size 4096;
application rtmp_live {
live on;
gop_cache on;
}
}
} http {
include mime.types;
default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on;
#tcp_nopush on; #keepalive_timeout 0;
keepalive_timeout 65; #gzip on; server {
listen 80;
server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / {
root html;
index index.html index.htm;
} location /http_live {
flv_live on;
chunked_transfer_encoding on;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
} #error_page 404 /404.html; # redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
} # proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#} # deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
} # another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias; # location / {
# root html;
# index index.html index.htm;
# }
#} # HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost; # ssl_certificate cert.pem;
# ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on; # location / {
# root html;
# index index.html index.htm;
# }
#} }
nginx.conf
我们要关注的重点是gop_cache,具体后面会解释。完成以后如果没有其他问题,我们推流服务器就可以使用了。
1.3 Web框架
这里我采用了Angular和flv.js的集成方案,具体的使用其实也很简单。通过npm引入,然后直接在ts文件中声明一下即可。如果你对Angular不熟悉也可以选择其他前端框架。下面是html的内容以及ts代码:
<div class="camera" nz-row>
<div nz-col [nzSpan]="20">
<div nz-col [nzSpan]="12" class="camera_screen">
<video class="videoElement" controls="controls"></video>
</div>
<div nz-col [nzSpan]="12" class="camera_screen">
<video class="videoElement" controls="controls"></video>
</div>
<div nz-col [nzSpan]="12" class="camera_screen">
<video class="videoElement" controls="controls"></video>
</div>
<div nz-col [nzSpan]="12" class="camera_screen">
<video class="videoElement" controls="controls"></video>
</div>
</div>
<div class="camera_stand" nz-col [nzSpan]="4"></div>
</div>
loadVideo(httpUrl: string, index: number): void {
this.player = document.getElementsByClassName('videoElement').item(index);
if (flvjs.default.isSupported()) {
// 创建flvjs对象
this.flvPlayer = flvjs.default.createPlayer({
type: 'flv', // 指定视频类型
isLive: true, // 开启直播
hasAudio: false, // 关闭声音
cors: true, // 开启跨域访问
url: httpUrl, // 指定流链接
},
{
enableStashBuffer: false,
lazyLoad: true,
lazyLoadMaxDuration: 1,
lazyLoadRecoverDuration: 1,
deferLoadAfterSourceOpen: false,
statisticsInfoReportInterval: 1,
fixAudioTimestampGap: false,
autoCleanupSourceBuffer: true,
autoCleanupMaxBackwardDuration: 5,
autoCleanupMinBackwardDuration: 2,
}); // 将flvjs对象和DOM对象绑定
this.flvPlayer.attachMediaElement(this.player);
// 加载视频
this.flvPlayer.load();
// 播放视频
this.flvPlayer.play();
this.player.addEventListener('progress', function() {
const len = this.buffered.length ;
const buftime = this.buffered.end(len - 1) - this.currentTime;
if (buftime >= 0.5) {
this.currentTime = this.buffered.end(len - 1);
}
});
}
}
有关flv的参数配置与事件监听器后面会专门解释,先展示一下直播的效果:
这里模拟了四路视频的情况,效果还是很理想的。
2. 直播延迟分析及解决方案
2.1 网络因素
目前使用在直播领域比较常用的网络协议有rtmp和http_flv。hls是苹果公司开发的直播协议,多用在苹果自己的设备上,延迟比较明显。此外从播放器的角度来看,有一个因素也是需要考虑的。我们知道视频传输分为关键帧(I)和非关键帧(P/B),播放器对画面进行解码的起始帧必须是关键帧。但是受到直播条件的约束,用户打开播放的时候接收到的第一帧视频帧不会刚刚好是关键帧。根据我在接收端对于海康摄像机的测试,每两个关键帧之间大约有50帧非关键帧,而设备的fps值是25,即每秒25帧画面。也就是说,大概每2每秒才会有一帧关键帧。那么假设用户在网络传输的第1秒开始播放,推流服务器就面临两个选择:让播放端黑屏1秒等到下一个关键帧才开始播放 或 从上一个关键帧开始发送出去让用户端有1秒的画面延迟。实际上,无论怎么选择都是一个鱼与熊掌的故事,要想直播没有延迟就得忍受黑屏,要想用户体验好就会有画面延迟。
这里我们选择后者,先保证用户体验,后面我会用其他手段来弥补画面延迟的缺点。所以在nginx的配置选项中打开gop_cache。
2.2 播放器缓冲
无论是在C端还是在B端,从服务器读取到的数据流都不会被立刻播放而是首先被缓冲起来。由于我们的网络协议采用TCP连接,数据包有可能在客户端不断累积,造成播放延迟。回到上面的loadVideo方法重点看addEventListener。HTML5提供了与音视频播放相关的事件监听器,this.buffered.end(len - 1)返回最后一个缓冲区的结束时间。我们可以利用这个缓冲时间与当前时间进行比较,当大于某一阈值的时候就直接向后跳帧。要注意这个阈值的设置时间越短,网络抖动越有可能影响收看效果。所以我们需要根据实际业务需求来设置。同时通过在播放端动态调整缓冲进度既保证了用户在打开浏览器的第一时间就看到画面又降低了直播延迟。
2.3 传输延迟
以上考虑的情况都是在局域网内部进行,网络延迟基本忽略不计。但是如果您的应用要部署到公网上,传输延迟就必须要考虑了。
3.总结
本文的重点是如何在保证用户体验的基础上尽量提升直播效果,这类需求一般适用于企业内部监控系统的实施和异地办公地点举行视频会议。传统的直接使用rtsp网络摄像机所提供的C端解决方案也能够达到极小的延迟和较高的视频效果。但是部署起来要比B端复杂。
最后号外一下我的QQ讨论群:960652410
监控视频采集与Web直播开发全流程分析的更多相关文章
- springboot 事务执行全流程分析
springboot 事务执行全流程分析 目录 springboot 事务执行全流程分析 1. 事务方法执行前的准备工作 2. 业务代码的调用 3. 事务方法执行后处理 4. 业务代码在事务和非事务中 ...
- 移动物体监控系统-sprint4嵌入式web服务器开发
一.BOA嵌入式服务器的移植 step1:下载BOA服务器并解压,进入boa下面的src目录,执行./configure生成必须的配置文件以及Makefile step2:修改Makefile文件 c ...
- Kafka控制器事件处理全流程分析
前言 大家好,我是 yes. 这是Kafka源码分析第四篇文章,今天来说说 Kafka控制器,即 Kafka Controller. 源码类的文章在手机上看其实效果很差,这篇文章我分为两部分,第一部分 ...
- Kafka处理请求的全流程分析
大家好,我是 yes. 这是我的第三篇Kafka源码分析文章,前两篇讲了日志段的读写和二分算法在kafka索引上的应用 今天来讲讲 Kafka Broker端处理请求的全流程,剖析下底层的网络通信是如 ...
- 十分钟带你了解CANN应用开发全流程
摘要:CANN作为昇腾AI处理器的发动机,支持业界多种主流的AI框架,包括MindSpore.TensorFlow.Pytorch.Caffe等,并提供1200多个基础算子. 2021年7月8日,第四 ...
- VS2017+QT5.12.10+QGIS3.16环境搭建及开发全流程
题记:大力发展生产力,助力高效采集.(转载请注明出处https://www.cnblogs.com/1024bytes/p/15477374.html) 本篇随笔分为五个部分: 一.获取QGIS3.1 ...
- Odoo9.0模块开发全流程
构建Odoo模块 模块组成 业务对象 业务对象声明为Python类, 由Odoo自己主动加载. 数据文件 XML或CSV文件格式, 在当中声明了元数据(视图或工作流).配置数据(模块參数).演示数据等 ...
- go web framework gin 启动流程分析
最主要的package : gin 最主要的struct: Engine Engine 是整个framework的实例,它包含了muxer, middleware, configuration set ...
- WEB前端开发流程总结
作者声明:本博客中所写的文章,都是博主自学过程的笔记,参考了很多的学习资料,学习资料和笔记会注明出处,所有的内容都以交流学习为主.有不正确的地方,欢迎批评指正 WEB前端开发项目流程总结 1.新建项目 ...
随机推荐
- Linux命令: 编辑模式移动光标
敲命令按以下顺序 ①vim filename ②e ③i ④ESC 移动光标 0 (零):将光标移动到行的起始处. $:将光标移动到行的末尾处. H:将光标移到当前窗口(而非全文)的第一行起始处. M ...
- 基于Axis1.4的webservice接口开发(环境搭建)
基于Axis1.4的webservice接口开发(环境搭建) 一.环境搭建: 1.搜索关键字“Axis1.4”下载Axis1.4相关的jar包. 下载地址:http://download.csdn.n ...
- python 文件操作,os.path.walk()的回调函数打印文件名
#coding=utf-8 import osdef find_file(arg,dirname,files): #for i in arg: #print i for file ...
- IO(字节流)
1. 字节流类以InputStream 和 OutputStream为顶层类,他们都是抽象类(abstract) 2. 最重要的两种方法是read()和write(),它们分别对数据的字节进行读写.两 ...
- SpringBoot集成Socket服务后打包(war包)启动时如何启动Socket服务(web应用外部tomcat启动)
1.首先知道SpringBoot打包为jar和war包是不一样的(只讨论SpringBoot环境下web应用打包) 1.1.jar和war包的打开方式不一样,虽然都依赖java环境,但是j ...
- AtCoder Beginner Contest 086 D - Checker
Time limit : 2sec / Memory limit : 256MB Score : 500 points Problem Statement AtCoDeer is thinking o ...
- CentOS 7 安装OpenCV
CentOS 7 安装OpenCV步骤如下: 1.在CentOS 7命令行中直接在线安装: yum install numpy opencv* 2.安装完成后进行全盘搜索:find / -n ...
- phpstorm常用快捷键(自备不全)
CTRL+N 查找类 CTRL+SHIFT+N 全局搜索文件 ,优先文件名匹配的文件 CTRL+SHIFT+ALT+N 查找php类名/变量名 ,js方法名/变量名, css 选择器 CTRL+G 定 ...
- Centos文件切割利器_split命令及cat命令合并文件
有个文件要处理,因为很大,所以想把它切成若干份,每份N行,以便并行处理.split命令可以将一个大文件分割成很多个小文件,有时需要将文件分割成更小的片段,为提高可读性,生成日志等 命令格式 -b:值为 ...
- Linux解压文件到指定目录
Linux解压文件到指定目录 tar在Linux上是常用的打包.压缩.加压缩工具,他的参数很多,折里仅仅列举常用的压缩与解压缩参数 参数:-c :create 建立压缩档案的参数:-x : 解压缩压缩 ...