mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 18:46:11 +00:00
144 lines
2.8 KiB
Go
144 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 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")
|
|
}
|
|
}
|
|
}
|