2026-03-05 14:45:15 -06:00
|
|
|
//go:build cgo
|
|
|
|
|
|
|
|
|
|
package capture
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2026-03-06 13:27:51 -06:00
|
|
|
"fmt"
|
2026-03-05 14:45:15 -06:00
|
|
|
"image"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
x264 "github.com/gen2brain/x264-go"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
2026-03-06 13:27:51 -06:00
|
|
|
h264Mu sync.Mutex
|
|
|
|
|
h264Enc *x264.Encoder
|
|
|
|
|
h264Buf bytes.Buffer
|
|
|
|
|
h264Width int
|
|
|
|
|
h264Height int
|
|
|
|
|
h264LastErr error
|
2026-03-05 14:45:15 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func encodeH264Frame(img *image.RGBA) ([]byte, error) {
|
|
|
|
|
bounds := img.Bounds()
|
|
|
|
|
width := bounds.Dx()
|
|
|
|
|
height := bounds.Dy()
|
|
|
|
|
|
|
|
|
|
h264Mu.Lock()
|
|
|
|
|
defer h264Mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if err := ensureH264EncoderLocked(width, height); err != nil {
|
2026-03-06 13:27:51 -06:00
|
|
|
h264LastErr = err
|
2026-03-05 14:45:15 -06:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h264Buf.Reset()
|
|
|
|
|
if err := h264Enc.Encode(img); err != nil {
|
2026-03-06 13:27:51 -06:00
|
|
|
h264LastErr = err
|
2026-03-05 14:45:15 -06:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-06 13:27:51 -06:00
|
|
|
h264LastErr = nil
|
2026-03-05 14:45:15 -06:00
|
|
|
|
|
|
|
|
out := make([]byte, h264Buf.Len())
|
|
|
|
|
copy(out, h264Buf.Bytes())
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func h264Available() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 13:27:51 -06:00
|
|
|
func h264AvailabilityDetail() string {
|
|
|
|
|
h264Mu.Lock()
|
|
|
|
|
defer h264Mu.Unlock()
|
|
|
|
|
if h264LastErr != nil {
|
|
|
|
|
return fmt.Sprintf("x264 runtime error: %v", h264LastErr)
|
|
|
|
|
}
|
|
|
|
|
return "cgo build with x264-go"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 14:45:15 -06:00
|
|
|
func ensureH264EncoderLocked(width, height int) error {
|
|
|
|
|
if h264Enc != nil && h264Width == width && h264Height == height {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closeH264EncoderLocked()
|
|
|
|
|
|
|
|
|
|
opts := &x264.Options{
|
2026-03-06 18:47:12 -06:00
|
|
|
Width: width,
|
|
|
|
|
Height: height,
|
|
|
|
|
FrameRate: 120,
|
|
|
|
|
Tune: "zerolatency,fastdecode",
|
|
|
|
|
Preset: "ultrafast",
|
|
|
|
|
Profile: "baseline",
|
|
|
|
|
RateControl: "crf",
|
|
|
|
|
RateConstant: 30,
|
|
|
|
|
LogLevel: x264.LogError,
|
2026-03-05 14:45:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enc, err := x264.NewEncoder(&h264Buf, opts)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h264Enc = enc
|
|
|
|
|
h264Width = width
|
|
|
|
|
h264Height = height
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resetH264Encoder() {
|
|
|
|
|
h264Mu.Lock()
|
|
|
|
|
defer h264Mu.Unlock()
|
|
|
|
|
closeH264EncoderLocked()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func closeH264EncoderLocked() {
|
|
|
|
|
if h264Enc != nil {
|
|
|
|
|
_ = h264Enc.Close()
|
|
|
|
|
h264Enc = nil
|
|
|
|
|
}
|
|
|
|
|
h264Width = 0
|
|
|
|
|
h264Height = 0
|
|
|
|
|
h264Buf.Reset()
|
|
|
|
|
}
|