diff --git a/.docker/firefox/Dockerfile.nvidia b/.docker/firefox/Dockerfile.nvidia index 0bdc70b7..6bf7c2ad 100644 --- a/.docker/firefox/Dockerfile.nvidia +++ b/.docker/firefox/Dockerfile.nvidia @@ -1,4 +1,4 @@ -ARG BASE_IMAGE=m1k1o/neko:base +ARG BASE_IMAGE=ghcr.io/m1k1o/neko/nvidia-base:latest FROM $BASE_IMAGE ARG SRC_URL="https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" diff --git a/Dockerfile b/Dockerfile index 607d58af..57f0840c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,13 +33,6 @@ RUN go mod download COPY server/ . RUN ./build -FROM ghcr.io/m1k1o/neko/intel-firefox:latest - -RUN set -eux; apt-get update; \ - apt-get install -y --no-install-recommends intel-media-va-driver-non-free i965-va-driver-shaders; \ - # - # clean up - apt-get clean -y; \ - rm -rf /var/lib/apt/lists/* /var/cache/apt/* +FROM cave-firefox:latest COPY --from=server /src/bin/neko /usr/bin/neko diff --git a/docker-compose.yml b/docker-compose.yml index b61cdb99..2f6f5dea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,10 +10,16 @@ services: extra_hosts: - "host.docker.internal:host-gateway" volumes: - - ./bin/firefox:/home/neko/.mozilla/firefox + - ./bin/home/neko:/home/neko devices: - - /dev/dri/renderD128:/dev/dri/renderD128 - - /dev/dri/card0:/dev/dri/card0 + - /dev/dri:/dev/dri + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] privileged: true environment: NEKO_SCREEN: 1920x1080@60 @@ -23,4 +29,5 @@ services: NEKO_ICELITE: 1 NEKO_BROADCAST_URL: rtmp://ome.thuan.au:1935/app/stream-neko NEKO_NAT1TO1: 192.168.0.34 - NEKO_HWENC: + NEKO_HWENC: NVENC + NEKO_VIDEO_CODEC: h264 diff --git a/server/internal/capture/broadcast.go b/server/internal/capture/broadcast.go index f1e81335..95c6e784 100644 --- a/server/internal/capture/broadcast.go +++ b/server/internal/capture/broadcast.go @@ -14,9 +14,10 @@ type BroacastManagerCtx struct { logger zerolog.Logger mu sync.Mutex - pipeline *gst.Pipeline - pipelineMu sync.Mutex - pipelineFn func(url string) (string, error) + pipeline *gst.Pipeline + pipelineMu sync.Mutex + pipelineFn func(url string) (string, error) + pipelineRestart chan bool url string started bool @@ -29,10 +30,11 @@ func broadcastNew(pipelineFn func(url string) (string, error), defaultUrl string Logger() return &BroacastManagerCtx{ - logger: logger, - pipelineFn: pipelineFn, - url: defaultUrl, - started: defaultUrl != "", + logger: logger, + pipelineFn: pipelineFn, + pipelineRestart: make(chan bool), + url: defaultUrl, + started: defaultUrl != "", } } @@ -71,6 +73,10 @@ func (manager *BroacastManagerCtx) Started() bool { return manager.started } +func (manager *BroacastManagerCtx) GetRestart() chan bool { + return manager.pipelineRestart +} + func (manager *BroacastManagerCtx) Url() string { manager.mu.Lock() defer manager.mu.Unlock() diff --git a/server/internal/capture/manager.go b/server/internal/capture/manager.go index 014e1a62..e0529de3 100644 --- a/server/internal/capture/manager.go +++ b/server/internal/capture/manager.go @@ -53,6 +53,25 @@ func (manager *CaptureManagerCtx) Start() { } } + go func() { + for { + _, ok := <-manager.broadcast.GetRestart() + if !ok { + return + } + + if !manager.broadcast.Started() { + return + } + + manager.broadcast.destroyPipeline() + err := manager.broadcast.createPipeline() + if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) { + manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline") + } + } + }() + go func() { for { before, ok := <-manager.desktop.GetScreenSizeChangeChannel() diff --git a/server/internal/capture/pipelines.go b/server/internal/capture/pipelines.go index 51398f1f..307937fd 100644 --- a/server/internal/capture/pipelines.go +++ b/server/internal/capture/pipelines.go @@ -30,7 +30,7 @@ import ( */ const ( - videoSrc = "ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=%d/1 ! vaapipostproc ! queue ! " + videoSrc = "ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=%d/1 ! videoconvert ! queue ! " audioSrc = "pulsesrc device=%s ! audio/x-raw,channels=2 ! audioconvert ! " ) @@ -47,7 +47,8 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url // replace display pipelineStr = strings.Replace(pipelineStr, "{display}", display, -1) } else { - pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='%s live=1' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s video/x-raw,format=NV12 ! vaapih264enc rate-control=cbr bitrate=%d keyframe-period=180 quality-level=7 ! h264parse ! mux.", url, audio, video, 8000) + birate := 8000 + pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='%s live=1 subscribe=stream-neko buffer=1200000' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s video/x-raw,format=NV12 ! nvh264enc name=encoder rc-lookahead=20 preset=2 gop-size=120 temporal-aq=true bitrate=%d vbv-buffer-size=%d rc-mode=cbr bframes=0 ! h264parse config-interval=-1 ! mux.", url, audio, video, birate, birate) } return pipelineStr, nil diff --git a/server/internal/types/capture.go b/server/internal/types/capture.go index 36e54b8b..80e6f948 100644 --- a/server/internal/types/capture.go +++ b/server/internal/types/capture.go @@ -14,6 +14,7 @@ type BroadcastManager interface { Start(url string) error Stop() Started() bool + GetRestart() chan bool Url() string } diff --git a/server/internal/webrtc/handle.go b/server/internal/webrtc/handle.go index 89e92125..a4f47314 100644 --- a/server/internal/webrtc/handle.go +++ b/server/internal/webrtc/handle.go @@ -9,11 +9,12 @@ import ( ) const ( - OP_MOVE = 0x01 - OP_SCROLL = 0x02 - OP_KEY_DOWN = 0x03 - OP_KEY_UP = 0x04 - OP_KEY_CLK = 0x05 + OP_MOVE = 0x01 + OP_SCROLL = 0x02 + OP_KEY_DOWN = 0x03 + OP_KEY_UP = 0x04 + OP_KEY_CLK = 0x05 + OP_RESTART_BROADCAST = 0x06 ) type PayloadHeader struct { diff --git a/server/internal/websocket/upstream.go b/server/internal/websocket/upstream.go index e8c294dd..a6a7fb87 100644 --- a/server/internal/websocket/upstream.go +++ b/server/internal/websocket/upstream.go @@ -121,6 +121,9 @@ func (ws *WebSocketHandler) connectUpstream() { ws.logger.Debug().Msgf("key up %d", payload.Key) } + case webrtc.OP_RESTART_BROADCAST: + ws.logger.Info().Msg("Restarting broadcast") + ws.capture.Broadcast().GetRestart() <- true } } } diff --git a/server/internal/websocket/websocket.go b/server/internal/websocket/websocket.go index c7f207e1..3ee61406 100644 --- a/server/internal/websocket/websocket.go +++ b/server/internal/websocket/websocket.go @@ -66,6 +66,7 @@ func New(sessions types.SessionManager, desktop types.DesktopManager, capture ty conf: conf, sessions: sessions, desktop: desktop, + capture: capture, webrtc: webrtc, state: state, upgrader: websocket.Upgrader{ @@ -88,6 +89,7 @@ type WebSocketHandler struct { upgrader websocket.Upgrader sessions types.SessionManager desktop types.DesktopManager + capture types.CaptureManager webrtc types.WebRTCManager state *state.State conf *config.WebSocket