fix: single-pass NVENC encode for selfbot stream (no double encode)
Address review: the capture ffmpeg had no -b:v, so it encoded at nvenc's low default (~2.47 Mbps) and the library then re-encoded to 8 Mbps, which only upscaled already-lost detail. The double encode also kept CPU decode + scale + re-encode in the library, contradicting the "GPU handles it" claim. Now the system ffmpeg produces the final Discord-ready H264 in one pass (-b:v/-maxrate at the configured bitrate, -bf 0, 1s keyframes, yuv420p, -forced-idr) and prepareStream uses noTranscoding:true to remux only. One GPU encode, no library decode/scale/re-encode. Verified locally: high-motion source fills 8.7 Mbps at these args (vs the ~2.47 Mbps no-bitrate default), real :1 desktop holds 60fps at realtime, and the capture -> copy/remux chain yields h264 1920x1080 yuv420p 60fps has_b_frames=0. tsc --noEmit passes. Live Discord test pending reboot.
This commit is contained in:
@@ -46,9 +46,9 @@ export class SelfbotStreamer implements ScreenStreamer {
|
|||||||
`원본 오류: ${(e as Error).message}`,
|
`원본 오류: ${(e as Error).message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!vs.Streamer || !vs.prepareStream || !vs.playStream || !vs.Encoders) {
|
if (!vs.Streamer || !vs.prepareStream || !vs.playStream) {
|
||||||
throw new Error(
|
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으로 맞추세요.",
|
"package.json 버전을 ^6.0.0으로 맞추세요.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ export class SelfbotStreamer implements ScreenStreamer {
|
|||||||
return "셀프봇 송출은 음성 채널 안에서 호출해야 합니다.";
|
return "셀프봇 송출은 음성 채널 안에서 호출해야 합니다.";
|
||||||
}
|
}
|
||||||
const { selfbot, vs } = await this.loadLib();
|
const { selfbot, vs } = await this.loadLib();
|
||||||
const { Streamer, prepareStream, playStream, Encoders } = vs;
|
const { Streamer, prepareStream, playStream } = vs;
|
||||||
|
|
||||||
this.streamer = new Streamer(new selfbot.Client());
|
this.streamer = new Streamer(new selfbot.Client());
|
||||||
await this.streamer.client.login(this.config.selfbotToken);
|
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
|
// bundled libav for the x11grab input device is not portable; piping the
|
||||||
// system ffmpeg is. (Verified live against a real voice channel.)
|
// system ffmpeg is. (Verified live against a real voice channel.)
|
||||||
//
|
//
|
||||||
// With streamHw on (default) the capture is encoded by the GPU
|
// The SYSTEM ffmpeg produces the final, Discord-ready H264 in one pass:
|
||||||
// (h264_nvenc, RTX 5050) so 1080p60 stays smooth without loading the CPU;
|
// target bitrate (-b:v/-maxrate), no B-frames (WebRTC requires this), a
|
||||||
// the library then transcodes with NVENC too (see encoder below). Without
|
// 1s keyframe interval, and yuv420p. The library then only REMUXES it
|
||||||
// hardware support we fall back to software x264.
|
// (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 hw = this.config.streamHw;
|
||||||
|
const kbps = this.config.vncBitrateKbps;
|
||||||
|
const maxKbps = Math.round(kbps * 1.5);
|
||||||
const captureCodecArgs = hw
|
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"];
|
: ["-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency"];
|
||||||
const capture = spawn("ffmpeg", [
|
const capture = spawn("ffmpeg", [
|
||||||
"-loglevel", "error",
|
"-loglevel", "error",
|
||||||
@@ -93,7 +97,10 @@ export class SelfbotStreamer implements ScreenStreamer {
|
|||||||
"-video_size", this.config.vncResolution,
|
"-video_size", this.config.vncResolution,
|
||||||
"-i", this.config.vncDisplay,
|
"-i", this.config.vncDisplay,
|
||||||
...captureCodecArgs,
|
...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",
|
"-f", "mpegts", "pipe:1",
|
||||||
]);
|
]);
|
||||||
this.capture = capture;
|
this.capture = capture;
|
||||||
@@ -104,13 +111,15 @@ export class SelfbotStreamer implements ScreenStreamer {
|
|||||||
const { command, output } = prepareStream(
|
const { command, output } = prepareStream(
|
||||||
capture.stdout,
|
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,
|
width: w || 1920,
|
||||||
height: h || 1080,
|
height: h || 1080,
|
||||||
frameRate: this.config.vncFramerate,
|
frameRate: this.config.vncFramerate,
|
||||||
videoCodec: "H264",
|
videoCodec: "H264",
|
||||||
bitrateVideo: this.config.vncBitrateKbps,
|
noTranscoding: true,
|
||||||
bitrateVideoMax: Math.round(this.config.vncBitrateKbps * 1.5),
|
|
||||||
encoder: hw ? Encoders.nvenc() : Encoders.software(),
|
|
||||||
},
|
},
|
||||||
this.controller.signal,
|
this.controller.signal,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user