1、 rtsp 工具
1 vlc
必备工具
2 wireshark
必备工具
3 自己制作的工具
player 使用tcp 拉流,不自己写的话,使用ffmpeg 去写一个播放器就行
4 live555
编译好live555, 将live555的参数修改以下,主要是缓存大小
文章使用c++ 来写一个server,目的主要是为了gb28181 接收ps流, 主动拉rtsp 流,经过AI 算法以后 ,再将AI 结果 转成wsflv 和 rtsp 流。为了对比live555的过程,编译live555,本身live是一个非常好的服务端和客户端,测试非常方便。这里AI 使用libtorch。整个协议都使用原始的代码编写,最重要的是要支持投屏协议,可以将结果直接投送到大屏上,包含的协议比较多,综合比较强,因此准备使用多篇文章分开来写。
解码依然使用ffmpeg来解码,尽量使用ffmpeg的硬解码, 如果有可能,尽量使用vulkan。
最后生成rtsp server 只支撑tcp 协议,因为有可能要穿到外网部署。同时支持分布式推送ps流。各位读者不要觉得复杂,这个是真的需求。
2、rtsp 抓包
如下,黑体加粗的是客户端,紧接着的是服务端。
OPTIONS rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
RTSP/1.0 401 Unauthorized
CSeq: 2
WWW-Authenticate: Digest realm=“Login to 6FEF45758F9C6A42”,nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”
OPTIONS rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 3
Authorization: Digest username=“admin”, realm=“Login to 6FEF45758F9C6A42”, nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”, uri=“rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0”, response=“3e64ebc6b330912aa130ed02dc7fb46b”
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
RTSP/1.0 200 OK
CSeq: 3
Public: OPTIONS, DESCRIBE, ANNOUNCE, SETUP, PLAY, PAUSE, TEARDOWN, GET_PARAMETER, SET_PARAMETER, REDIRECT, RECORD
Server: Rtsp Server/3.0
DESCRIBE rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 4
Authorization: Digest username=“admin”, realm=“Login to 6FEF45758F9C6A42”, nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”, uri=“rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0”, response=“83a27a943b70d6094e8f27612f2fc026”
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 200 OK
CSeq: 4
Content-Base: rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/
Content-Type: application/sdp
x-Accept-Dynamic-Rate: 1
Cache-Control: must-revalidate
Content-Length: 477
v=0
o=- 2229913047 2229913047 IN IP4 0.0.0.0
s=Media Server
c=IN IP4 0.0.0.0
t=0 0
a=control:*
a=packetization-supported:DH
a=rtppayload-supported:DH
a=range:npt=now-
m=video 0 RTP/AVP 96
a=control:trackID=0
a=framerate:25.000000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=4D0029;sprop-parameter-sets=Z00AKZY1QPAET8s3BQEFQAAAAwBAAAAMoQA=,aO4xsgA=
a=recvonly
m=audio 0 RTP/AVP 8
a=control:trackID=1
a=rtpmap:8 PCMA/8000
a=recvonly
SETUP rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/trackID=0 RTSP/1.0
CSeq: 5
Authorization: Digest username=“admin”, realm=“Login to 6FEF45758F9C6A42”, nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”, uri=“rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/”, response=“0eae62d04ad69162488d27765b8c0078”
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP;unicast;client_port=60860-60861
RTSP/1.0 200 OK
CSeq: 5
Session: 2667172900
Transport: RTP/AVP;unicast;client_port=60860-60861;server_port=2000-2001;ssrc=8c70b4ab
x-Dynamic-Rate: 1
SETUP rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/trackID=1 RTSP/1.0
CSeq: 6
Authorization: Digest username=“admin”, realm=“Login to 6FEF45758F9C6A42”, nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”, uri=“rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/”, response=“0eae62d04ad69162488d27765b8c0078”
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP;unicast;client_port=60862-60863
Session: 2667172900
RTSP/1.0 200 OK
CSeq: 6
Session: 2667172900
Transport: RTP/AVP;unicast;client_port=60862-60863;server_port=2002-2003;ssrc=99bb5969
x-Dynamic-Rate: 1
PLAY rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/ RTSP/1.0
CSeq: 7
Authorization: Digest username=“admin”, realm=“Login to 6FEF45758F9C6A42”, nonce=“fa305e83-5897-4eaa-a787-1395d12bac9b”, uri=“rtsp://192.168.0.108:554/cam/realmonitor?channel=1&subtype=0/”, response=“ea485ad09bc4e5cd23ae7bfdbd1dccd8”
User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)
Session: 2667172900
Range: npt=0.000-
RTSP/1.0 200 OK
CSeq: 7
Session: 2667172900
Range: npt=0.000-
RTP-Info: url=trackID=0;seq=1;rtptime=0,url=trackID=1;seq=1;rtptime=0
整个交互过程确实一目了然,rtsp 协议是非常简单明了,
3、使用vlc 拉流
注意点:
使用vlc时要注意一点,就是拉流如果是用tcp方式,把vlc里面设置以下,打开的速度会很快,不然他会尝试udp方式,最后才打开tcp,会使用很长时间
可以从界面上看到实际上vlc使用的是live555 来制作的rtsp client。
输入输出的时间戳问题
接收到的流分为两种,一种是ps流,一种是rtsp流,对于ps流,我们需要首先要建立RTPserver 解析,然后将ps 转成标准的RTP协议。
标准的RTP协议的时间戳以90000为基,而wsflv流也就是websocket流必须以普通的时间戳为准,因此,里面需要转换时间戳。
GB28181 收流后接收到的时间戳直接给RTP,也就是RTSP 协议里直接可以使用,因为ps流的时间戳是以90000为基的,而转到websocket flv 流,必须变成正常的时间,公式应该是
1/90000 * pts * 1000, 比如 4500 的RTP时间戳,对应的时间应该为 ,4500* 1000 / 90000 = 50,
4500 这个数值我们如果敏感的话,其实就是一秒钟为20帧, 也就是 时间戳为 0 50 100 150,而对应的RTP 时间戳为 0 4500 9000 13500 …
接收ps流
暂时先做一个ps server over udp, 先不做tcp ,这样容易出成果
void PsServer(int listenPort)
{int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if(sock_fd == -1){printf("Socket init error\n");exit(-1);}struct sockaddr_in addr_s; memset(&addr_s, 0, sizeof(addr_s));addr_s.sin_family = AF_INET;addr_s.sin_port = htons(listenPort);addr_s.sin_addr.s_addr = htonl(INADDR_ANY);/* 绑定socket */ if(bind(sock_fd, (struct sockaddr *)&addr_s, sizeof(addr_s)) < 0) { perror("bind error:"); exit(1); } char ipbuf[20];int n, len;char recv_buf[MAX_BUFF_SIZE]; struct sockaddr_in addr_c;memset(&addr_c, 0, sizeof(addr_c));while(1) { n = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_c, (socklen_t *)&len); if(n < 0) {perror("recvfrom error:"); exit(1); }// bzero(&ipbuf,sizeof(ipbuf));// inet_ntop(AF_INET,&addr_c.sin_addr.s_addr,ipbuf,sizeof(ipbuf));if(n > 12){uint32_t ssrc;memcpy((uint8_t*)&ssrc, (uint8_t*)recv_buf + 8, 4);ssrc = htonl(ssrc);RtpSource* rtp = getRtpSource(ssrc);memcpy(rtp->data, (uint8_t*)recv_buf, n);rtp->len = n;m_rtpFactory.parserRtpData(rtp);}}
}
收到流以后要解封包ps,变成es 流,然后 解码,使用libtorch 做AI 识别,最后将画面编码,成es 流,再交给rtsp server,我们一点点来,先讲一下解码后用torch 识别,下一次讲如何解封包ps流
拉取流后解码调用
#include <iostream>
#include "torch/script.h"
#include "torch/torch.h"
using namespace std;int main() {//加载pytorch模型torch::jit::script::Module module = torch::jit::load("./model.pt");//注意检查路径//是否支持GPU加速if(!torch::cuda::is_available())exit(0);torch::DeviceType device_type; //设置Device类型device_type = torch::kCUDA; //torch::kCUDA and torch::kCPUtorch::Device device(device_type, 0);//模型转到GPU中去module.to(device);// Create a vector of inputs.std::vector<torch::jit::IValue> inputs;inputs.push_back(torch::ones({1, 3, 46, 224}).to(device));// Execute the model and turn its output into a tensor.for (int i = 0; i < 100; i++) {at::Tensor output = module.forward(inputs).toTensor();std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';}
}
下一节继续