mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2026-04-22 23:57:20 +08:00
ea03aa832d
- implement WebP streaming with multipart support - add snapshot endpoint for WebP format with quality options - introduce WebP encoding using pure Go library without FFmpeg - update documentation and links for new WebP features
161 lines
3.5 KiB
Go
161 lines
3.5 KiB
Go
package webp
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AlexxIT/go2rtc/internal/api"
|
|
"github.com/AlexxIT/go2rtc/internal/app"
|
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/magic"
|
|
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
|
|
"github.com/AlexxIT/go2rtc/pkg/webp"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func Init() {
|
|
api.HandleFunc("api/frame.webp", handlerKeyframe)
|
|
api.HandleFunc("api/stream.webp", handlerStream)
|
|
|
|
log = app.GetLogger("webp")
|
|
}
|
|
|
|
var log zerolog.Logger
|
|
|
|
var cache map[string]cacheEntry
|
|
var cacheMu sync.Mutex
|
|
|
|
type cacheEntry struct {
|
|
payload []byte
|
|
timestamp time.Time
|
|
}
|
|
|
|
func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
|
|
query := r.URL.Query()
|
|
stream, _ := streams.GetOrPatch(query)
|
|
if stream == nil {
|
|
http.Error(w, api.StreamNotFound, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
quality := 75
|
|
if s := query.Get("quality"); s != "" {
|
|
if q, err := strconv.Atoi(s); err == nil && q > 0 && q <= 100 {
|
|
quality = q
|
|
}
|
|
}
|
|
|
|
var b []byte
|
|
|
|
if s := query.Get("cache"); s != "" {
|
|
if timeout, err := time.ParseDuration(s); err == nil {
|
|
src := query.Get("src")
|
|
|
|
cacheMu.Lock()
|
|
entry, found := cache[src]
|
|
cacheMu.Unlock()
|
|
|
|
if found && time.Since(entry.timestamp) < timeout {
|
|
writeWebPResponse(w, entry.payload)
|
|
return
|
|
}
|
|
|
|
defer func() {
|
|
if b == nil {
|
|
return
|
|
}
|
|
entry = cacheEntry{payload: b, timestamp: time.Now()}
|
|
cacheMu.Lock()
|
|
if cache == nil {
|
|
cache = map[string]cacheEntry{src: entry}
|
|
} else {
|
|
cache[src] = entry
|
|
}
|
|
cacheMu.Unlock()
|
|
}()
|
|
}
|
|
}
|
|
|
|
cons := magic.NewKeyframe()
|
|
cons.WithRequest(r)
|
|
|
|
if err := stream.AddConsumer(cons); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
return
|
|
}
|
|
|
|
once := &core.OnceBuffer{}
|
|
_, _ = cons.WriteTo(once)
|
|
b = once.Buffer()
|
|
|
|
stream.RemoveConsumer(cons)
|
|
|
|
var err error
|
|
switch cons.CodecName() {
|
|
case core.CodecH264, core.CodecH265:
|
|
ts := time.Now()
|
|
var jpegBytes []byte
|
|
if jpegBytes, err = ffmpeg.JPEGWithQuery(b, query); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Debug().Msgf("[webp] transcoding time=%s", time.Since(ts))
|
|
if b, err = webp.EncodeJPEG(jpegBytes, quality); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
case core.CodecJPEG:
|
|
fixed := mjpeg.FixJPEG(b)
|
|
if b, err = webp.EncodeJPEG(fixed, quality); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
writeWebPResponse(w, b)
|
|
}
|
|
|
|
func writeWebPResponse(w http.ResponseWriter, b []byte) {
|
|
h := w.Header()
|
|
h.Set("Content-Type", "image/webp")
|
|
h.Set("Content-Length", strconv.Itoa(len(b)))
|
|
h.Set("Cache-Control", "no-cache")
|
|
h.Set("Connection", "close")
|
|
h.Set("Pragma", "no-cache")
|
|
|
|
if _, err := w.Write(b); err != nil {
|
|
log.Error().Err(err).Caller().Send()
|
|
}
|
|
}
|
|
|
|
func handlerStream(w http.ResponseWriter, r *http.Request) {
|
|
src := r.URL.Query().Get("src")
|
|
stream := streams.Get(src)
|
|
if stream == nil {
|
|
http.Error(w, api.StreamNotFound, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
cons := webp.NewConsumer()
|
|
cons.WithRequest(r)
|
|
|
|
if err := stream.AddConsumer(cons); err != nil {
|
|
log.Error().Err(err).Msg("[api.webp] add consumer")
|
|
return
|
|
}
|
|
|
|
h := w.Header()
|
|
h.Set("Cache-Control", "no-cache")
|
|
h.Set("Connection", "close")
|
|
h.Set("Pragma", "no-cache")
|
|
|
|
wr := webp.NewWriter(w)
|
|
_, _ = cons.WriteTo(wr)
|
|
|
|
stream.RemoveConsumer(cons)
|
|
}
|