Merge aspect ratio to master (#116)

* Add frame scaling support (#107)

* Update README.md with additional info about Windows builds

* Add frame scaling

* Add and enable bilinear scaling (#109)

* Add and enable bilinear scaling

* Use Go x/image lib for interpolation

* Reformat the code goimport/gofmt

* Move worker config into the pkg/worker directory

* Change separator in the save file path allowing it to work on Windows (#113)
This commit is contained in:
giongto35 2019-10-19 02:29:07 +08:00 committed by GitHub
parent 08999ee3c2
commit fde4a24158
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 312 additions and 140 deletions

View file

@ -7,6 +7,8 @@ import (
"os/signal"
"time"
config "github.com/giongto35/cloud-game/pkg/config/worker"
"github.com/giongto35/cloud-game/pkg/util/logging"
"github.com/giongto35/cloud-game/pkg/worker"
"github.com/golang/glog"
@ -16,7 +18,7 @@ import (
func main() {
rand.Seed(time.Now().UTC().UnixNano())
cfg := worker.NewDefaultConfig()
cfg := config.NewDefaultConfig()
cfg.AddFlags(pflag.CommandLine)
logging.Init()

1
go.mod vendored
View file

@ -11,5 +11,6 @@ require (
github.com/pion/webrtc/v2 v2.1.2
github.com/prometheus/client_golang v1.1.0
github.com/spf13/pflag v1.0.3
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd
)

3
go.sum vendored
View file

@ -152,7 +152,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmV
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View file

@ -47,6 +47,9 @@ type EmulatorMeta struct {
Height int
AudioSampleRate int
Fps int
BaseWidth int
BaseHeight int
Ratio float64
}
var EmulatorConfig = map[string]EmulatorMeta{

View file

@ -9,13 +9,23 @@ type Config struct {
Port int
OverlordAddress string
// video
Scale int
DisableCustomSize bool
Width int
Height int
MonitoringConfig monitoring.ServerMonitoringConfig
}
func NewDefaultConfig() Config {
return Config{
Port: 8800,
OverlordAddress: "ws://localhost:8000/wso",
Port: 8800,
OverlordAddress: "ws://localhost:8000/wso",
Scale: 1,
DisableCustomSize: false,
Width: 320,
Height: 240,
MonitoringConfig: monitoring.ServerMonitoringConfig{
Port: 6601,
URLPrefix: "/worker",
@ -28,6 +38,11 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) *Config {
fs.IntVarP(&c.Port, "port", "", 8800, "OverWorker server port")
fs.StringVarP(&c.OverlordAddress, "overlordhost", "", c.OverlordAddress, "OverWorker URL to connect")
fs.IntVarP(&c.Scale, "scale", "s", c.Scale, "Set output viewport scale factor")
fs.BoolVarP(&c.DisableCustomSize, "disable-custom-size", "", c.DisableCustomSize, "Disable custom size")
fs.IntVarP(&c.Width, "width", "w", c.Width, "Set custom viewport width")
fs.IntVarP(&c.Height, "height", "h", c.Height, "Set custom viewport height")
fs.BoolVarP(&c.MonitoringConfig.MetricEnabled, "monitoring.metric", "m", c.MonitoringConfig.MetricEnabled, "Enable prometheus metric for server")
fs.BoolVarP(&c.MonitoringConfig.ProfilingEnabled, "monitoring.pprof", "p", c.MonitoringConfig.ProfilingEnabled, "Enable golang pprof for server")
fs.IntVarP(&c.MonitoringConfig.Port, "monitoring.port", "", c.MonitoringConfig.Port, "Monitoring server port")

View file

@ -0,0 +1,36 @@
package image
import (
"image/color"
)
const (
// BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha
BIT_FORMAT_SHORT_5_5_5_1 = iota
// BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha
BIT_FORMAT_INT_8_8_8_8_REV
// BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
BIT_FORMAT_SHORT_5_6_5
)
type Format func(data []byte, index int) color.RGBA
func rgb565(data []byte, index int) color.RGBA {
pixel := (int)(data[index]) + ((int)(data[index+1]) << 8)
return color.RGBA{
R: byte(((pixel>>11)*255 + 15) / 31),
G: byte((((pixel>>5)&0x3F)*255 + 31) / 63),
B: byte(((pixel&0x1F)*255 + 15) / 31),
A: 255,
}
}
func rgba8888(data []byte, index int) color.RGBA {
return color.RGBA{
R: data[index+2],
G: data[index+1],
B: data[index],
A: data[index+3],
}
}

View file

@ -0,0 +1,18 @@
package image
import (
"image"
)
func DrawRgbaImage(pixFormat int, scaleType int, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, image *image.RGBA) {
switch pixFormat {
case BIT_FORMAT_SHORT_5_6_5:
Resize(scaleType, rgb565, w, h, packedW, vw, vh, bpp, data, image)
case BIT_FORMAT_INT_8_8_8_8_REV:
Resize(scaleType, rgba8888, w, h, packedW, vw, vh, bpp, data, image)
case BIT_FORMAT_SHORT_5_5_5_1:
fallthrough
default:
image = nil
}
}

View file

@ -0,0 +1,135 @@
package image
import (
"image"
"image/color"
"golang.org/x/image/draw"
)
const (
// skips image interpolation
ScaleSkip = -1
// initial image interpolation algorithm
ScaleOld = 0
// nearest neighbour interpolation
ScaleNearestNeighbour = 1
// bilinear interpolation
ScaleBilinear = 2
)
func Resize(scaleType int, fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, out *image.RGBA) {
// !to implement own image interfaces img.Pix = bytes[]
src := image.NewRGBA(image.Rect(0, 0, w, h))
toRgba(fn, w, h, packedW, bpp, data, src)
// !to do set it once instead switching on each iteration
// !to do skip resize if w=vw h=vh
switch scaleType {
case ScaleSkip:
skip(fn, w, h, packedW, vw, vh, bpp, data, src, out)
case ScaleNearestNeighbour:
draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
//nearest(fn, w, h, packedW, vw, vh, bpp, data, src, out)
case ScaleBilinear:
draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
//bilinear(fn, w, h, packedW, vw, vh, bpp, data, src, out)
case ScaleOld:
fallthrough
default:
old(fn, w, h, packedW, vw, vh, bpp, data, src, out)
}
}
func old(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
seek := 0
scaleWidth := float64(vw) / float64(w)
scaleHeight := float64(vh) / float64(h)
for y := 0; y < h; y++ {
y2 := int(float64(y) * scaleHeight)
for x := 0; x < packedW; x++ {
x2 := int(float64(x) * scaleWidth)
if x2 < vw {
out.Set(x2, y2, fn(data, seek))
}
seek += bpp
}
}
}
func skip(fn Format, w int, h int, packedW int, _ int, _ int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
index := (y * packedW) + x
index *= bpp
out.Set(x, y, fn(data, index))
}
}
}
func nearest(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
xRatio := ((w << 16) / vw) + 1
yRatio := ((h << 16) / vh) + 1
for y := 0; y < vh; y++ {
y2 := (y * yRatio) >> 16
for x := 0; x < vw; x++ {
x2 := (x * xRatio) >> 16
index := (y2 * packedW) + x2
index *= bpp
out.Set(x, y, fn(data, index))
}
}
}
// This implementation has some color bleeding issues
func bilinear(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
xRatio := float64(w-1) / float64(vw)
yRatio := float64(h-1) / float64(vh)
for y := 0; y < vh; y++ {
y2 := int(yRatio * float64(y))
for x := 0; x < vw; x++ {
x2 := int(xRatio * float64(x))
w := (xRatio * float64(x)) - float64(x2)
h := (yRatio * float64(y)) - float64(y2)
index := (y2 * packedW) + x2
a := fn(data, index*bpp)
b := fn(data, (index+1)*bpp)
c := fn(data, (index+packedW)*bpp)
d := fn(data, (index+packedW+1)*bpp)
out.Set(x, y, color.RGBA{
// don't sink the boat
R: byte(float64(a.R)*(1-w)*(1-h) + float64(b.R)*w*(1-h) + float64(c.R)*h*(1-w) + float64(d.R)*w*h),
G: byte(float64(a.G)*(1-w)*(1-h) + float64(b.G)*w*(1-h) + float64(c.G)*h*(1-w) + float64(d.G)*w*h),
B: byte(float64(a.B)*(1-w)*(1-h) + float64(b.B)*w*(1-h) + float64(c.B)*h*(1-w) + float64(d.B)*w*h),
//A: byte(float64(a.A)*(1-w)*(1-h) + float64(b.A)*w*(1-h) + float64(c.A)*h*(1-w) + float64(d.A)*w*h),
})
}
}
}
func toRgba(fn Format, w int, h int, packedW int, bpp int, data []byte, image *image.RGBA) {
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
index := (y*packedW + x) * bpp
c := fn(data, index)
i := (y-image.Rect.Min.Y)*image.Stride + (x-image.Rect.Min.X)*4
s := image.Pix[i : i+4 : i+4]
s[0] = c.R
s[1] = c.G
s[2] = c.B
s[3] = c.A
}
}
}

View file

@ -67,10 +67,6 @@ var outputImg *image.RGBA
// NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch)
func NewNAEmulator(etype string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- []int16, inputChannel <-chan int) *naEmulator {
meta := config.EmulatorConfig[etype]
ewidth = meta.Width
eheight = meta.Height
// outputImg is tmp img used for decoding and reuse in encoding flow
outputImg = image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
return &naEmulator{
meta: meta,
@ -117,6 +113,14 @@ func (na *naEmulator) LoadMeta(path string) config.EmulatorMeta {
return na.meta
}
func (na *naEmulator) SetViewport(width int, height int) {
// outputImg is tmp img used for decoding and reuse in encoding flow
outputImg = image.NewRGBA(image.Rect(0, 0, width, height))
ewidth = width
eheight = height
}
func (na *naEmulator) Start() {
na.playGame(na.gamePath)
ticker := time.NewTicker(time.Second / 60)

View file

@ -4,14 +4,13 @@ import (
"bufio"
"errors"
"fmt"
"image"
"image/color"
"log"
"os"
"os/user"
"reflect"
"sync"
"unsafe"
"github.com/giongto35/cloud-game/pkg/emulator/libretro/image"
)
/*
@ -63,8 +62,6 @@ var video struct {
bpp uint32
}
var scale = 3.0
const bufSize = 1024 * 4
const joypadNumKeys = int(C.RETRO_DEVICE_ID_JOYPAD_R3 + 1)
@ -85,15 +82,6 @@ var bindRetroKeys = map[int]int{
9: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
}
const (
// BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha
BIT_FORMAT_SHORT_5_5_5_1 = iota
// BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha
BIT_FORMAT_INT_8_8_8_8_REV
// BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
BIT_FORMAT_SHORT_5_6_5
)
type CloudEmulator interface {
Start(path string)
SaveGame(saveExtraFunc func() error) error
@ -102,109 +90,25 @@ type CloudEmulator interface {
Close()
}
func resizeToAspect(ratio float64, sw float64, sh float64) (dw float64, dh float64) {
if ratio <= 0 {
ratio = sw / sh
}
if sw/sh < 1.0 {
dw = dh * ratio
dh = sh
} else {
dw = sw
dh = dw / ratio
}
return
}
//export coreVideoRefresh
func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) {
bytesPerRow := int(uint32(pitch) / video.bpp)
if data != nil {
NAEmulator.imageChannel <- toImageRGBA(data, bytesPerRow, int(width), int(height))
}
}
// toImageRGBA convert nanoarch 2d array to image.RGBA
func toImageRGBA(data unsafe.Pointer, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
// Convert unsafe Pointer to bytes array
var bytes []byte
// Convert bytes array to image
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
sh.Data = uintptr(data)
sh.Len = bytesPerRow * inputHeight * 4
sh.Cap = bytesPerRow * inputHeight * 4
if video.pixFmt == BIT_FORMAT_SHORT_5_6_5 {
return to565Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
} else if video.pixFmt == BIT_FORMAT_INT_8_8_8_8_REV {
return to8888Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
}
return nil
}
func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
seek := 0
// scaleWidth and scaleHeight is the scale
scaleWidth := float64(ewidth) / float64(inputWidth)
scaleHeight := float64(eheight) / float64(inputHeight)
for y := 0; y < inputHeight; y++ {
for x := 0; x < bytesPerRow; x++ {
xx := int(float64(x) * scaleWidth)
yy := int(float64(y) * scaleHeight)
if xx < ewidth {
b8 := bytes[seek]
g8 := bytes[seek+1]
r8 := bytes[seek+2]
a8 := bytes[seek+3]
outputImg.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), byte(a8)})
}
seek += 4
}
// some cores can return nothing
if data == nil {
return
}
// TODO: Resize Image
return outputImg
}
// calculate real frame width in pixels from packed data (realWidth >= width)
packedWidth := int(uint32(pitch) / video.bpp)
func to565Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
seek := 0
// convert data from C
bytes := int(height) * packedWidth * int(video.bpp)
data_ := (*[1 << 30]byte)(data)[:bytes:bytes]
// scaleWidth and scaleHeight is the scale
scaleWidth := float64(ewidth) / float64(inputWidth)
scaleHeight := float64(eheight) / float64(inputHeight)
// !to move it on the other side of the channel
image.DrawRgbaImage(int(video.pixFmt), image.ScaleBilinear, int(width), int(height),
packedWidth, ewidth, eheight, int(video.bpp), data_, outputImg)
for y := 0; y < inputHeight; y++ {
for x := 0; x < bytesPerRow; x++ {
xx := int(float64(x) * scaleWidth)
yy := int(float64(y) * scaleHeight)
if xx < ewidth {
var bi int
bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8)
b5 := bi & 0x1F
g6 := (bi >> 5) & 0x3F
r5 := (bi >> 11)
b8 := (b5*255 + 15) / 31
g8 := (g6*255 + 31) / 63
r8 := (r5*255 + 15) / 31
outputImg.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), 255})
}
seek += 2
}
}
// TODO: Resize Image
return outputImg
NAEmulator.imageChannel <- outputImg
}
//export coreInputPoll
@ -434,15 +338,25 @@ func coreLoadGame(filename string) {
C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &avi)
// Append the library name to the window title.
NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate)
NAEmulator.meta.Fps = int(avi.timing.fps)
NAEmulator.meta.BaseWidth = int(avi.geometry.base_width)
NAEmulator.meta.BaseHeight = int(avi.geometry.base_height)
// set aspect ratio
/* Nominal aspect ratio of game. If aspect_ratio is <= 0.0,
an aspect ratio of base_width / base_height is assumed.
* A frontend could override this setting, if desired. */
ratio := float64(avi.geometry.aspect_ratio)
if ratio <= 0.0 {
ratio = float64(avi.geometry.base_width) / float64(avi.geometry.base_height)
}
NAEmulator.meta.Ratio = ratio
fmt.Println("-----------------------------------")
fmt.Println("--- System audio and video info ---")
fmt.Println("-----------------------------------")
fmt.Println(" Aspect ratio: ", avi.geometry.aspect_ratio)
/* Nominal aspect ratio of game. If
* aspect_ratio is <= 0.0, an aspect ratio
* of base_width / base_height is assumed.
* A frontend could override this setting,
* if desired. */
fmt.Println(" Aspect ratio: ", ratio)
fmt.Println(" Base width: ", avi.geometry.base_width) /* Nominal video width of game. */
fmt.Println(" Base height: ", avi.geometry.base_height) /* Nominal video height of game. */
fmt.Println(" Max width: ", avi.geometry.max_width) /* Maximum possible width of game. */
@ -450,10 +364,6 @@ func coreLoadGame(filename string) {
fmt.Println(" Sample rate: ", avi.timing.sample_rate) /* Sampling rate of audio. */
fmt.Println(" FPS: ", avi.timing.fps) /* FPS of video content. */
fmt.Println("-----------------------------------")
// Append the library name to the window title.
NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate)
NAEmulator.meta.Fps = int(avi.timing.fps)
}
// serializeSize returns the amount of data the implementation requires to serialize
@ -500,15 +410,15 @@ func nanoarchRun() {
func videoSetPixelFormat(format uint32) C.bool {
switch format {
case C.RETRO_PIXEL_FORMAT_0RGB1555:
video.pixFmt = BIT_FORMAT_SHORT_5_5_5_1
video.pixFmt = image.BIT_FORMAT_SHORT_5_5_5_1
video.bpp = 2
break
case C.RETRO_PIXEL_FORMAT_XRGB8888:
video.pixFmt = BIT_FORMAT_INT_8_8_8_8_REV
video.pixFmt = image.BIT_FORMAT_INT_8_8_8_8_REV
video.bpp = 4
break
case C.RETRO_PIXEL_FORMAT_RGB565:
video.pixFmt = BIT_FORMAT_SHORT_5_6_5
video.pixFmt = image.BIT_FORMAT_SHORT_5_6_5
video.bpp = 2
break
default:

View file

@ -7,6 +7,9 @@ type CloudEmulator interface {
// LoadMeta returns meta data of emulator. Refer below
LoadMeta(path string) config.EmulatorMeta
// Start is called after LoadGame
SetViewport(width int, height int)
Start()
// SaveGame save game state, saveExtraFunc is callback to do extra step. Ex: save to google cloud
SaveGame(saveExtraFunc func() error) error

View file

@ -6,6 +6,8 @@ import (
"path"
"time"
"github.com/giongto35/cloud-game/pkg/config/worker"
"github.com/giongto35/cloud-game/pkg/util"
"github.com/giongto35/cloud-game/pkg/webrtc"
storage "github.com/giongto35/cloud-game/pkg/worker/cloud-storage"
@ -26,6 +28,7 @@ type Handler struct {
oClient *OverlordClient
// Raw address of overlord
overlordHost string
cfg worker.Config
// Rooms map : RoomID -> Room
rooms map[string]*room.Room
// ID of the current server globalwise
@ -37,7 +40,7 @@ type Handler struct {
}
// NewHandler returns a new server
func NewHandler(overlordHost string) *Handler {
func NewHandler(cfg worker.Config) *Handler {
// Create offline storage folder
createOfflineStorage()
@ -46,7 +49,8 @@ func NewHandler(overlordHost string) *Handler {
return &Handler{
rooms: map[string]*room.Room{},
sessions: map[string]*Session{},
overlordHost: overlordHost,
overlordHost: cfg.OverlordAddress,
cfg: cfg,
onlineStorage: onlineStorage,
}
}
@ -135,7 +139,7 @@ func (h *Handler) createNewRoom(gameName string, roomID string, playerIndex int,
// or the roomID doesn't have any running sessions (room was closed)
// we spawn a new room
if roomID == "" || !h.isRoomRunning(roomID) {
room := room.NewRoom(roomID, gameName, videoEncoderType, h.onlineStorage)
room := room.NewRoom(roomID, gameName, videoEncoderType, h.onlineStorage, h.cfg)
// TODO: Might have race condition
h.rooms[room.ID] = room
return room

View file

@ -8,18 +8,20 @@ import (
"net/http"
"strconv"
"github.com/giongto35/cloud-game/pkg/config/worker"
"github.com/giongto35/cloud-game/pkg/monitoring"
"github.com/golang/glog"
)
type OverWorker struct {
ctx context.Context
cfg Config
cfg worker.Config
monitoringServer *monitoring.ServerMonitoring
}
func New(ctx context.Context, cfg Config) *OverWorker {
func New(ctx context.Context, cfg worker.Config) *OverWorker {
return &OverWorker{
ctx: ctx,
cfg: cfg,
@ -50,7 +52,7 @@ func (o *OverWorker) Shutdown() {
// initializeWorker setup a worker
func (o *OverWorker) initializeWorker() {
worker := NewHandler(o.cfg.OverlordAddress)
worker := NewHandler(o.cfg)
defer func() {
log.Println("Close worker")

View file

@ -4,6 +4,7 @@ import (
"image"
"io/ioutil"
"log"
"math"
"math/rand"
"os"
"runtime"
@ -11,6 +12,8 @@ import (
"strings"
"sync"
"github.com/giongto35/cloud-game/pkg/config/worker"
"github.com/giongto35/cloud-game/pkg/config"
"github.com/giongto35/cloud-game/pkg/emulator"
"github.com/giongto35/cloud-game/pkg/emulator/libretro/nanoarch"
@ -49,8 +52,10 @@ type Room struct {
//meta emulator.Meta
}
const separator = "___"
// NewRoom creates a new room
func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStorage *storage.Client) *Room {
func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStorage *storage.Client, cfg worker.Config) *Room {
// If no roomID is given, generate it from gameName
// If the is roomID, get gameName from roomID
if roomID == "" {
@ -101,7 +106,27 @@ func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStor
room.director = getEmulator(emuName, roomID, imageChannel, audioChannel, inputChannel)
gameMeta := room.director.LoadMeta(game.Path)
go room.startVideo(gameMeta.Width, gameMeta.Height, videoEncoderType)
var nwidth, nheight int
if !cfg.DisableCustomSize {
baseAspectRatio := float64(gameMeta.BaseWidth) / float64(gameMeta.Height)
nwidth, nheight = resizeToAspect(baseAspectRatio, cfg.Width, cfg.Height)
log.Printf("Viewport size will be changed from %dx%d (%f) -> %dx%d", cfg.Width, cfg.Height,
baseAspectRatio, nwidth, nheight)
} else {
log.Println("Viewport custom size is disabled, base size will be used instead")
nwidth, nheight = gameMeta.BaseWidth, gameMeta.BaseHeight
}
if cfg.Scale > 1 {
nwidth, nheight = nwidth*cfg.Scale, nheight*cfg.Scale
log.Printf("Viewport size has scaled to %dx%d", nwidth, nheight)
}
room.director.SetViewport(nwidth, nheight)
log.Println("meta: ", gameMeta)
go room.startVideo(nwidth, nheight, videoEncoderType)
go room.startAudio(gameMeta.AudioSampleRate)
room.director.Start()
@ -114,6 +139,17 @@ func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStor
return room
}
func resizeToAspect(ratio float64, sw int, sh int) (dw int, dh int) {
// ratio is always > 0
dw = int(math.Round(float64(sh)*ratio/2) * 2)
dh = sh
if dw > sw {
dw = sw
dh = int(math.Round(float64(sw)/ratio/2) * 2)
}
return
}
// create director
func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- []int16, inputChannel <-chan int) emulator.CloudEmulator {
nanoarch.Init(emuName, roomID, imageChannel, audioChannel, inputChannel)
@ -123,7 +159,7 @@ func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA,
// getGameNameFromRoomID parse roomID to get roomID and gameName
func getGameNameFromRoomID(roomID string) string {
parts := strings.Split(roomID, "|")
parts := strings.Split(roomID, separator)
if len(parts) <= 1 {
return ""
}
@ -134,7 +170,7 @@ func getGameNameFromRoomID(roomID string) string {
func generateRoomID(gameName string) string {
// RoomID contains random number + gameName
// Next time when we only get roomID, we can launch game based on gameName
roomID := strconv.FormatInt(rand.Int63(), 16) + "|" + gameName
roomID := strconv.FormatInt(rand.Int63(), 16) + separator + gameName
return roomID
}