feat(streamer): ffmpeg rtmp encoder spawn

This commit is contained in:
devilreef 2026-04-30 15:28:35 +06:00
parent baef915561
commit 7cdc8f0d44
Signed by: devilreef
SSH key fingerprint: SHA256:UZisRr4iuXx+IhkbZnR655L2RWAT6o2rgbGv5F/6m3Y

58
streamer/src/ffmpeg.ts Normal file
View file

@ -0,0 +1,58 @@
import { ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { EventEmitter } from "node:events";
import { Writable } from "node:stream";
export interface FfmpegOpts {
rtmpUrl: string;
width: number;
height: number;
framerate: number;
videoBitrate: string;
audioBitrate: string;
}
export class Ffmpeg extends EventEmitter {
private proc: ChildProcessWithoutNullStreams | null = null;
constructor(private opts: FfmpegOpts) { super(); }
start(): { videoIn: Writable; audioIn: Writable } {
const args = [
"-loglevel", "warning",
// video in (mjpeg pipe on fd 3)
"-f", "image2pipe", "-c:v", "mjpeg",
"-r", String(this.opts.framerate), "-i", "pipe:3",
// audio in (s16le pcm pipe on fd 4)
"-f", "s16le", "-ar", "48000", "-ac", "2", "-i", "pipe:4",
// video encode
"-c:v", "libx264", "-preset", "veryfast",
"-pix_fmt", "yuv420p",
"-b:v", this.opts.videoBitrate,
"-maxrate", this.opts.videoBitrate, "-bufsize", "9000k",
"-g", String(this.opts.framerate * 2), "-keyint_min", String(this.opts.framerate * 2),
"-r", String(this.opts.framerate),
// audio encode
"-c:a", "aac", "-b:a", this.opts.audioBitrate, "-ar", "48000",
// output
"-f", "flv", this.opts.rtmpUrl,
];
this.proc = spawn("ffmpeg", args, {
stdio: ["ignore", "pipe", "pipe", "pipe", "pipe"],
}) as unknown as ChildProcessWithoutNullStreams;
const stdio = (this.proc as unknown as { stdio: Writable[] }).stdio;
const videoIn = stdio[3]!;
const audioIn = stdio[4]!;
this.proc.stderr.on("data", (d) => this.emit("log", d.toString()));
this.proc.on("exit", (code) => this.emit("exit", code));
return { videoIn, audioIn };
}
stop(): void {
this.proc?.kill("SIGTERM");
this.proc = null;
}
}