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/app/demo/pullrtsp2pushrtsp/pullrtsp2pushrtsp.go

201 lines
5.5 KiB
Go

// 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 main
import (
"flag"
"fmt"
"os"
"sync"
"time"
3 years ago
"github.com/q191201771/naza/pkg/nazaerrors"
"github.com/q191201771/naza/pkg/unique"
"github.com/q191201771/lal/pkg/sdp"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/rtprtcp"
"github.com/q191201771/lal/pkg/rtsp"
"github.com/q191201771/naza/pkg/nazalog"
)
type RtspTunnel struct {
pullUrl string
pushUrl string
pullOverTcp bool
pushOverTcp bool
uniqueKey string
rtpPacketChan chan rtprtcp.RtpPacket
disposeOnce sync.Once
waitChan chan error
pullSession *rtsp.PullSession
pushSession *rtsp.PushSession
}
func NewRtspTunnel(pullUrl string, pushUrl string, pullOverTcp bool, pushOverTcp bool) *RtspTunnel {
uniqueKey := unique.GenUniqueKey("RTSPTUNNEL")
nazalog.Debugf("[%s] lifecycle new RtspTunnel. pullUrl=%s, pushUrl=%s", uniqueKey, pullUrl, pushUrl)
return &RtspTunnel{
pullUrl: pullUrl,
pushUrl: pushUrl,
pullOverTcp: pullOverTcp,
pushOverTcp: pushOverTcp,
uniqueKey: uniqueKey,
rtpPacketChan: make(chan rtprtcp.RtpPacket, 1024),
waitChan: make(chan error, 1),
}
}
// Start 开启任务,阻塞直到任务开启成功或失败
//
// @return true 表示任务启动成功,此时数据已经在后台转发
// false 表示任务启动失败
//
func (r *RtspTunnel) Start() error {
r.pullSession = rtsp.NewPullSession(r, func(option *rtsp.PullSessionOption) {
option.PullTimeoutMs = 5000
option.OverTcp = r.pullOverTcp
})
if err := r.pullSession.Pull(r.pullUrl); err != nil {
nazalog.Errorf("[%s] start pull failed. err=%+v, url=%s", r.uniqueKey, err, r.pullUrl)
return err
}
sdpCtx := r.pullSession.GetSdp()
nazalog.Debugf("[%s] start pull succ. sdp=%s", r.uniqueKey, string(sdpCtx.RawSdp))
r.pushSession = rtsp.NewPushSession(func(option *rtsp.PushSessionOption) {
option.PushTimeoutMs = 5000
option.OverTcp = r.pushOverTcp
})
if err := r.pushSession.Push(r.pushUrl, sdpCtx); err != nil {
nazalog.Errorf("[%s] start push failed. err=%+v, url=%s", r.uniqueKey, err, r.pushUrl)
return err
}
nazalog.Debugf("[%s] start push succ.", r.uniqueKey)
go func() {
t := time.NewTicker(1 * time.Second)
defer t.Stop()
for {
select {
case pkt := <-r.rtpPacketChan:
_ = r.pushSession.WriteRtpPacket(pkt)
case err := <-r.pullSession.WaitChan():
nazalog.Debugf("[%s] < pullSession.Wait(). err=%+v", r.uniqueKey, err)
_ = r.dispose(err)
return
case err := <-r.pushSession.WaitChan():
nazalog.Debugf("[%s] < pushSession.Wait(). err=%+v", r.uniqueKey, err)
_ = r.dispose(err)
return
case <-t.C:
r.pullSession.UpdateStat(1)
pullStat := r.pullSession.GetStat()
r.pushSession.UpdateStat(1)
pushStat := r.pushSession.GetStat()
nazalog.Debugf("[%s] stat. pull=%+v, push=%+v", r.uniqueKey, pullStat, pushStat)
}
}
}()
return nil
}
// Dispose 主动关闭tunnel时调用
//
// 注意,只有 Start 成功后的tunnel才能调用否则行为未定义
//
// 更详细的说明参考 IClientSessionLifecycle interface
//
func (r *RtspTunnel) Dispose() error {
return r.dispose(nil)
}
// WaitChan Start 成功后可使用这个channel来接收tunnel结束的消息
//
// 更详细的说明参考 IClientSessionLifecycle interface
//
func (r *RtspTunnel) WaitChan() chan error {
return r.waitChan
}
// ---------------------------------------------------------------------------------------------------------------------
func (r *RtspTunnel) OnRtpPacket(pkt rtprtcp.RtpPacket) {
r.rtpPacketChan <- pkt
}
func (r *RtspTunnel) OnSdp(sdpCtx sdp.LogicContext) {
// noop
}
func (r *RtspTunnel) OnAvPacket(pkt base.AvPacket) {
// noop
}
func (r *RtspTunnel) dispose(err error) error {
var retErr error
r.disposeOnce.Do(func() {
nazalog.Infof("[%s] lifecycle dispose RtspTunnel.", r.uniqueKey)
e1 := r.pullSession.Dispose()
e2 := r.pushSession.Dispose()
retErr = nazaerrors.CombineErrors(e1, e2)
r.waitChan <- err
})
return retErr
}
// ---------------------------------------------------------------------------------------------------------------------
func main() {
_ = nazalog.Init(func(option *nazalog.Option) {
option.AssertBehavior = nazalog.AssertFatal
})
defer nazalog.Sync()
inUrl, outUrl, pullOverTcp, pushOverTcp := parseFlag()
rtspTunnel := NewRtspTunnel(inUrl, outUrl, pullOverTcp == 1, pushOverTcp == 1)
err := rtspTunnel.Start()
if err != nil {
nazalog.Errorf("start tunnel failed. err=%+v", err)
return
}
//go func() {
// time.Sleep(5 * time.Second)
// _ = rtspTunnel.Dispose()
//}()
err = <-rtspTunnel.WaitChan()
nazalog.Errorf("tunnel stopped. err=%+v", err)
}
func parseFlag() (inUrl string, outUrl string, pullOverTcp int, pushOverTcp int) {
i := flag.String("i", "", "specify pull rtsp url")
o := flag.String("o", "", "specify push rtsp url")
t := flag.Int("t", 0, "specify pull interleaved mode(rtp/rtcp over tcp)")
y := flag.Int("y", 0, "specify push interleaved mode(rtp/rtcp over tcp)")
flag.Parse()
if *i == "" || *o == "" {
flag.Usage()
_, _ = fmt.Fprintf(os.Stderr, `Example:
%s -i rtsp://localhost:5544/live/test110 -o rtsp://localhost:5544/live/test220
%s -i rtsp://localhost:5544/live/test110 -o rtsp://localhost:5544/live/test220 -t 1 -y 1
`, os.Args[0], os.Args[0])
base.OsExitAndWaitPressIfWindows(1)
}
return *i, *o, *t, *y
}