112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package ingest
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/go-gst/go-gst/gst"
|
|
)
|
|
|
|
// addStatsBranch populates the pipeline with:
|
|
//
|
|
// [vTee] ---> fakesink
|
|
//
|
|
// StreamStats are extracted from the fakesink's handovers (using the sink pad caps) and sent to the returned channel.
|
|
// If the channel has no received or is full, the stats are dropped to avoid blocking the pipeline. The channel is never closed.
|
|
func (p *CameraPipeline) addStatsBranch() (<-chan StreamStats, error) {
|
|
if p.vTee == nil {
|
|
return nil, fmt.Errorf("video tee not initialized")
|
|
}
|
|
vPad := p.vTee.GetRequestPad("src_%u")
|
|
if vPad == nil {
|
|
return nil, fmt.Errorf("failed to get request pad from video tee")
|
|
}
|
|
|
|
statsChan := make(chan StreamStats)
|
|
fakeSink, err := gst.NewElement("fakesink")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create fakesink element: %w", err)
|
|
}
|
|
if err := fakeSink.SetProperty("signal-handoffs", true); err != nil {
|
|
return nil, fmt.Errorf("failed to set fakesink signal-handoffs property: %w", err)
|
|
}
|
|
|
|
if err := p.pipeline.Add(fakeSink); err != nil {
|
|
return nil, fmt.Errorf("failed to add fakesink to pipeline: %w", err)
|
|
}
|
|
|
|
if res := vPad.Link(fakeSink.GetStaticPad("sink")); res != gst.PadLinkOK {
|
|
return nil, fmt.Errorf("failed to link video tee to fakesink: %s", res)
|
|
}
|
|
|
|
fakeSink.Connect("handoff", func(element *gst.Element, _ *gst.Buffer, pad *gst.Pad) {
|
|
caps := pad.GetCurrentCaps()
|
|
if caps == nil || caps.GetSize() == 0 {
|
|
p.log.Error("Stats pad has no or empty caps")
|
|
return
|
|
}
|
|
if caps.GetSize() > 1 {
|
|
// Note: should be impossible
|
|
p.log.Warn("Stats pad has multiple structures, using the first one", "caps", caps.String())
|
|
}
|
|
structure := caps.GetStructureAt(0)
|
|
if structure == nil {
|
|
p.log.Error("Stats pad received nil structure")
|
|
return
|
|
}
|
|
|
|
rawWidth, err := structure.GetValue("width")
|
|
if err != nil {
|
|
p.log.Error("Failed to get width from stats pad caps", "error", err)
|
|
return
|
|
}
|
|
width, ok := rawWidth.(int)
|
|
if !ok {
|
|
p.log.Error("Width field in stats pad caps is not an int", "value", rawWidth)
|
|
return
|
|
}
|
|
|
|
rawHeight, err := structure.GetValue("height")
|
|
if err != nil {
|
|
p.log.Error("Failed to get height from stats pad caps", "error", err)
|
|
return
|
|
}
|
|
height, ok := rawHeight.(int)
|
|
if !ok {
|
|
p.log.Error("Height field in stats pad caps is not an int", "value", rawHeight)
|
|
return
|
|
}
|
|
|
|
fps, err := structure.GetValue("framerate")
|
|
if err != nil {
|
|
p.log.Error("Failed to get framerate from stats pad caps", "error", err)
|
|
return
|
|
}
|
|
|
|
var fpsFloat float64
|
|
switch v := fps.(type) {
|
|
case *gst.FractionValue:
|
|
fpsFloat = float64(v.Num()) / float64(v.Denom())
|
|
case gst.FractionValue:
|
|
fpsFloat = float64(v.Num()) / float64(v.Denom())
|
|
case float64:
|
|
fpsFloat = v
|
|
default:
|
|
p.log.Error("Framerate field in stats pad caps is not a fraction or float", "type", fmt.Sprintf("%T", fps), "value", fps)
|
|
return
|
|
}
|
|
|
|
select {
|
|
|
|
case statsChan <- StreamStats{
|
|
FPS: fpsFloat,
|
|
Width: width,
|
|
Height: height,
|
|
}:
|
|
default:
|
|
p.log.Warn("Stats channel is full, dropping stats update")
|
|
}
|
|
return
|
|
})
|
|
return statsChan, nil
|
|
}
|