mirror of
https://github.com/deepch/RTSPtoRTMP.git
synced 2025-09-26 19:21:22 +08:00
first commit
This commit is contained in:
1
.gitignore
vendored
Executable file
1
.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
||||
.idea/
|
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# RTSPtoRMP
|
||||
|
||||
RTSP Stream to YouTube or Twitch
|
||||
|
||||
full native! not use ffmpeg or gstreamer
|
||||
|
||||
|
||||
### Download Source
|
||||
|
||||
1. Download source
|
||||
```bash
|
||||
$ git clone https://github.com/deepch/RTSPtoRTMP
|
||||
```
|
||||
3. CD to Directory
|
||||
```bash
|
||||
$ cd RTSPtoRTMP/
|
||||
```
|
||||
4. Test Run
|
||||
```bash
|
||||
$ GO111MODULE=on go run *.go
|
||||
```
|
||||
5. Edit config.json
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
### Edit file config.json
|
||||
|
||||
format:
|
||||
|
||||
```bash
|
||||
{
|
||||
"streams": {
|
||||
"H264_AAC": {
|
||||
"on_demand": false,
|
||||
"url": "rtsp://171.25.232.11/05dad5221e774c85abbde206385a38b4",
|
||||
"broadcast": {
|
||||
"enable": true,
|
||||
"url": "rtmp://a.rtmp.youtube.com/live2/{YOU_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Video Codecs Supported: H264 all profiles
|
||||
|
||||
Audio Codecs Supported: AAC or NONE
|
||||
|
||||
## Test
|
||||
|
||||
CPU usage 0.2% one core cpu intel core i7 / stream
|
||||
|
||||
## Team
|
||||
|
||||
Deepch - https://github.com/deepch streaming developer
|
||||
|
||||
Dmitry - https://github.com/vdalex25 web developer
|
||||
|
||||
## Other Example
|
||||
|
||||
Examples of working with video on golang
|
||||
|
||||
- [RTSPtoWeb](https://github.com/deepch/RTSPtoWeb)
|
||||
- [RTSPtoWebRTC](https://github.com/deepch/RTSPtoWebRTC)
|
||||
- [RTSPtoWSMP4f](https://github.com/deepch/RTSPtoWSMP4f)
|
||||
- [RTSPtoImage](https://github.com/deepch/RTSPtoImage)
|
||||
- [RTSPtoHLS](https://github.com/deepch/RTSPtoHLS)
|
||||
- [RTSPtoHLSLL](https://github.com/deepch/RTSPtoHLSLL)
|
||||
- [RTSPtoRTMP](https://github.com/deepch/RTSPtoRTMP)
|
||||
-
|
||||
[](https://www.paypal.me/AndreySemochkin) - You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea:
|
206
config.go
Normal file
206
config.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
)
|
||||
|
||||
var (
|
||||
Success = "success"
|
||||
ErrorStreamNotFound = errors.New("stream not found")
|
||||
ErrorStreamAlreadyExists = errors.New("stream already exists")
|
||||
ErrorStreamChannelAlreadyExists = errors.New("stream channel already exists")
|
||||
ErrorStreamNotHLSSegments = errors.New("stream hls not ts seq found")
|
||||
ErrorStreamNoVideo = errors.New("stream no video")
|
||||
ErrorStreamNoClients = errors.New("stream no clients")
|
||||
ErrorStreamRestart = errors.New("stream restart")
|
||||
ErrorStreamStopCoreSignal = errors.New("stream stop core signal")
|
||||
ErrorStreamStopRTSPSignal = errors.New("stream stop rtsp signal")
|
||||
ErrorStreamChannelNotFound = errors.New("stream channel not found")
|
||||
ErrorStreamChannelCodecNotFound = errors.New("stream channel codec not ready, possible stream offline")
|
||||
ErrorStreamsLen0 = errors.New("streams len zero")
|
||||
)
|
||||
|
||||
//Config global
|
||||
var Config = loadConfig()
|
||||
|
||||
//ConfigST struct
|
||||
type ConfigST struct {
|
||||
mutex sync.RWMutex
|
||||
Server ServerST `json:"server"`
|
||||
Streams map[string]StreamST `json:"streams"`
|
||||
}
|
||||
|
||||
//ServerST struct
|
||||
type ServerST struct {
|
||||
HTTPPort string `json:"http_port"`
|
||||
}
|
||||
|
||||
//StreamST struct
|
||||
type StreamST struct {
|
||||
URL string `json:"url"`
|
||||
Broadcast BroadcastST `json:"broadcast"`
|
||||
Status bool `json:"status"`
|
||||
OnDemand bool `json:"on_demand"`
|
||||
RunLock bool `json:"-"`
|
||||
Codecs []av.CodecData
|
||||
Cl map[string]viewer
|
||||
}
|
||||
type BroadcastST struct {
|
||||
Enable bool `json:"enable"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
//Segment HLS cache section
|
||||
type Segment struct {
|
||||
dur time.Duration
|
||||
data []*av.Packet
|
||||
}
|
||||
|
||||
type viewer struct {
|
||||
c chan av.Packet
|
||||
}
|
||||
|
||||
func (element *ConfigST) RunIFNotRun(uuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok {
|
||||
if tmp.OnDemand && !tmp.RunLock {
|
||||
tmp.RunLock = true
|
||||
element.Streams[uuid] = tmp
|
||||
go RTSPWorkerLoop(uuid, tmp.URL, tmp.OnDemand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) RunUnlock(uuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok {
|
||||
if tmp.OnDemand && tmp.RunLock {
|
||||
tmp.RunLock = false
|
||||
element.Streams[uuid] = tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) HasViewer(uuid string) bool {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok && len(tmp.Cl) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loadConfig() *ConfigST {
|
||||
var tmp ConfigST
|
||||
data, err := ioutil.ReadFile("config.json")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
err = json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for i, v := range tmp.Streams {
|
||||
v.Cl = make(map[string]viewer)
|
||||
tmp.Streams[i] = v
|
||||
}
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (element *ConfigST) cast(uuid string, pck av.Packet) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
for _, v := range element.Streams[uuid].Cl {
|
||||
if len(v.c) < cap(v.c) {
|
||||
v.c <- pck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) ext(suuid string) bool {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
_, ok := element.Streams[suuid]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (element *ConfigST) coAd(suuid string, codecs []av.CodecData) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
t := element.Streams[suuid]
|
||||
t.Codecs = codecs
|
||||
element.Streams[suuid] = t
|
||||
}
|
||||
|
||||
func (element *ConfigST) coGe(suuid string) []av.CodecData {
|
||||
for i := 0; i < 100; i++ {
|
||||
element.mutex.RLock()
|
||||
tmp, ok := element.Streams[suuid]
|
||||
element.mutex.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if tmp.Codecs != nil {
|
||||
return tmp.Codecs
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (element *ConfigST) clAd(suuid string) (string, chan av.Packet) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
cuuid := pseudoUUID()
|
||||
ch := make(chan av.Packet, 100)
|
||||
element.Streams[suuid].Cl[cuuid] = viewer{c: ch}
|
||||
return cuuid, ch
|
||||
}
|
||||
|
||||
func (element *ConfigST) list() (string, []string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
var res []string
|
||||
var fist string
|
||||
for k := range element.Streams {
|
||||
if fist == "" {
|
||||
fist = k
|
||||
}
|
||||
res = append(res, k)
|
||||
}
|
||||
return fist, res
|
||||
}
|
||||
|
||||
func (element *ConfigST) clDe(suuid, cuuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
delete(element.Streams[suuid].Cl, cuuid)
|
||||
}
|
||||
|
||||
func (element *ConfigST) bcConfig(suuid string) (bool, string) {
|
||||
element.mutex.RLock()
|
||||
defer element.mutex.RUnlock()
|
||||
return element.Streams[suuid].Broadcast.Enable, element.Streams[suuid].Broadcast.URL
|
||||
}
|
||||
|
||||
func pseudoUUID() (uuid string) {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
return
|
||||
}
|
||||
uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
return
|
||||
}
|
12
config.json
Normal file
12
config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"streams": {
|
||||
"H264_AAC": {
|
||||
"on_demand": false,
|
||||
"url": "YOU_RTSP_CAMERA_URL",
|
||||
"broadcast": {
|
||||
"enable": true,
|
||||
"url": "rtmp://a.rtmp.youtube.com/live2/YOU_YOUTUBE_KEY"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
fakeAudio.go
Normal file
42
fakeAudio.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/codec/aacparser"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
)
|
||||
|
||||
type FakeAAC struct {
|
||||
closer chan bool
|
||||
OutgoingPacketQueue string
|
||||
}
|
||||
|
||||
func NewFakeAudio() *FakeAAC {
|
||||
return &FakeAAC{closer: make(chan bool)}
|
||||
}
|
||||
|
||||
func (obj *FakeAAC) Start(CodecData []av.CodecData, OutgoingPacketQueue chan *av.Packet) ([]av.CodecData, error) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(128 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-obj.closer:
|
||||
return
|
||||
case <-ticker.C:
|
||||
OutgoingPacketQueue <- &av.Packet{Duration: 128 * time.Millisecond, Idx: 1, CompositionTime: 1 * time.Millisecond, Data: []byte{1, 2, 52, 24, 67, 243, 0, 69, 107, 122, 169, 68, 50, 47, 46, 147, 26, 82, 161, 42, 80, 119, 97, 77, 199, 109, 20, 8, 26, 99, 53, 19, 85, 4, 186, 150, 69, 121, 208, 72, 42, 74, 224, 144, 172, 132, 146, 70, 246, 74, 13, 98, 76, 226, 5, 214, 169, 73, 18, 154, 33, 43, 11, 196, 2, 132, 43, 2, 240, 10, 14, 162, 64, 149, 243, 217, 96, 225, 85, 233, 90, 11, 19, 41, 67, 32, 5, 45, 5, 74, 72, 138, 215, 107, 33, 96, 49, 66, 85, 152, 150, 222, 87, 151, 89, 46, 80, 1, 172, 66, 150, 150, 150, 150, 150, 150, 150, 150, 150, 151, 7}}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
audioCodec, err := aacparser.NewCodecDataFromMPEG4AudioConfig(aacparser.MPEG4AudioConfig{SampleRate: 8000, SampleRateIndex: 11, ObjectType: 2, ChannelLayout: 1, ChannelConfig: 1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(CodecData, audioCodec), nil
|
||||
}
|
||||
|
||||
func (obj *FakeAAC) Close() {
|
||||
obj.closer <- true
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/deepch/RTSPtoWSMP4f
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/deepch/vdk v0.0.0-20210829103406-e99c71546c52
|
153
go.sum
Normal file
153
go.sum
Normal file
@@ -0,0 +1,153 @@
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepch/vdk v0.0.0-20210829103406-e99c71546c52 h1:4BU3M5FytIXd60lSNOQqshYpse1W8qi4NMvF30pLcgw=
|
||||
github.com/deepch/vdk v0.0.0-20210829103406-e99c71546c52/go.mod h1:LMmoiqSkkiwvrIpGiw+ZMsiw40xq3L7FadfAScgsVZE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
|
||||
github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
|
||||
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
|
||||
github.com/pion/ice v0.7.18/go.mod h1:+Bvnm3nYC6Nnp7VV6glUkuOfToB/AtMRZpOU8ihuf4c=
|
||||
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
|
||||
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
|
||||
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
|
||||
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
|
||||
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
|
||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc=
|
||||
github.com/pion/webrtc/v3 v3.0.29/go.mod h1:XFQeLYBf++bWWA0sJqh6zF1ouWluosxwTOMOoTZGaD0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
23
main.go
Normal file
23
main.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go serveStreams()
|
||||
sigs := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
log.Println(sig)
|
||||
done <- true
|
||||
}()
|
||||
log.Println("Server Start Awaiting Signal")
|
||||
<-done
|
||||
log.Println("Exiting")
|
||||
}
|
123
stream.go
Normal file
123
stream.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
|
||||
"github.com/deepch/vdk/format/rtmp"
|
||||
|
||||
"github.com/deepch/vdk/format/rtspv2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorStreamExitNoVideoOnStream = errors.New("Stream Exit No Video On Stream")
|
||||
ErrorStreamExitRtspDisconnect = errors.New("Stream Exit Rtsp Disconnect")
|
||||
ErrorStreamExitNoViewer = errors.New("Stream Exit On Demand No Viewer")
|
||||
)
|
||||
|
||||
func serveStreams() {
|
||||
for k, v := range Config.Streams {
|
||||
if v.OnDemand {
|
||||
log.Println("OnDemand not supported")
|
||||
v.OnDemand = false
|
||||
}
|
||||
if !v.OnDemand {
|
||||
go RTSPWorkerLoop(k, v.URL, v.OnDemand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RTSPWorkerLoop(name, url string, OnDemand bool) {
|
||||
defer Config.RunUnlock(name)
|
||||
for {
|
||||
log.Println(name, "Stream Try Connect")
|
||||
err := RTSPWorker(name, url, OnDemand)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if OnDemand && !Config.HasViewer(name) {
|
||||
log.Println(name, ErrorStreamExitNoViewer)
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func RTSPWorker(name, url string, OnDemand bool) error {
|
||||
keyTest := time.NewTimer(20 * time.Second)
|
||||
clientTest := time.NewTimer(20 * time.Second)
|
||||
RTSPClient, err := rtspv2.Dial(rtspv2.RTSPClientOptions{URL: url, DisableAudio: false, DialTimeout: 3 * time.Second, ReadWriteTimeout: 3 * time.Second, Debug: false})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer RTSPClient.Close()
|
||||
var connRTMP *rtmp.Conn
|
||||
broadCastEnable, broadCastURL := Config.bcConfig(name)
|
||||
if broadCastEnable {
|
||||
connRTMP, err = rtmp.DialTimeout(broadCastURL, 5*time.Second)
|
||||
//conn, err := rtmp.Dial("rtmp://live-ber.twitch.tv/app/live_55956025_liaYwOlnCZaZDkvVmcwGsTmznQqfhk")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer connRTMP.Close()
|
||||
}
|
||||
if len(RTSPClient.CodecData) < 2 {
|
||||
AudioStreamFake := NewFakeAudio()
|
||||
RTSPClient.CodecData, err = AudioStreamFake.Start(RTSPClient.CodecData, RTSPClient.OutgoingPacketQueue)
|
||||
defer AudioStreamFake.Close()
|
||||
} else if RTSPClient.CodecData[1].Type() != av.AAC {
|
||||
log.Fatalln("Only AAC or Disable Audio")
|
||||
}
|
||||
if broadCastEnable {
|
||||
err = connRTMP.WriteHeader(RTSPClient.CodecData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if RTSPClient.CodecData != nil {
|
||||
Config.coAd(name, RTSPClient.CodecData)
|
||||
}
|
||||
var start bool
|
||||
var lineA time.Duration
|
||||
var lineV time.Duration
|
||||
for {
|
||||
select {
|
||||
case <-clientTest.C:
|
||||
if OnDemand && !Config.HasViewer(name) {
|
||||
return ErrorStreamExitNoViewer
|
||||
}
|
||||
case <-keyTest.C:
|
||||
return ErrorStreamExitNoVideoOnStream
|
||||
case signals := <-RTSPClient.Signals:
|
||||
switch signals {
|
||||
case rtspv2.SignalCodecUpdate:
|
||||
Config.coAd(name, RTSPClient.CodecData)
|
||||
case rtspv2.SignalStreamRTPStop:
|
||||
return ErrorStreamExitRtspDisconnect
|
||||
}
|
||||
case packetAV := <-RTSPClient.OutgoingPacketQueue:
|
||||
if packetAV.IsKeyFrame {
|
||||
keyTest.Reset(20 * time.Second)
|
||||
}
|
||||
if packetAV.IsKeyFrame && !start {
|
||||
start = true
|
||||
}
|
||||
if packetAV.Idx == 0 {
|
||||
lineV += packetAV.Duration
|
||||
packetAV.Time = lineV
|
||||
}
|
||||
if packetAV.Idx == 1 {
|
||||
lineA += packetAV.Duration
|
||||
packetAV.Time = lineA
|
||||
}
|
||||
if broadCastEnable && start {
|
||||
if err = connRTMP.WritePacket(*packetAV); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user