内容概要:

摄像头 => 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直播开发全流程分析的更多相关文章

  1. springboot 事务执行全流程分析

    springboot 事务执行全流程分析 目录 springboot 事务执行全流程分析 1. 事务方法执行前的准备工作 2. 业务代码的调用 3. 事务方法执行后处理 4. 业务代码在事务和非事务中 ...

  2. 移动物体监控系统-sprint4嵌入式web服务器开发

    一.BOA嵌入式服务器的移植 step1:下载BOA服务器并解压,进入boa下面的src目录,执行./configure生成必须的配置文件以及Makefile step2:修改Makefile文件 c ...

  3. Kafka控制器事件处理全流程分析

    前言 大家好,我是 yes. 这是Kafka源码分析第四篇文章,今天来说说 Kafka控制器,即 Kafka Controller. 源码类的文章在手机上看其实效果很差,这篇文章我分为两部分,第一部分 ...

  4. Kafka处理请求的全流程分析

    大家好,我是 yes. 这是我的第三篇Kafka源码分析文章,前两篇讲了日志段的读写和二分算法在kafka索引上的应用 今天来讲讲 Kafka Broker端处理请求的全流程,剖析下底层的网络通信是如 ...

  5. 十分钟带你了解CANN应用开发全流程

    摘要:CANN作为昇腾AI处理器的发动机,支持业界多种主流的AI框架,包括MindSpore.TensorFlow.Pytorch.Caffe等,并提供1200多个基础算子. 2021年7月8日,第四 ...

  6. VS2017+QT5.12.10+QGIS3.16环境搭建及开发全流程

    题记:大力发展生产力,助力高效采集.(转载请注明出处https://www.cnblogs.com/1024bytes/p/15477374.html) 本篇随笔分为五个部分: 一.获取QGIS3.1 ...

  7. Odoo9.0模块开发全流程

    构建Odoo模块 模块组成 业务对象 业务对象声明为Python类, 由Odoo自己主动加载. 数据文件 XML或CSV文件格式, 在当中声明了元数据(视图或工作流).配置数据(模块參数).演示数据等 ...

  8. go web framework gin 启动流程分析

    最主要的package : gin 最主要的struct: Engine Engine 是整个framework的实例,它包含了muxer, middleware, configuration set ...

  9. WEB前端开发流程总结

    作者声明:本博客中所写的文章,都是博主自学过程的笔记,参考了很多的学习资料,学习资料和笔记会注明出处,所有的内容都以交流学习为主.有不正确的地方,欢迎批评指正 WEB前端开发项目流程总结 1.新建项目 ...

随机推荐

  1. 20165207 Exp2 后门原理与实践

    20165207 Exp2 后门原理与实践 〇.实验准备 两个虚拟机,一个kali一个win7.kali的ip是192.168.43.72,win7的ip是192.168.43.116,在win7关掉 ...

  2. Linux服务器---配置apache支持用户认证

    Apache支持用户认证 为了服务器的安全,通常用户在请求访问某个文件夹的时候,Apache可以要求用户输入有效的用户名和登录密码 1.创建一个测试目录 [root@localhost cgi-bin ...

  3. web前端----jQuery动画效果

    动画效果 // 基本 show([s,[e],[fn]]) hide([s,[e],[fn]]) toggle([s],[e],[fn]) // 滑动 slideDown([s],[e],[fn]) ...

  4. redhat6.4 elasticsearch1.7.3安装配置

    利用elasticsearch管理集群索引, 今天刚好需要重新调整elasticsearch的最大内存, 所以自己安装了练手 附件: elasticsearch 附件:elasticsearch-he ...

  5. SCP报错:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

    经过google,出现这个问题的原因是,这是ssh的问题, GkFool大神说(第一次使用SSH连接时,会生成一个认证,储存在客户端的known_hosts中) 我的解决办法是: ssh-keygen ...

  6. 数据结构-队列(3)-使用Java内置队列

    大多数流行语言都提供内置的队列库,因此您无需重新发明轮子. 如前所述,队列有两个重要的操作,入队 enqueue 和出队 dequeue. 此外,我们应该能够获得队列中的第一个元素,因为应该首先处理它 ...

  7. 【前端】Vue.js实现网格列表布局转换

    网格列表布局转换 实现效果: 实现代码及注释: <!DOCTYPE html> <html> <head> <title>布局转换</title& ...

  8. Linux系统编程--文件描述符的复制dup()和dup2()【转】

    本文转载自:http://blog.csdn.net/tennysonsky/article/details/45870459 dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个 ...

  9. SpringBoot添加自定义消息转换器

    首先我们需要明白一个概念:springboot中很多配置都是使用了条件注解进行判断一个配置或者引入的类是否在容器中存在,如果存在会如何,如果不存在会如何. 也就是说,有些配置会在springboot中 ...

  10. Springboot2.x 集成redis

    pom.xml 添加 <dependency> <groupId>org.springframework.boot</groupId> <artifactId ...