rtsp-simple-server
rtsp-simple-server
是RTSP / RTMP / LL-HLS 服务器和代理,允许读取、发布和代理视频和音频流。
目前支持的流有:RTSP
,RTMP
,HLS
。
github的地址在这里。
特征:
- 将直播流发布到服务器
- 从服务器读取实时流
- 来自其他服务器或摄像机的代理流,始终或按需
- 每个流可以有多个视频和音频轨道,用任何 RTP 兼容的编解码器编码,包括 H264、H265、VP8、- VP9、MPEG2、MP3、AAC、Opus、PCM、JPEG
- 流会自动从一种协议转换为另一种协议。例如,可以使用 RTSP 发布流并使用 HLS 读取它
- 在不同的路径中一次提供多个流
- 对用户进行身份验证;使用内部或外部身份验证
- 将阅读器重定向到其他 RTSP 服务器(负载平衡)
- 通过 HTTP API 查询和控制服务器
- 在不断开现有客户端的情况下重新加载配置(热重新加载)
- 阅读 Prometheus 兼容的指标
- 当客户端连接、断开、读取或发布流时运行外部命令
- 与 Raspberry Pi 相机原生兼容
- 与 Linux、Windows 和 macOS 兼容,不需要任何依赖项或解释器,它是单个可执行文件
安装
安装非常的简单,采用docker的方式:
docker run --rm -it -e RTSP_PROTOCOLS=tcp -p 8554:8554 -p 1935:1935 -p 8888:8888 aler9/rtsp-simple-server
用法
启动流服务器,记住那里的ip地址,并把流推到服务器上。
ffmpeg.exe -re -i a.mp4 -vcodec h264 -acodec aac -f rtsp -rtsp_transport tcp rtsp://192.168.100.170:8554/mystream
我这里采用的是ffmpeg
把流推到服务器,服务器的地址为192.168.100.170,我们把它推到mystream
的目录下,接下来取流的方式就比较简单了,可以使用vlc进行播放。
vlc rtsp://192.168.100.170:8554/mystream
也可以用直接拉流,存为视频:
ffmpeg -i rtsp://192.168.100.170:8554/mystream -c copy output.mp4
这样就完成了一次取流和拉流的功能,可能大家对这种流媒体并不熟悉,但是流媒体编程也是一个中友好的方向,下面简单看下代码过程。
代码分析
rtsp-simple-server
使用的是go语言编写,如果学着编写一款服务器的软件,可以用这款作为入门的代码。
rtsp的授权过程:
func (c *rtspConn) authenticate(
pathName string,
pathIPs []fmt.Stringer,
pathUser conf.Credential,
pathPass conf.Credential,
isPublishing bool,
req *base.Request,
query string,
) error {
if c.externalAuthenticationURL != "" {
username := ""
password := ""
var auth headers.Authorization
err := auth.Unmarshal(req.Header["Authorization"])
if err == nil && auth.Method == headers.AuthBasic {
username = auth.BasicUser
password = auth.BasicPass
}
err = externalAuth(
c.externalAuthenticationURL,
c.ip().String(),
username,
password,
pathName,
isPublishing,
query)
if err != nil {
c.authFailures++
// VLC with login prompt sends 4 requests:
// 1) without credentials
// 2) with password but without username
// 3) without credentials
// 4) with password and username
// therefore we must allow up to 3 failures
if c.authFailures > 3 {
return pathErrAuthCritical{
message: "unauthorized: " + err.Error(),
response: &base.Response{
StatusCode: base.StatusUnauthorized,
},
}
}
v := "IPCAM"
return pathErrAuthNotCritical{
message: "unauthorized: " + err.Error(),
response: &base.Response{
StatusCode: base.StatusUnauthorized,
Header: base.Header{
"WWW-Authenticate": headers.Authenticate{
Method: headers.AuthBasic,
Realm: &v,
}.Marshal(),
},
},
}
}
}
if pathIPs != nil {
ip := c.ip()
if !ipEqualOrInRange(ip, pathIPs) {
return pathErrAuthCritical{
message: fmt.Sprintf("IP '%s' not allowed", ip),
response: &base.Response{
StatusCode: base.StatusUnauthorized,
},
}
}
}
if pathUser != "" {
// reset authValidator every time the credentials change
if c.authValidator == nil || c.authUser != string(pathUser) || c.authPass != string(pathPass) {
c.authUser = string(pathUser)
c.authPass = string(pathPass)
c.authValidator = auth.NewValidator(string(pathUser), string(pathPass), c.authMethods)
}
err := c.authValidator.ValidateRequest(req)
if err != nil {
c.authFailures++
// VLC with login prompt sends 4 requests:
// 1) without credentials
// 2) with password but without username
// 3) without credentials
// 4) with password and username
// therefore we must allow up to 3 failures
if c.authFailures > 3 {
return pathErrAuthCritical{
message: "unauthorized: " + err.Error(),
response: &base.Response{
StatusCode: base.StatusUnauthorized,
},
}
}
return pathErrAuthNotCritical{
response: &base.Response{
StatusCode: base.StatusUnauthorized,
Header: base.Header{
"WWW-Authenticate": c.authValidator.Header(),
},
},
}
}
// login successful, reset authFailures
c.authFailures = 0
}
return nil
}
rtsp 的协议过程:
// OnDescribe implements gortsplib.ServerHandlerOnDescribe.
func (s *rtspServer) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
s.mutex.RUnlock()
return c.onDescribe(ctx)
}
// OnAnnounce implements gortsplib.ServerHandlerOnAnnounce.
func (s *rtspServer) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
return se.onAnnounce(c, ctx)
}
// OnSetup implements gortsplib.ServerHandlerOnSetup.
func (s *rtspServer) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
s.mutex.RLock()
c := s.conns[ctx.Conn]
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
return se.onSetup(c, ctx)
}
// OnPlay implements gortsplib.ServerHandlerOnPlay.
func (s *rtspServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
return se.onPlay(ctx)
}
// OnRecord implements gortsplib.ServerHandlerOnRecord.
func (s *rtspServer) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
return se.onRecord(ctx)
}
// OnPause implements gortsplib.ServerHandlerOnPause.
func (s *rtspServer) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, error) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
return se.onPause(ctx)
}
// OnPacketRTP implements gortsplib.ServerHandlerOnPacket.
func (s *rtspServer) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
s.mutex.RLock()
se := s.sessions[ctx.Session]
s.mutex.RUnlock()
se.onPacketRTP(ctx)
}
这个就是相当于rtsp的简单过程了,如果想进行深入了解的话,可以从代码中获得。