Files
AI-tianyan/internal/stream/ingestor.go
fyah f6a85d7dfc init: tianyan-edge edge agent with Ascend NPU inference backend
- Go main process: stream ingestor, infer client, event uploader,
  heartbeat, config agent, OTA agent
- Python inference server rewritten to use CANN ACL (acl module)
  replacing ultralytics/PyTorch; supports YOLOv8 raw and YOLOv10
  NMS-free output formats via OUTPUT_FORMAT env var
- systemd services for edge-agent and edge-infer
- build/install/package scripts targeting arm64

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 21:49:04 +08:00

130 lines
2.6 KiB
Go

package stream
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os/exec"
"sync"
"time"
"tianyan-edge/internal/config"
)
type Frame struct {
StreamID int
DeviceID string
URL string
JPEG []byte
TS float64
}
type Ingestor struct {
cfg *config.Config
frames chan<- Frame
}
func NewIngestor(cfg *config.Config, frames chan<- Frame) *Ingestor {
return &Ingestor{cfg: cfg, frames: frames}
}
func (ing *Ingestor) Run(ctx context.Context) {
if len(ing.cfg.RTSPURLs) == 0 {
log.Println("stream: no RTSP URLs configured, idle")
<-ctx.Done()
return
}
var wg sync.WaitGroup
for i, url := range ing.cfg.RTSPURLs {
wg.Add(1)
go func(idx int, u string) {
defer wg.Done()
ing.streamLoop(ctx, idx, u)
}(i, url)
}
wg.Wait()
}
func (ing *Ingestor) streamLoop(ctx context.Context, idx int, url string) {
deviceID := fmt.Sprintf("cam-%03d", idx)
fps := ing.cfg.InferFPS
if fps <= 0 {
fps = 5
}
for {
select {
case <-ctx.Done():
return
default:
}
cmd := exec.CommandContext(ctx, "ffmpeg",
"-rtsp_transport", "tcp", "-i", url,
"-vf", fmt.Sprintf("fps=%d", fps),
"-f", "image2pipe", "-vcodec", "mjpeg", "-q:v", "5", "-",
)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Printf("stream[%d] stdout pipe failed: %v, retry 10s", idx, err)
time.Sleep(10 * time.Second)
continue
}
if err := cmd.Start(); err != nil {
log.Printf("stream[%d] ffmpeg start failed: %v, retry 10s", idx, err)
time.Sleep(10 * time.Second)
continue
}
log.Printf("stream[%d] connected url=%s", idx, url)
ing.readFrames(ctx, stdout, idx, deviceID, url)
cmd.Wait()
select {
case <-ctx.Done():
return
default:
log.Printf("stream[%d] lost, retry 5s", idx)
time.Sleep(5 * time.Second)
}
}
}
func (ing *Ingestor) readFrames(ctx context.Context, r io.Reader, idx int, deviceID, url string) {
soi, eoi := []byte{0xFF, 0xD8}, []byte{0xFF, 0xD9}
buf, tmp := make([]byte, 0, 1<<20), make([]byte, 32768)
for {
select {
case <-ctx.Done():
return
default:
}
n, err := r.Read(tmp)
if n > 0 {
buf = append(buf, tmp[:n]...)
for {
s := bytes.Index(buf, soi)
if s < 0 {
buf = buf[:0]
break
}
buf = buf[s:]
e := bytes.Index(buf[2:], eoi)
if e < 0 {
break
}
end := e + 4
frame := make([]byte, end)
copy(frame, buf[:end])
buf = buf[end:]
select {
case ing.frames <- Frame{StreamID: idx, DeviceID: deviceID, URL: url,
JPEG: frame, TS: float64(time.Now().UnixMilli()) / 1000.0}:
default:
}
}
}
if err != nil {
return
}
}
}