在 Node.js 中使用 ffmpeg 在视频流上叠加文本流

问题描述 投票:0回答:0

我正在使用 Node.js 创建一个流媒体系统,它使用 ffmpeg 将视频和文本流发送到本地 RTMP 服务器,然后组合这些流并将它们发送到 Twitch。

我正在使用画布创建具有透明背景的文本图像,每次播放列表中的新视频开始时我都需要更改该文本。

Currently in stream 我只看到我视频的视频流,看不到文字。但是,如果我通过 VLC 去查看更多分开的内容,我会看到它们

但是,我遇到了文本流没有出现在 Twitch 上的最终视频流中的问题。此外,我收到以下错误消息:

Combine stderr: [NULL @ 0x1407069f0] Unable to find a suitable output format for 'rtmp://live.twitch.tv/app/streamKey'
rtmp://live.twitch.tv/app/streamKey: Invalid argument

这是我当前的 Node.js 代码:


const createTextImage = (runner) => {
    return new Promise((resolve, reject) => {
        const canvas = createCanvas(1920, 1080);
        const context = canvas.getContext('2d');

        // Fill the background with transparency
        context.fillStyle = 'rgba(0,0,0,0)';
        context.fillRect(0, 0, canvas.width, canvas.height);

        // Set the text options
        context.fillStyle = '#ffffff';
        context.font = '24px Arial';
        context.textAlign = 'start';
        context.textBaseline = 'middle';

        // Draw the text
        context.fillText(`Speedrun by ${runner}`, canvas.width / 2, canvas.height / 2);

        // Define the images directory
        const imagesDir = path.join(__dirname, 'images', 'runners');

        // Ensure the images directory exists
        fs.mkdirSync(imagesDir, { recursive: true });

        // Define the file path
        const filePath = path.join(imagesDir, runner + '.png');

        // Create the write stream
        const out = fs.createWriteStream(filePath);

        // Create the PNG stream
        const stream = canvas.createPNGStream();

        // Pipe the PNG stream to the write stream
        stream.pipe(out);

        out.on('finish', () => {
            console.log('The PNG file was created.');
            resolve();
        });

        out.on('error', reject);
    });
}
const streamVideo = (video) => {
    ffmpegLibrary.ffprobe(video.video, function (err, metadata) {
        if (err) {
            console.error(err);
            return;
        }
        currentVideoDuration = metadata.format.duration;

        // Annulez le délai précédent avant d'en créer un nouveau
        if (nextVideoTimeoutId) {
            clearTimeout(nextVideoTimeoutId);
        }

        // Déplacez votre appel setTimeout ici
        nextVideoTimeoutId = setTimeout(() => {
            console.log('Fin de la vidéo, passage à la suivante...');
            nextVideo();
        }, currentVideoDuration * 1000 + 10000);
    })


    ffmpegVideo = childProcess.spawn('ffmpeg', [
        '-nostdin', '-re', '-f', 'concat', '-safe', '0', '-i', 'playlist.txt',
        '-vcodec', 'libx264',
        '-s', '1920x1080',
        '-r', '30',
        '-b:v', '5000k',
        '-acodec', 'aac',
        '-preset', 'veryfast',
        '-f', 'flv',
        `rtmp://localhost:1935/live/video` // envoie le flux vidéo au serveur rtmp local
    ]);

    createTextImage(video.runner).then(() => {
        ffmpegText = childProcess.spawn('ffmpeg', [
            '-nostdin', '-re',
            '-loop', '1', '-i', `images/runners/${video.runner}.png`, // Utilise l'image créée par Puppeteer
            '-vcodec', 'libx264rgb', // Utilise le codec PNG pour conserver la transparence
            '-s', '1920x1080',
            '-r', '30',
            '-b:v', '5000k',
            '-acodec', 'aac',
            '-preset', 'veryfast',
            '-f', 'flv',
            `rtmp://localhost:1935/live/text` // envoie le flux de texte au serveur rtmp local
        ]);

        ffmpegText.stdout.on('data', (data) => {
            console.log(`text stdout: ${data}`);
        });

        ffmpegText.stderr.on('data', (data) => {
            console.error(`text stderr: ${data}`);
        });
    }).catch(error => {
        console.error(`Erreur lors de la création de l'image de texte: ${error}`);
    });

    ffmpegCombine = childProcess.spawn('ffmpeg', [
        '-i', 'rtmp://localhost:1935/live/video',
        '-i', 'rtmp://localhost:1935/live/text',
        '-filter_complex', '[0:v][1:v]overlay=main_w-overlay_w:0',
        '-s', '1920x1080',
        '-r', '30',
        '-vcodec', 'libx264',
        '-b:v', '5000k',
        '-acodec', 'aac',
        '-preset', 'veryfast',
        '-f', 'flv',
        `rtmp://live.twitch.tv/app/${twitchStreamKey}` // envoie le flux combiné à Twitch
    ]);

    ffmpegVideo.stdout.on('data', (data) => {
        console.log(`video stdout: ${data}`);
    });

    ffmpegVideo.stderr.on('data', (data) => {
        console.error(`video stderr: ${data}`);
    });

    ffmpegCombine.stdout.on('data', (data) => {
        console.log(`Combine stdout: ${data}`);
    });

    ffmpegCombine.stderr.on('data', (data) => {
        console.error(`Combine stderr: ${data}`);
    });

    ffmpegCombine.on('close', (code) => {
        console.log(`ffmpeg exited with code ${code}`);
        if (currentIndex >= playlist.length) {
            console.log('End of playlist');
            currentIndex = 0;
        }
    });
}

本地我使用nginx和rtmp模块来管理多流并合并为一个发送到twitch

在 NGINX 中,它是我的 nginx.conf 模块:

rtmp {
    server {
        listen 1935; # le port pour le protocole RTMP
        
        application live {
            live on; # active le streaming en direct
            record off; # désactive l'enregistrement du flux
    
            # définit l'endroit où les flux doivent être envoyés
            push rtmp://live.twitch.tv/app/liveKey;
        }
    
        application text {
            live on; # active le streaming en direct
            record off; # désactive l'enregistrement du flux
        }
    }
}

我已检查两个流的编解码器、分辨率和帧速率是否相同。我还使用 -filter_complex 命令将文本流覆盖在视频流之上,但我不确定它是否正常工作。

每个流必须有相同的参数吗?

我想知道是否有人知道可能导致此问题的原因以及解决方法。我应该为 Twitch 的输出流使用不同的格式吗?或者我应该考虑另一种方法来将动态文本流分层到视频流上吗?

此外,我想知道当视频发生变化时我是否正在正确处理更新文本流。目前,每次视频更改时,我都会使用 Canvas 创建一个新的文本图像,然后为文本流创建一个新的 ffmpeg 进程。这是正确的方法,还是有更好的方法来处理这个问题?

提前感谢您的任何帮助或建议。

ffmpeg streaming javascript node.js
© www.soinside.com 2019 - 2024. All rights reserved.