demo live stream
This commit is contained in:
@@ -10,6 +10,12 @@ import (
|
||||
"github.com/go-gst/go-gst/gst/app"
|
||||
)
|
||||
|
||||
const (
|
||||
liveFrameTypeInit = 0x01
|
||||
liveFrameTypeMedia = 0x02
|
||||
liveFrameTypeError = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
liveSampleTimeout = 200 * time.Millisecond
|
||||
liveSampleBufferSize = 5
|
||||
@@ -97,7 +103,7 @@ func (p *CameraPipeline) addLiveBranch(branchTimeout time.Duration) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create mp4mux element: %w", err)
|
||||
}
|
||||
if err := mp4Mux.SetProperty("fragment-duration", uint(100*time.Millisecond.Nanoseconds())); err != nil {
|
||||
if err := mp4Mux.SetProperty("fragment-duration", uint(100)); err != nil {
|
||||
return fmt.Errorf("failed to set mp4mux fragment-duration property: %w", err)
|
||||
}
|
||||
if err := setGlibValueProperty(mp4Mux, "fragment-mode", 0); err != nil { // dash-or-mss
|
||||
@@ -193,6 +199,8 @@ SamplerLoop:
|
||||
func (p *CameraPipeline) liveManager(ctx context.Context) {
|
||||
active := false
|
||||
var initSeg []byte
|
||||
assembler := newFMP4Assembler()
|
||||
var lastMediaEmit time.Time
|
||||
subscribers := &subscribersManager{m: make(map[int]subscriber), nextID: 0}
|
||||
samples := make(chan []byte, liveSampleBufferSize)
|
||||
samplerCtx, samplerCancel := context.WithCancel(ctx)
|
||||
@@ -217,6 +225,9 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
|
||||
case req := <-p.liveSubscribe:
|
||||
if !active {
|
||||
p.log.Info("Activating live branch")
|
||||
initSeg = nil
|
||||
assembler = newFMP4Assembler()
|
||||
lastMediaEmit = time.Time{}
|
||||
if err := p.liveVValve.SetProperty("drop", false); err != nil {
|
||||
req.Result <- subscribeRes{Err: fmt.Errorf("failed to activate video valve: %w", err)}
|
||||
continue
|
||||
@@ -244,10 +255,29 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
|
||||
case req := <-p.liveUnsubscribe:
|
||||
subscribers.RemoveSubscriber(req.Id)
|
||||
case data := <-samples:
|
||||
// TODO : init segment
|
||||
p.log.Debug("got video sample")
|
||||
chunks, err := assembler.Push(data)
|
||||
if err != nil {
|
||||
p.log.Warn("Failed to assemble live fMP4 chunks", "error", err)
|
||||
continue
|
||||
}
|
||||
numSamples += 1
|
||||
subscribers.Broadcast(data)
|
||||
for _, chunk := range chunks {
|
||||
framed := marshalLiveFrame(chunk.FrameType, chunk.Payload)
|
||||
switch chunk.FrameType {
|
||||
case liveFrameTypeInit:
|
||||
initSeg = framed
|
||||
p.log.Debug("Emitted init segment", "size", len(chunk.Payload))
|
||||
case liveFrameTypeMedia:
|
||||
now := time.Now()
|
||||
if !lastMediaEmit.IsZero() {
|
||||
p.log.Debug("Emitted media segment", "size", len(chunk.Payload), "gap", now.Sub(lastMediaEmit))
|
||||
} else {
|
||||
p.log.Debug("Emitted media segment", "size", len(chunk.Payload))
|
||||
}
|
||||
lastMediaEmit = now
|
||||
}
|
||||
subscribers.Broadcast(framed)
|
||||
}
|
||||
case <-timeout:
|
||||
p.log.Info("Deactivating live branch due to inactivity")
|
||||
if err := p.liveVValve.SetProperty("drop", true); err != nil {
|
||||
@@ -263,6 +293,13 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func marshalLiveFrame(frameType byte, payload []byte) []byte {
|
||||
framed := make([]byte, len(payload)+1)
|
||||
framed[0] = frameType
|
||||
copy(framed[1:], payload)
|
||||
return framed
|
||||
}
|
||||
|
||||
func (p *CameraPipeline) LiveSubscribe(timeout time.Duration, bufferSize int) (id int, stream <-chan *bytes.Reader, err error) {
|
||||
result := make(chan subscribeRes)
|
||||
req := subscribeReq{
|
||||
|
||||
Reference in New Issue
Block a user