【音视频通话】使用asp.net core 8+vue3 实现高效音视频通话
引言
在三年前,写智能小车的时候,当时小车上有一个摄像头需要采集,实现推拉流的操作,技术选型当时第一版用的是nginx的rtmp的推拉流,服务器的配置环境是centos,2H4G3M的一个配置,nginx的rtmp的延迟是20秒,超慢,后来研究了SRS以及ZLMediaKit这两个开源的推拉流服务器,没记错的话,两个都是基于c++开发的,性能都很棒,后来更换了推拉流服务器,小汽车的延迟在一秒不到,性能大幅度提升,当时研究了一下,然后没有记录,这次使用SRS实现了音视频通话,以及共享桌面的功能,特此记录以及分享给大家。
技术概览
本demo总共涉及到三个服务,部署环境是腾讯云的轻量服务器,配置是2H2G3M的服务器,CENTOS系统,nginx部署前端,docker部署后端以及SRS服务。
SRS
SRS是一个开源的(MIT协议)简单高效的实时视频服务器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT、MPEG-DASH和GB28181等协议。 SRS媒体服务器和FFmpeg、OBS、VLC、 WebRTC等客户端配合使用,提供流的接收和分发的能力,是一个典型的发布 (推流)和订阅(播放)服务器模型。 SRS支持互联网广泛应用的音视频协议转换,比如可以将RTMP或SRT, 转成HLS或HTTP-FLV或WebRTC等协议。
后端
后端是一个基于net8.0基础框架开发的一个webapi的接口,前后端通讯使用的是signalr,用来实现不同用户之间的一个消息更改通知,本项目没有涉及到任何数据库方面的。
前端
前端是基于node20.16.0,vue3。其中,推拉流的地址和后端服务器的地址配置在configjs.json中配置,pushUrl是推流的地址,pullUrl是拉流的地址,apiUrl是后端接口的地址,signalrUrl是用来做前后端websocket通讯的一个地址。在apiUrl和signalrUrl中的端口需要和上方的后端运行的端口保持一致才可以互相访问通讯,具体配置文档查看下方图例。
功能概览
总体功能分为一下几个:
设备检测/参数设置
这个页码主要是用来配置摄像头,并且检测摄像头是否可用,音频检测音频是否可用,并且配置音频设备,后续的音视频通话都是根据此处选择的设备进行推拉流,用户名称,由于本项目是一个demo,所以都是基于内存,用户是自己设置之后,在demo中与人通讯的一个标识;在这个页面配置好视频设备,音频设备,以及用户名称,就可以去后续的页面进行群聊或者单聊,或者桌面共享的功能。
群聊
群聊进入到页面,就可以看到已经存在的群聊信息,以及在线人数,可以进行加入或者删除群聊的操作,如果有新增的群组,此处也会使用Signalr进行同步更新获取最新的群组列表数据,此demo没有判断删除的时候权限问题以及如果有人正在群聊删除群组的问题,在设备检测页面没有选择音视频设备,也可以进入群组,可以查看有音视频的在线人员的一个音视频,如图所示,在上方输入要创建的群组名称,点击添加群组即可到如下页面,进入视频页面,会先去推流到服务器,以便下一个进入的人可以看到你的视频流,同时如果没有选择音视频设备,点击加入也是如下页面,并且该页面不会展示群组的在线人,可以从视频的摄像头上方可以查看是哪个用户的音视频。
单聊
单聊就是只有两个人进行音视频通话,同时也支持没有选择设备可以进行通话,没有添加文字聊天的支持,单聊列表,也会使用signalr进行同步更新,在用户设置了用户名称之后,在这里就可以看到列表数据的更新。点击通话就可以和在线的人员进行音视频通话,同时不能和自己通话,且选择的人如果在处于单聊状态中,也无法进行音视频通话,在选择了要进行通话的用户后,会进入到聊天页面,同时对方用户会收到一个提醒,是否接受音视频通话,可以拒绝,也可以接受,拒绝之后,请求方会退出到在线列表的页面,点击接受会进入到双方的一个音视频通话的页面,可以进行语音聊天,可以看对方的摄像头推送的视频流。同时接受之后,任意一方离开聊天,另外一方也会直接退出聊天页面。返回到用户在线列表页面中。
共享桌面
共享桌面的操作和群组的一样,在上方输入共享桌面的名称,点击按钮,即可进入到共享桌面的页面中去。进入到页面,会提示需要共享的屏幕内容,可选整个屏幕,浏览器或者窗口都可以,同时也可以选择是否要共享此页面的音频,点击分享之后,即可看到共享的视频内容,右侧显示了参与人列表,并且如果选择了音频设备,也会采集语音推送,多个参与人进行语音聊天,语音会显示谁在发言,收到了谁的语音流。同时当共享的人,点击了停止共享,其他参与人也可以共享自己的桌面,点击了共享桌面按钮,其他参与人的共享桌面按钮会隐藏,并且他们会看到采集的你的屏幕的视频信息。
程序配置
关于配置方面,主要是前端的配置以及SRS的配置,前端方面的配置,在刚开始已经阐述过,主要是SRS的推拉流的配置,以及API和Signalr的地址配置,如果服务器是外网, 地址必须是外网服务器的地址,关于SRS的配置,在下载好SRS之后,SRS提供了默认的配置文件夹,文件夹名字是叫conf文件夹,里面提供了各种不同功能的conf配置文件,此处我们基于console.conf进行改造,下面的是console.conf的配置信息,第一行是listen,默认是1935,代表SRS启动的时候的RMP的直播服务端口。max_connection代表最大连接数量,默认是1000,srs_log_tank是日志的打印方式,默认是console,即控制台,可选配置有file和console,file是文件的输出形式,如果是file的形式,必须通过配置srs_log_file参数配置日志的打印文件的路径,默认是.objs/srs.log的文件。daemon,代表的是是否以后台的形式运行,on代表开启,off代表关闭。http_api,SRS提供了一些api,这个配置代表是否开启http_api,并且端口是8080。下方,展示了SRS官网提供的API导航。rtc_server启用webrtc的功能,端口是udp的8000,默认是8000,其中最后一个配置是candidata,如果是本机部署,这个可以不需要配置,如果是需要部署在外网服务器,那这个就需要设置成外网的服务器地址。
SRS的视频推拉流,有虚拟主机的概念,默认提供了一个defaultVhost的虚拟主机在配置文件里,虚拟主机内有单独的关于主机的配置,hls代表开启hls的视频协议功能,http_remux,是否为http开启直播流服务,mout代表的是挂载的http流,可以是不同的格式,默认是flv,可选有ts,即后缀是.ts,有mp3,aac等协议。rtc代表是否给虚拟主机开启webrtc的功能,并且是否开启rtmp转rtc,以及rtc转rtmp的功能。http_hooks是SRS在有客户端推流或者拉流的时候配置的一个接口回调,这个回调可以是我们自己写的后端服务,这里的配置是我配置的我实际的后端服务的接口地址,可以根据自己的实际情况进行调整,enable代表是开启HOOK,publish是推流的回调,unpublish是停止推流的时候,play和stop是拉流的播放和停止回调。play是针对拉流的配置,gop_cache是否缓存最后一帧,如果开启,客户端能够快速播放,如果关闭,客户端拉取的一直都是最新的,queue_length是缓存的帧一秒缓存的队列长度,如果超过这个,会移除之前的帧,mw_latency,关于流的合并写入的延迟,毫秒级单位。Publish是推流的一个配置,mr是合并读取的延迟,毫米级单位。
关于上面说了那么多的配置,主要的配置还是http_hooks的配置,这个配置,是SRS在接收到推流请求,或者停止推流,以及拉流,停止拉流的时候对我们后端服务的一个回调,enabled代表是否开启回调,publish是开始推流的回调,unpublish是停止推流,play和stop是客户端在播放流或者停止流的时候的一个回调,这里如果部署在外网,且srs和后端服务在一个服务器,此处的地址可以使用局域网的地址,与前端配置不同的是,前端的配置必须是外网的地址。
以上关于所有的全面配置可以参考以下网址:
http_api:http://ossrs.net/lts/zh-cn/docs/v6/doc/http-api。
http_server:http://ossrs.net/lts/zh-cn/docs/v6/doc/http-server。
rtc_server/rtc:http://ossrs.net/lts/zh-cn/docs/v6/doc/webrtc。
hls:http://ossrs.net/lts/zh-cn/docs/v6/doc/hls。
部署
上面说到,我们的发布环境是centos,nginx和docker,所以在安装好了nginx之后 需要对nginx进行一个端口反向代理的配置,这里我用的是443端口,在网上生成了一个ssl证书,因为如果是使用http开头的网址,浏览器的机制,会不让使用摄像头,需要更改浏览器的配置才可以使得http的网址可以访问摄像头,如果是在本地部署和调试此服务,建议使用127.0.0.1或者localhost等方式来获取摄像头的信息,因为浏览器的安全机制,非https并且url地址是IP非127.0.0.1或者localhost的方式获取摄像头是有限制,接触限制的方式是在浏览器地址输入chrome://flags,回车之后,在弹出的页面中搜索Insecure origins treated as secure,如图,回车之后,需要将服务的IP地址输入在下方输入框中,并且将disabled更改为enabled,在更改完成后,浏览器下方会出现图3的面板,点击Relauch重启浏览器即可通过IP地址访问服务,并且加载摄像头,如果是中文disable是禁用,enable是启用,Relauch是重启。
所以基于上面http的问题,需要生成一个ssl证书,让web使用https进行访问。下面我放一份我的nginx的配置,在下面的443的配置中,指定了servicename,以及root指向的是我们发布的之后的web前端的地址。指定了index.html,以及ssl证书相关的配置地址,配置根路径访问的是web前端的index.html,然后将路由中包括api,pull,push的指向对应的端口,如上图的端口配置情况,1985,使用webrtc进行推流,8080,进行拉流的端口,video是signalr的反向代理地址。
以上是我服务器自己部署的nginx的一个配置。
# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { 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 /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; map $http_upgrade $connection_upgrade { default upgrade; '' close; } include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name _; root /video/dist/; index index.html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { try_files $uri $uri/ /index.html; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } server { listen 443 ssl; listen [::]:443; server_name 49.233.22.57; root /video/dist/; index index.html; ssl_certificate /sslfile/49.233.22.57.crt; # ssl证书存储路径 ssl_certificate_key /sslfile/49.233.22.57.private; # 秘钥存储路径 # ssl的一些配置 ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #开启TLS协议 location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://10.2.20.12:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } location /rtc { proxy_pass http://10.2.20.12:1985; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } location /pull { rewrite ^/pull/(.*)$ /$1 break; proxy_pass http://10.2.20.12:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } location /video { proxy_pass http://10.2.20.12:5000; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_cache_bypass $http_upgrade != ''; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } # Settings for a TLS enabled server. # # server { # listen 443 ssl http2; # listen [::]:443 ssl http2; # server_name _; # root /usr/share/nginx/html; # # ssl_certificate "/etc/pki/nginx/server.crt"; # ssl_certificate_key "/etc/pki/nginx/private/server.key"; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 10m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # # # Load configuration files for the default server block. # include /etc/nginx/default.d/*.conf; # # error_page 404 /404.html; # location = /40x.html { # } # # error_page 500 502 503 504 /50x.html; # location = /50x.html { # } # } }
后端部署在docker中, 环境是net8的开发环境,dockerfile配置如下,将发布后的文件和dockerfile放在一起,执行docker build -t videoimg .指令,打包docker镜像。记得后面有一个点.,回车之后,生成了videoimg的docker镜像,生成镜像后,需要启动容器,docker run --name videoc --security-opt seccomp=unconfined -d -p 5000:8080 videoimg 回车之后即可启动了后端服务的容器,本机的端口是5000,需要防火墙或者云服务器开启5000端口,
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 8080 EXPOSE 8081 COPY . . FROM base AS final WORKDIR /app ENTRYPOINT ["dotnet", "WebApplication1.dll"]
docker启动SRS,如果SRS部署在外网服务器,需要在启动SRS容器之前,配置一个环境变量CANDIDATE="外网服务器IP地址",将外网的IP地址写入环境变量中,SRS启动会从中读取配置,也可以直接修改conf中关于CANDIDATE的配置直接更改为你的外网服务器的地址。配置好之后,启动SRS,docker run --rm --name srs -it -d -p 1935:1935 -p 1985:1985 -p 8080:8080 -p 1990:1990 -p 8088:8088 -p 8000:8000/udp -v /video/conf:/conf --env CANDIDATE=$CANDIDATE registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5 ./objs/srs -c /conf/console.conf 上面的指令,-v是将容器内部的conf文件夹挂载出来,然后在后面指定使用了console.conf作为启动的配置文件,并且将端口信息暴露出来,推拉流,以及web服务器需要使用这些端口。
SRS提供了一个web管理系统,防火墙等配置好之后,在浏览器输入IP:8080即可打开如下页面,在控制台可以看到视频的推拉流信息,以及流量的流入和流出。
端口配置
服务器需要开启一下端口
1:后端服务的端口,例如本例子的5000端口
2:SRS相关端口:1935,1985,8080,1990,8088,8000/udp
代码和详细Windows文档地址
关于更多的详细解读,可以查看写好的windows的文档部署地址,除了部署方式的差异,其余都一样。
git地址:https://gitee.com/cxd199645/audio-video.git
总结
关于做一个音视频通话的技术分享就到这里了,如果又不懂的,可以添加QQ/WX:934550201.