nodeJs用ffmpeg直播推流到rtmp服务器上

总结

最近在写直播项目 目前比较重要的点就是推拉流 自己也去了解了一下

ffmpeg

FFmpeg 是一个开源项目,它提供了一个跨平台的命令行工具,以及一系列用于处理音频和视频数据的库。FFmpeg 能够执行多种任务,包括解封装、转封装、视频和音频的编码和解码、流处理等。它广泛应用于多媒体处理领域,被许多项目和网站推荐使用。

FFmpeg 的主要特点包括:

多平台支持:FFmpeg 可以在多种操作系统上运行,包括 Windows、Mac OS X、Linux 等。

功能丰富:它支持大量的音视频格式,能够进行编码、解码、转码、复用、解复用、滤波、转换、抓取等操作。

命令行工具:FFmpeg 提供了简洁的命令行接口,使得用户能够方便地进行批处理操作。

下载ffmpeg

官网 http://ffmpeg.org/
配置环境变量 添加到系统环境变量中
在这里插入图片描述
输入这个ffmpeg -version 出现这个 说明成功
在这里插入图片描述

node端


const http = require('http');
const express = require('express');
const socketio = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(express.static('public'))
var spawn = require('child_process').spawn;
spawn('ffmpeg', ['-h']).on('error', function (m) {
    console.error("FFMpeg not found in system cli; please install ffmpeg properly or make a softlink to ./!");
    process.exit(-1);
});

io.on('connection', function (socket) {
    socket.emit('message', 'Hello from mediarecorder-to-rtmp server!');

    socket.emit('message', 'Please set rtmp destination before start streaming.');

    var ffmpeg_process, feedStream = false;
    socket.on('config_rtmpDestination', function (m) {
        socket._rtmpDestination = m;
    });

    socket.on('config_vcodec', function (m) {
        socket._vcodec = m;
    });


    socket.on('start', function (m) {
        if (ffmpeg_process || feedStream) {

            socket.emit('fatal', 'stream already started.');
            return;
        }
        if (!socket._rtmpDestination) {
            socket.emit('fatal', 'no destination given.');
            return;
        }

        var framerate = socket.handshake.query.framespersecond;
        var audioBitrate = parseInt(socket.handshake.query.audioBitrate);
        var audioEncoding = "64k";

        console.log(audioEncoding, audioBitrate);

        console.log('framerate on node side', framerate);

        var ops = [
            '-i', '-',
            '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency',
            '-c:a', 'aac', '-ar', '44100', '-b:a', '64k',
            '-y',
            '-use_wallclock_as_timestamps', '1',
            '-async', '1',
            '-filter_complex', 'aresample=44100',
            '-strict', 'experimental',
            '-bufsize', '1000',
            '-f', 'flv', socket._rtmpDestination
        ];

        ffmpeg_process = spawn('ffmpeg', ops);

        ffmpeg_process.stderr.on('data', function (data) {
            socket.emit('ffmpeg_stderr', '' + data);
        });

        ffmpeg_process.on('error', function (err) {
            socket.emit('fatal', 'ffmpeg error!' + err);
            feedStream = false;
            console.log('意外出错',err);
            socket.disconnect();
        });

        ffmpeg_process.on('exit', function (err) {
            socket.emit('fatal', 'ffmpeg exit!' + err);
            socket.disconnect();
            console.log('exit',err);
        });

        socket.on('binarystream', function (data) {
            console.log(data,'48888');
            ffmpeg_process.stdin.write(data);
        });
    });

    socket.on('disconnect', function () {
        feedStream = false;
        ffmpeg_process.stdin.end();
        ffmpeg_process.kill('SIGINT');
    });
});

const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
  console.log(`Socket.IO server is running on port ${PORT}`);
});


解释一下代码
开启一个子线程用于运行ffmpeg

var spawn = require('child_process').spawn;
spawn('ffmpeg', ['-h']).on('error', function (m) {
    console.error("FFMpeg not found in system cli; please install ffmpeg properly or make a softlink to ./!");
    process.exit(-1);
});

用sockect进行连接 当监听到connection事件之后所做的事情

io.on('connection', function (socket) {
....
}

socket监听start消息

io.on('start', function (socket) {
....
}

设置对ffmpeg进行操作的命令 用于推流

  var ops = [
            '-i', '-',
            '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency',
            '-c:a', 'aac', '-ar', '44100', '-b:a', '64k',
            '-y',
            '-use_wallclock_as_timestamps', '1',
            '-async', '1',
            '-filter_complex', 'aresample=44100',
            '-strict', 'experimental',
            '-bufsize', '1000',
            '-f', 'flv', socket._rtmpDestination
        ];

        ffmpeg_process = spawn('ffmpeg', ops);

这个data就是接收到的数据流

        ffmpeg_process.stderr.on('data', function (data) {
            socket.emit('ffmpeg_stderr', '' + data);
        });

前端部分

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./index.css">
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.7.2/socket.io.js"></script>

<body>
    <header class="header">
        <h1>
            Start a Live Stream!
        </h1>

    </header>
    <br><br>
    <label for="option_width" class="form">Size:</label>
    <input class="form" type="text" id="option_width" value="1280" /> &times;
    <input class="form" type="text" id="option_height" value="720" />
    <br><br>
    <label class="form" for="option_framerate">Frame Rate:</label>
    <input class="form" type="text" id="option_framerate" value="15" />
    <br><br>
    <label class="form" for="option_framerate">Audio bitrate:</label>
    <select class="form" id="option_bitrate">
        <option value="22050">22050</option>
        <option value="44100">44100</option>
        <option value="11025">11025</option>
    </select>
    <br><br>
    <input class="form" type="hidden" id="socket.io_address" value="/" />
    <label class="form" for="option_url">RTMP:</label>

    <!-- RMTP YOUTUBE URL/KEY-->
    <input class="form" type="text" id="option_url" style="width:33%"
        value="rtmp://a.rtmp.youtube.com/live2/cqg8-76xs-hztf-hfx7-9q5y" />
    <br>
    <!-- RMTP YOUTUBE URL/KEY-->

    <input class="form" type="checkbox" style="display:none" id="checkbox_Reconection" checked="true">

    <label class="form" style="display:none">Reconnection </label>
    <br>
    <div class="form">
        Connect the server, then start streaming.
    </div>
    <br />
    <button class="form" id="button_server">Connect Server</button>
    <button class="form" id="button_start">Start Streaming</button>
    <button class="form" id="button_stop">Stop Streaming</button>

    <br>
    <p id="output_message"></p>
    <textarea readonly="true" id="output_console" rows=15>Server Logs</textarea>

    <br>
    <div class="video">
        <video class="output_video" id="output_video" autoplay=true></video>
    </div>

    <br>
    <script src="./index.js"></script>
</body>

</html>

js部分

var output_console = document.getElementById('output_console'),
    output_message = document.getElementById('output_message'),
    output_video = document.getElementById('output_video'),
    option_url = document.getElementById('option_url'),
    socketio_address = document.getElementById('socket.io_address'),
    option_width = document.getElementById('option_width'),
    option_height = document.getElementById('option_height'),
    option_framerate = document.getElementById('option_framerate'),
    option_bitrate = document.getElementById('option_bitrate'),
    button_start = document.getElementById('button_start'),
    height = parseInt(option_height.value),
    width = parseInt(option_width.value),
    framerate = parseInt(option_framerate.value),
    audiobitrate = parseInt(option_bitrate.value),
    url = option_url.value;

option_height.onchange = option_height.onkeyup = function () { height = 1 * this.value; }
option_width.onchange = option_width.onkeyup = function () { width = 1 * this.value; console.log("width" + width); }
option_framerate.onchange = option_framerate.onkeyup = function () { framerate = 1 * this.value; console.log("framerate" + framerate); }
option_bitrate.onchange = option_bitrate.onkeyup = function () { audiobitrate = 1 * this.value; console.log("bitrate" + audiobitrate); }
option_url.onchange = option_url.onkeyup = function () { url = this.value; }
button_start.onclick = requestMedia;
button_server.onclick = connect_server;
var flagConnect = true

var mediaRecorder;
var socket;
var state = "stop";
console.log("state initiated = " + state);
var t;
button_start.disabled = true;
button_stop.disabled = true;
function connect_server() {
    navigator.getUserMedia = (navigator.mediaDevices.getUserMedia ||
        navigator.mediaDevices.mozGetUserMedia ||
        navigator.mediaDevices.msGetUserMedia ||
        navigator.mediaDevices.webkitGetUserMedia);
    if (!navigator.getUserMedia) { fail('No getUserMedia() available.'); }
    if (!MediaRecorder) { fail('No MediaRecorder available.'); }


    var socketOptions = { secure: true, reconnection: true, reconnectionDelay: 1000, timeout: 15000, pingTimeout: 15000, pingInterval: 45000, query: { framespersecond: framerate, audioBitrate: audiobitrate } };

    socket = io(socketOptions);

    socket.on('connect_timeout', (timeout) => {
        console.log("连接超时" + timeout);
        output_message.innerHTML = "Connection timed out";
    });
    socket.on('error', (error) => {
        console.log("state on connection error= " + error);
        output_message.innerHTML = "Connection error";
    });

    socket.on('connect_error', function () {
        console.log("连接错误" + state);
    });

    socket.on('message', function (m) {
        console.log("state on message= " + state + m);
        show_output('SERVER:' + m);

    });

    socket.on('fatal', function (m) {

        show_output('Fatal ERROR: unexpected:' + m);
        console.log("fatal socket error!!", m);
        console.log("state on fatal error= " + state);
        console.log('media recorder restarted');

        if (flagConnect) {
            output_message.innerHTML = "server is reload!";
            console.log("重置连接");
        }
        //should reload?
    });

    socket.on('flagConnect', function (m) {
        //this is the ffmpeg output for each frame
        show_output('FFMPEG:' + m);
        console.log(m, '我是每一帧');
    });

    socket.on('disconnect', function (reason) {
        console.log("state disconec= " + state);
        show_output('ERROR: server disconnected!');
        console.log('失去连接' + reason);
        connect_server();

        if (flagConnect) {
            output_message.innerHTML = "重置";
            console.log("重置");
        }
    });

    state = "ready";
    console.log("state = " + state);
    button_start.disabled = false;
    button_stop.disabled = false;
    button_server.disabled = true;
    output_message.innerHTML = "Connecting local server...";
}
async function requestMedia() {
    var constraints = {
        audio: {
            sampleRate: audiobitrate,
            echoCancellation: true
        },
        video: {
            width: { min: 100, ideal: width, max: 1920 },
            height: { min: 100, ideal: height, max: 1080 },
            frameRate: { ideal: framerate }
        }
    }
    const stream = await navigator.mediaDevices.getUserMedia(constraints)
    video_show(stream)
    socket.emit('config_rtmpDestination', url);
    socket.emit('start', 'start');
    mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.start(250)
    button_stop.disabled = false;
    button_start.disabled = true;
    button_server.disabled = true;
    var livestream = document.getElementsByClassName("Livestream");
    livestream.innerHtml = "test";
    mediaRecorder.onstop = function (e) {
        console.log("stopped!");
        console.log(e);
    }
    mediaRecorder.onpause = function (e) {
        console.log("media recorder paused!!");
        console.log(e);
    }
    mediaRecorder.onerror = function (event) {
        let error = event.error;
        console.log("error", error.name);
    };
    mediaRecorder.ondataavailable = function (e) {
        socket.emit("binarystream", e.data);
        state = "start";
    }
}
function video_show(stream) {
    console.log(stream, '111111');
    if ("srcObject" in output_video) {
        output_video.muted = true;
        output_video.srcObject = stream;
    } else {
        output_video.src = window.URL.createObjectURL(stream);
    }
}
function show_output(str) {
    output_console.value += "\n" + str;
    output_console.scrollTop = output_console.scrollHeight;
};


以上代码分为 这三个方法
在这里插入图片描述
其中 以connect-server
先使用浏览器自带的 navigator.getUserMedia获取设备 并且将配置的属性传入socket配置中 这个方法就是对socket连接进行处理 包括处理服务器端连接过程通信的过程

requestMedia() 获取媒体数据流 并且通过 socket.emit(“binarystream”, e.data);传递给后台

 const stream = await navigator.mediaDevices.getUserMedia(constraints)

实例
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下周

目前弄了一个分片上传 下周弄完之后可以分享分享

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值