diff --git a/bot/src/stream/selfbot.ts b/bot/src/stream/selfbot.ts index c8e5002..bfa4d45 100644 --- a/bot/src/stream/selfbot.ts +++ b/bot/src/stream/selfbot.ts @@ -46,9 +46,9 @@ export class SelfbotStreamer implements ScreenStreamer { `원본 오류: ${(e as Error).message}`, ); } - if (!vs.Streamer || !vs.prepareStream || !vs.playStream || !vs.Encoders) { + if (!vs.Streamer || !vs.prepareStream || !vs.playStream) { throw new Error( - "@dank074/discord-video-stream v6 API(Streamer/prepareStream/playStream/Encoders)를 찾지 못했습니다. " + + "@dank074/discord-video-stream v6 API(Streamer/prepareStream/playStream)를 찾지 못했습니다. " + "package.json 버전을 ^6.0.0으로 맞추세요.", ); } @@ -64,7 +64,7 @@ export class SelfbotStreamer implements ScreenStreamer { return "셀프봇 송출은 음성 채널 안에서 호출해야 합니다."; } const { selfbot, vs } = await this.loadLib(); - const { Streamer, prepareStream, playStream, Encoders } = vs; + const { Streamer, prepareStream, playStream } = vs; this.streamer = new Streamer(new selfbot.Client()); await this.streamer.client.login(this.config.selfbotToken); @@ -78,13 +78,17 @@ export class SelfbotStreamer implements ScreenStreamer { // bundled libav for the x11grab input device is not portable; piping the // system ffmpeg is. (Verified live against a real voice channel.) // - // With streamHw on (default) the capture is encoded by the GPU - // (h264_nvenc, RTX 5050) so 1080p60 stays smooth without loading the CPU; - // the library then transcodes with NVENC too (see encoder below). Without - // hardware support we fall back to software x264. + // The SYSTEM ffmpeg produces the final, Discord-ready H264 in one pass: + // target bitrate (-b:v/-maxrate), no B-frames (WebRTC requires this), a + // 1s keyframe interval, and yuv420p. The library then only REMUXES it + // (noTranscoding below) so there is no second decode/scale/encode. With + // streamHw on (default) this single encode runs on the GPU (h264_nvenc, + // RTX 5050); otherwise it falls back to software x264. const hw = this.config.streamHw; + const kbps = this.config.vncBitrateKbps; + const maxKbps = Math.round(kbps * 1.5); const captureCodecArgs = hw - ? ["-c:v", "h264_nvenc", "-preset", "p4", "-tune", "ll"] + ? ["-c:v", "h264_nvenc", "-preset", "p4", "-tune", "ll", "-forced-idr", "1"] : ["-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency"]; const capture = spawn("ffmpeg", [ "-loglevel", "error", @@ -93,7 +97,10 @@ export class SelfbotStreamer implements ScreenStreamer { "-video_size", this.config.vncResolution, "-i", this.config.vncDisplay, ...captureCodecArgs, - "-pix_fmt", "yuv420p", "-g", String(this.config.vncFramerate), + "-b:v", `${kbps}k`, "-maxrate", `${maxKbps}k`, "-bufsize", `${kbps}k`, + "-bf", "0", + "-pix_fmt", "yuv420p", + "-g", String(this.config.vncFramerate), "-f", "mpegts", "pipe:1", ]); this.capture = capture; @@ -104,13 +111,15 @@ export class SelfbotStreamer implements ScreenStreamer { const { command, output } = prepareStream( capture.stdout, { + // The capture above is already a Discord-ready H264 elementary stream, + // so the library only remuxes it (no second encode). width/height/ + // frameRate are passed for signalling; encoding options are ignored + // on the copy path. width: w || 1920, height: h || 1080, frameRate: this.config.vncFramerate, videoCodec: "H264", - bitrateVideo: this.config.vncBitrateKbps, - bitrateVideoMax: Math.round(this.config.vncBitrateKbps * 1.5), - encoder: hw ? Encoders.nvenc() : Encoders.software(), + noTranscoding: true, }, this.controller.signal, );