mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-17 16:50:31 +00:00
Add new params cpuUsed: 0 tileColumns: 0 tune: screen In VP9 it will use multithreaded encoding now. Keyframe interval was not set properly wasting bitrate. By default. It is changed to 120 frames instead of 5.
283 lines
7 KiB
Go
283 lines
7 KiB
Go
package vpx
|
||
|
||
/*
|
||
#cgo pkg-config: vpx
|
||
#cgo st LDFLAGS: -l:libvpx.a
|
||
|
||
#include "vpx/vpx_encoder.h"
|
||
#include "vpx/vpx_image.h"
|
||
#include "vpx/vp8cx.h"
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#define VP8_FOURCC 0x30385056
|
||
#define VP9_FOURCC 0x30395056
|
||
|
||
#ifndef VP9E_TUNE_SCREEN
|
||
#define VP9E_TUNE_SCREEN 1
|
||
#endif
|
||
|
||
typedef struct VpxInterface {
|
||
const char *const name;
|
||
const uint32_t fourcc;
|
||
vpx_codec_iface_t *(*const codec_interface)();
|
||
} VpxInterface;
|
||
|
||
typedef struct FrameBuffer {
|
||
void *ptr;
|
||
int size;
|
||
} FrameBuffer;
|
||
|
||
vpx_codec_err_t call_vpx_codec_enc_config_default(const VpxInterface *encoder, vpx_codec_enc_cfg_t *cfg) {
|
||
return vpx_codec_enc_config_default(encoder->codec_interface(), cfg, 0);
|
||
}
|
||
vpx_codec_err_t call_vpx_codec_enc_init(vpx_codec_ctx_t *codec, const VpxInterface *encoder, vpx_codec_enc_cfg_t *cfg) {
|
||
return vpx_codec_enc_init(codec, encoder->codec_interface(), cfg, 0);
|
||
}
|
||
|
||
FrameBuffer get_frame_buffer(vpx_codec_ctx_t *codec, vpx_codec_iter_t *iter) {
|
||
// iter has set to NULL when after add new image
|
||
FrameBuffer fb = {NULL, 0};
|
||
const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data(codec, iter);
|
||
if (pkt != NULL && pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
|
||
fb.ptr = pkt->data.frame.buf;
|
||
fb.size = pkt->data.frame.sz;
|
||
}
|
||
return fb;
|
||
}
|
||
|
||
vpx_codec_err_t set_vpx_cpu_used(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP8E_SET_CPUUSED, value);
|
||
}
|
||
|
||
vpx_codec_err_t set_vp9_tile_columns(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP9E_SET_TILE_COLUMNS, value);
|
||
}
|
||
|
||
vpx_codec_err_t set_vp9_tune_content(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP9E_SET_TUNE_CONTENT, value);
|
||
}
|
||
|
||
vpx_codec_err_t set_vp9_row_mt(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP9E_SET_ROW_MT, value);
|
||
}
|
||
|
||
vpx_codec_err_t set_vp8_auto_alt_ref(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP8E_SET_ENABLEAUTOALTREF, value);
|
||
}
|
||
|
||
vpx_codec_err_t set_vp8_noise_sensitivity(vpx_codec_ctx_t *ctx, int value) {
|
||
return vpx_codec_control_(ctx, VP8E_SET_NOISE_SENSITIVITY, value);
|
||
}
|
||
|
||
const VpxInterface vpx_encoders[] = {
|
||
{ "vp8", VP8_FOURCC, &vpx_codec_vp8_cx },
|
||
{ "vp9", VP9_FOURCC, &vpx_codec_vp9_cx },
|
||
};
|
||
|
||
int vpx_img_plane_width(const vpx_image_t *img, int plane) {
|
||
if (plane > 0 && img->x_chroma_shift > 0)
|
||
return (img->d_w + 1) >> img->x_chroma_shift;
|
||
else
|
||
return img->d_w;
|
||
}
|
||
|
||
int vpx_img_plane_height(const vpx_image_t *img, int plane) {
|
||
if (plane > 0 && img->y_chroma_shift > 0)
|
||
return (img->d_h + 1) >> img->y_chroma_shift;
|
||
else
|
||
return img->d_h;
|
||
}
|
||
|
||
void vpx_img_read(vpx_image_t *dst, void *src) {
|
||
for (int plane = 0; plane < 3; ++plane) {
|
||
unsigned char *buf = dst->planes[plane];
|
||
const int stride = dst->stride[plane];
|
||
const int w = vpx_img_plane_width(dst, plane);
|
||
const int h = vpx_img_plane_height(dst, plane);
|
||
|
||
for (int y = 0; y < h; ++y) {
|
||
memcpy(buf, src, w);
|
||
buf += stride;
|
||
src += w;
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
import "C"
|
||
import (
|
||
"fmt"
|
||
"runtime"
|
||
"unsafe"
|
||
)
|
||
|
||
type Vpx struct {
|
||
frameCount C.int
|
||
image C.vpx_image_t
|
||
codecCtx C.vpx_codec_ctx_t
|
||
kfi C.int
|
||
flipped bool
|
||
v int
|
||
}
|
||
|
||
func (vpx *Vpx) SetFlip(b bool) { vpx.flipped = b }
|
||
|
||
func autoThreads(configured, cpus int) int {
|
||
if configured != 0 {
|
||
return configured
|
||
}
|
||
reserve := cpus / 4
|
||
if reserve < 1 {
|
||
reserve = 1
|
||
}
|
||
if t := cpus - reserve; t > 0 {
|
||
return t
|
||
}
|
||
return 1
|
||
}
|
||
|
||
type Options struct {
|
||
// Target bandwidth to use for this stream, in kilobits per second.
|
||
Bitrate uint
|
||
// Force keyframe interval.
|
||
KeyframeInterval uint
|
||
// Speed/quality tradeoff (0=auto, 1–8 manual). Defaults to 0.
|
||
// Applies to both VP8 and VP9.
|
||
CpuUsed int
|
||
// Log2 of tile columns for VP9 frame-level parallelism (0–6). VP9 only.
|
||
TileColumns int
|
||
// Content tuning: "screen" for game/screen content. VP9 only.
|
||
Tune string
|
||
}
|
||
|
||
func NewEncoder(w, h int, th int, version int, opts *Options) (*Vpx, error) {
|
||
idx := 0
|
||
if version == 9 {
|
||
idx = 1
|
||
}
|
||
encoder := &C.vpx_encoders[idx]
|
||
if encoder == nil {
|
||
return nil, fmt.Errorf("couldn't get the encoder")
|
||
}
|
||
|
||
if opts == nil {
|
||
opts = &Options{
|
||
Bitrate: 1200,
|
||
KeyframeInterval: 120,
|
||
TileColumns: 2,
|
||
Tune: "screen",
|
||
}
|
||
}
|
||
|
||
vpx := Vpx{
|
||
frameCount: C.int(0),
|
||
kfi: C.int(opts.KeyframeInterval),
|
||
v: version,
|
||
}
|
||
|
||
if C.vpx_img_alloc(&vpx.image, C.VPX_IMG_FMT_I420, C.uint(w), C.uint(h), 1) == nil {
|
||
return nil, fmt.Errorf("vpx_img_alloc failed")
|
||
}
|
||
|
||
var cfg C.vpx_codec_enc_cfg_t
|
||
if C.call_vpx_codec_enc_config_default(encoder, &cfg) != 0 {
|
||
return nil, fmt.Errorf("failed to get default codec config")
|
||
}
|
||
|
||
cfg.g_w = C.uint(w)
|
||
cfg.g_h = C.uint(h)
|
||
cfg.g_threads = C.uint(autoThreads(th, runtime.NumCPU()))
|
||
cfg.g_lag_in_frames = 0
|
||
cfg.rc_target_bitrate = C.uint(opts.Bitrate)
|
||
cfg.g_error_resilient = 0
|
||
|
||
if C.call_vpx_codec_enc_init(&vpx.codecCtx, encoder, &cfg) != 0 {
|
||
return nil, fmt.Errorf("failed to initialize encoder")
|
||
}
|
||
|
||
// Speed/quality tradeoff: higher = faster, lower = better quality.
|
||
if opts.CpuUsed == 0 {
|
||
thr := autoThreads(th, runtime.NumCPU())
|
||
switch {
|
||
case thr <= 2:
|
||
opts.CpuUsed = 8
|
||
case thr <= 4:
|
||
opts.CpuUsed = 7
|
||
default:
|
||
opts.CpuUsed = 6
|
||
}
|
||
}
|
||
C.set_vpx_cpu_used(&vpx.codecCtx, C.int(opts.CpuUsed))
|
||
|
||
// VP9-specific tuning.
|
||
if version == 9 {
|
||
if opts.TileColumns == 0 {
|
||
thr := autoThreads(th, runtime.NumCPU())
|
||
if thr >= 4 {
|
||
opts.TileColumns = 2
|
||
} else if thr >= 2 {
|
||
opts.TileColumns = 1
|
||
}
|
||
}
|
||
if opts.TileColumns > 0 {
|
||
C.set_vp9_tile_columns(&vpx.codecCtx, C.int(opts.TileColumns))
|
||
}
|
||
if opts.Tune == "screen" {
|
||
C.set_vp9_tune_content(&vpx.codecCtx, C.VP9E_TUNE_SCREEN)
|
||
}
|
||
C.set_vp9_row_mt(&vpx.codecCtx, 1)
|
||
}
|
||
|
||
// VP8 real-time tuning: disable features that add latency.
|
||
if version == 8 {
|
||
// No alt-ref frames for zero-latency.
|
||
C.set_vp8_auto_alt_ref(&vpx.codecCtx, 0)
|
||
// Reduce noise sensitivity — game content is low-noise.
|
||
C.set_vp8_noise_sensitivity(&vpx.codecCtx, 0)
|
||
}
|
||
|
||
return &vpx, nil
|
||
}
|
||
|
||
// Encode encodes yuv image with the VPX8 encoder.
|
||
// see: https://chromium.googlesource.com/webm/libvpx/+/master/examples/simple_encoder.c
|
||
func (vpx *Vpx) Encode(yuv []byte) []byte {
|
||
C.vpx_img_read(&vpx.image, unsafe.Pointer(&yuv[0]))
|
||
if vpx.flipped {
|
||
C.vpx_img_flip(&vpx.image)
|
||
}
|
||
|
||
var flags C.int
|
||
if vpx.kfi > 0 && vpx.frameCount%vpx.kfi == 0 {
|
||
flags |= C.VPX_EFLAG_FORCE_KF
|
||
}
|
||
if C.vpx_codec_encode(&vpx.codecCtx, &vpx.image, C.vpx_codec_pts_t(vpx.frameCount), 1, C.vpx_enc_frame_flags_t(flags), C.VPX_DL_REALTIME) != 0 {
|
||
fmt.Println("Failed to encode frame")
|
||
}
|
||
vpx.frameCount++
|
||
|
||
var iter C.vpx_codec_iter_t
|
||
fb := C.get_frame_buffer(&vpx.codecCtx, &iter)
|
||
if fb.ptr == nil {
|
||
return []byte{}
|
||
}
|
||
return C.GoBytes(fb.ptr, fb.size)
|
||
}
|
||
|
||
func (vpx *Vpx) Info() string {
|
||
return fmt.Sprintf("vpx (%v): %v", vpx.v, C.GoString(C.vpx_codec_version_str()))
|
||
}
|
||
|
||
func (vpx *Vpx) IntraRefresh() {
|
||
// !to implement
|
||
}
|
||
|
||
func (vpx *Vpx) Shutdown() error {
|
||
//if &vpx.image != nil {
|
||
C.vpx_img_free(&vpx.image)
|
||
//}
|
||
C.vpx_codec_destroy(&vpx.codecCtx)
|
||
vpx.flipped = false
|
||
return nil
|
||
}
|