You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/rtsp/client_pull_session.go

314 lines
7.6 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright 2020, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package rtsp
import (
"context"
"fmt"
"net"
"net/url"
"time"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/rtprtcp"
"github.com/q191201771/lal/pkg/sdp"
"github.com/q191201771/naza/pkg/connection"
"github.com/q191201771/naza/pkg/nazahttp"
"github.com/q191201771/naza/pkg/nazalog"
"github.com/q191201771/naza/pkg/nazanet"
)
const (
pullReadBufSize = 256
)
type PullSessionObserver interface {
BaseInSessionObserver
}
type PullSessionOption struct {
PullTimeoutMS int // 从调用Pull函数到收到rtsp play response接收音视频数据的前一步的超时时间
}
var defaultPullSessionOption = PullSessionOption{
PullTimeoutMS: 10000,
}
type PullSession struct {
baseInSession *BaseInSession
UniqueKey string // const after ctor
option PullSessionOption // const after ctor
CmdConn connection.Connection
cseq int
sessionID string
rawURL string
host string // without port
hostport string
pathWithQuery string
}
type ModPullSessionOption func(option *PullSessionOption)
func NewPullSession(observer PullSessionObserver, modOptions ...ModPullSessionOption) *PullSession {
option := defaultPullSessionOption
for _, fn := range modOptions {
fn(&option)
}
uk := base.GenUniqueKey(base.UKPRTSPPullSession)
s := &PullSession{
UniqueKey: uk,
option: option,
}
baseInSession := &BaseInSession{
UniqueKey: uk,
stat: base.StatPub{
StatSession: base.StatSession{
Protocol: base.ProtocolRTSP,
SessionID: uk,
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
},
},
observer: observer,
cmdSession: s,
}
s.baseInSession = baseInSession
nazalog.Infof("[%s] lifecycle new rtsp PullSession. session=%p", uk, s)
return s
}
func (session *PullSession) Pull(rawURL string) error {
var (
ctx context.Context
cancel context.CancelFunc
)
if session.option.PullTimeoutMS == 0 {
ctx, cancel = context.WithCancel(context.Background())
} else {
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(session.option.PullTimeoutMS)*time.Millisecond)
}
defer cancel()
return session.pullContext(ctx, rawURL)
}
func (session *PullSession) Write(channel int, b []byte) error {
return nil
}
func (session *PullSession) Dispose() error {
return nil
}
func (session *PullSession) pullContext(ctx context.Context, rawURL string) error {
errChan := make(chan error, 1)
go func() {
if err := session.connect(rawURL); err != nil {
errChan <- err
return
}
if err := session.writeOptions(); err != nil {
errChan <- err
return
}
if err := session.writeDescribe(); err != nil {
errChan <- err
return
}
if err := session.writeSetup(); err != nil {
errChan <- err
return
}
if err := session.writePlay(); err != nil {
errChan <- err
return
}
errChan <- nil
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errChan:
if err != nil {
return err
}
}
dummy := make([]byte, 1) // 用于接收TCP对端关闭FIN信号
_, err := session.CmdConn.Read(dummy)
return err
}
func (session *PullSession) connect(rawURL string) error {
// # 从 url 中解析 host uri addr
u, err := url.Parse(rawURL)
if err != nil {
return err
}
if u.Scheme != "rtsp" {
return ErrRTSP
}
session.rawURL = rawURL
if u.RawQuery == "" {
session.pathWithQuery = u.Path
} else {
session.pathWithQuery = fmt.Sprintf("%s?%s", u.Path, u.RawQuery)
}
// TODO chef: 检查其他协议pull session这块的处理
host, _, err := net.SplitHostPort(u.Host)
if err == nil {
session.host = host
session.hostport = u.Host
} else {
session.host = u.Host
session.hostport = net.JoinHostPort(u.Host, fmt.Sprintf("%d", DefaultRTSPPort))
}
nazalog.Debugf("[%s] > tcp connect.", session.UniqueKey)
// # 建立连接
conn, err := net.Dial("tcp", session.hostport)
if err != nil {
return err
}
session.CmdConn = connection.New(conn, func(option *connection.Option) {
option.ReadBufSize = pullReadBufSize
})
return nil
}
func (session *PullSession) writeOptions() error {
session.cseq++
req := PackRequestOptions(session.rawURL, session.cseq)
nazalog.Debugf("[%s] > write options.", session.UniqueKey)
if _, err := session.CmdConn.Write([]byte(req)); err != nil {
return err
}
ctx, err := nazahttp.ReadHTTPResponseMessage(session.CmdConn)
if err != nil {
return err
}
nazalog.Debugf("[%s] < read response. %s", session.UniqueKey, ctx.StatusCode)
return nil
}
func (session *PullSession) writeDescribe() error {
session.cseq++
req := PackRequestDescribe(session.rawURL, session.cseq)
nazalog.Debugf("[%s] > write describe.", session.UniqueKey)
if _, err := session.CmdConn.Write([]byte(req)); err != nil {
return err
}
ctx, err := nazahttp.ReadHTTPResponseMessage(session.CmdConn)
if err != nil {
return err
}
nazalog.Debugf("[%s] < read response. code=%s, body=%s", session.UniqueKey, ctx.StatusCode, string(ctx.Body))
sdpCtx, err := sdp.ParseSDP2LogicContext(ctx.Body)
if err != nil {
return err
}
session.baseInSession.InitWithSDP(ctx.Body, sdpCtx)
return nil
}
func (session *PullSession) writeSetup() error {
if session.baseInSession.sdpLogicCtx.VideoAControl != "" {
if err := session.writeOneSetup(session.baseInSession.sdpLogicCtx.VideoAControl); err != nil {
return err
}
}
if session.baseInSession.sdpLogicCtx.AudioAControl != "" {
if err := session.writeOneSetup(session.baseInSession.sdpLogicCtx.AudioAControl); err != nil {
return err
}
}
return nil
}
func (session *PullSession) writeOneSetup(aControl string) error {
setupURI := fmt.Sprintf("%s/%s", session.rawURL, aControl)
rtpC, rtpPort, rtcpC, rtcpPort, err := availUDPConnPool.Acquire2()
if err != nil {
return err
}
session.cseq++
req := PackRequestSetup(setupURI, session.cseq, session.sessionID, int(rtpPort), int(rtcpPort))
nazalog.Debugf("[%s] > write setup.", session.UniqueKey)
if _, err := session.CmdConn.Write([]byte(req)); err != nil {
return err
}
ctx, err := nazahttp.ReadHTTPResponseMessage(session.CmdConn)
if err != nil {
return err
}
nazalog.Debugf("[%s] < read response. code=%s, ctx=%+v", session.UniqueKey, ctx.StatusCode, ctx)
session.sessionID = ctx.Headers[HeaderFieldSession]
srvRTPPort, srvRTCPPort, err := parseServerPort(ctx.Headers[HeaderFieldTransport])
if err != nil {
return err
}
rtpConn, err := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) {
option.Conn = rtpC
option.RAddr = net.JoinHostPort(session.host, fmt.Sprintf("%d", srvRTPPort))
option.MaxReadPacketSize = rtprtcp.MaxRTPRTCPPacketSize
})
if err != nil {
return err
}
rtcpConn, err := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) {
option.Conn = rtcpC
option.RAddr = net.JoinHostPort(session.host, fmt.Sprintf("%d", srvRTCPPort))
option.MaxReadPacketSize = rtprtcp.MaxRTPRTCPPacketSize
})
if err != nil {
return err
}
if err := session.baseInSession.SetupWithConn(setupURI, rtpConn, rtcpConn); err != nil {
return err
}
return nil
}
func (session *PullSession) writePlay() error {
session.cseq++
req := PackRequestPlay(session.rawURL, session.cseq, session.sessionID)
nazalog.Debugf("[%s] > write play.", session.UniqueKey)
if _, err := session.CmdConn.Write([]byte(req)); err != nil {
return err
}
ctx, err := nazahttp.ReadHTTPResponseMessage(session.CmdConn)
if err != nil {
return err
}
nazalog.Debugf("[%s] < read response. %s", session.UniqueKey, ctx.StatusCode)
return nil
}