cloud-game/pkg/encoder/encoder.go
2024-03-08 18:43:23 +03:00

146 lines
2.8 KiB
Go

package encoder
import (
"fmt"
"sync/atomic"
"github.com/giongto35/cloud-game/v3/pkg/config"
"github.com/giongto35/cloud-game/v3/pkg/encoder/h264"
"github.com/giongto35/cloud-game/v3/pkg/encoder/vpx"
"github.com/giongto35/cloud-game/v3/pkg/encoder/yuv"
"github.com/giongto35/cloud-game/v3/pkg/logger"
)
type (
InFrame yuv.RawFrame
OutFrame []byte
Encoder interface {
Encode([]byte) []byte
IntraRefresh()
Info() string
SetFlip(bool)
Shutdown() error
}
)
type Video struct {
codec Encoder
log *logger.Logger
stopped atomic.Bool
y yuv.Conv
pf yuv.PixFmt
rot uint
}
type VideoCodec string
const (
H264 VideoCodec = "h264"
VP8 VideoCodec = "vp8"
VP9 VideoCodec = "vp9"
VPX VideoCodec = "vpx"
)
// NewVideoEncoder returns new video encoder.
// By default, it waits for RGBA images on the input channel,
// converts them into YUV I420 format,
// encodes with provided video encoder, and
// puts the result into the output channel.
func NewVideoEncoder(w, h, dw, dh int, scale float64, conf config.Video, log *logger.Logger) (*Video, error) {
var enc Encoder
var err error
codec := VideoCodec(conf.Codec)
switch codec {
case H264:
opts := h264.Options(conf.H264)
enc, err = h264.NewEncoder(dw, dh, conf.Threads, &opts)
case VP8, VP9, VPX:
opts := vpx.Options(conf.Vpx)
v := 8
if codec == VP9 {
v = 9
}
enc, err = vpx.NewEncoder(dw, dh, conf.Threads, v, &opts)
default:
err = fmt.Errorf("unsupported codec: %v", conf.Codec)
}
if err != nil {
return nil, err
}
if enc == nil {
return nil, fmt.Errorf("no encoder")
}
return &Video{codec: enc, y: yuv.NewYuvConv(w, h, scale), log: log}, nil
}
func (v *Video) Encode(frame InFrame) OutFrame {
if v.stopped.Load() {
return nil
}
yCbCr := v.y.Process(yuv.RawFrame(frame), v.rot, v.pf)
defer v.y.Put(&yCbCr)
if bytes := v.codec.Encode(yCbCr); len(bytes) > 0 {
return bytes
}
return nil
}
func (v *Video) Info() string {
return fmt.Sprintf("%v, libyuv: %v", v.codec.Info(), v.y.Version())
}
func (v *Video) SetPixFormat(f uint32) {
if v == nil {
return
}
switch f {
case 0:
v.pf = yuv.PixFmt(yuv.FourccRgb0)
case 1:
v.pf = yuv.PixFmt(yuv.FourccArgb)
case 2:
v.pf = yuv.PixFmt(yuv.FourccRgbp)
default:
v.pf = yuv.PixFmt(yuv.FourccAbgr)
}
}
// SetRot sets the de-rotation angle of the frames.
func (v *Video) SetRot(a uint) {
if v == nil {
return
}
if a > 0 {
v.rot = (a + 180) % 360
}
}
// SetFlip tells the encoder to flip the frames vertically.
func (v *Video) SetFlip(b bool) {
if v == nil {
return
}
v.codec.SetFlip(b)
}
func (v *Video) Stop() {
if v == nil {
return
}
if v.stopped.Swap(true) {
return
}
v.rot = 0
defer func() { v.codec = nil }()
if err := v.codec.Shutdown(); err != nil {
if v.log != nil {
v.log.Error().Err(err).Msg("failed to close the encoder")
}
}
}