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)

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

下周

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/617512.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ps各种修改文字超实用方法

介绍 在日常生活中,难免会遇到进行文字修改的ps场景,此时就需要用到比较专业的ps进行文字修改,博主特意整合了多种情况下的文字p图方法进行记录,但是不包含全部情况,只记录日常中常见的情况,也可以解决大部分场景了 原图有可用文字素材 在p图时,我们可以先观察我们要p的图中…

【ARMv8/v9 系统寄存器 5 -- CPU ID 判断寄存器 MPIDR_EL1 使用详细介绍】

文章目录 寄存器名称: MPIDR_EL1寄存器结构:主要功能和用途亲和级别&#xff08;Affinity Levels&#xff09;简介CORE ID 获取函数 在ARMv8-A架构中&#xff0c; MPIDR_EL1寄存器是一个非常重要的系统寄存器&#xff0c;它提供了关于处理器在其物理和逻辑配置中的位置的信息。…

Linux下安装JDK并配置环境变量

一、Oracle官网下载jdk 1、官网地址 https://www.oracle.com/java/technologies/downloads/#java17 2、命令下载 wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz 3、解压 tar -zxvf jdk-17_linux-x64_bin.tar.gz 4、配置环境变量 ec…

webrtc windows 编译,以及peerconnection_client

webrtc windows环境编译&#xff0c;主要参考webrtc官方文档&#xff0c;自备梯子 depot tools 安装 Install depot_tools 因为我用的是windows&#xff0c;这里下载bundle 的安装包&#xff0c;然后直接解压&#xff0c;最后设置到环境变量PATH。 执行gn等命令不报错&…

SQLite .journal 文件

在之前插入大量数据测试的时候&#xff0c;发现在数据库文件同级目录下会产生一个同名.journal的文件&#xff0c;并且不是一直会存在&#xff0c;而是生成一会就会自动删除&#xff0c;然后继续生成继续删除&#xff0c;直到数据插入完成。 初步猜测&#xff0c;应该是类似 re…

rust开发web服务器框架,github排名对比

Rocket Star最多的框架 github仓库地址&#xff1a;GitHub - rwf2/Rocket: A web framework for Rust. Rocket 是一个针对 Rust 的异步 Web 框架&#xff0c;重点关注可用性、安全性、可扩展性和速度。 Axum 异步运行时 githuh仓库地址&#xff1a;GitHub - tokio-rs/axum: …

Unity开发中导弹路径散射的原理与实现

Unity开发中导弹路径散射的原理与实现 前言逻辑原理代码实现导弹自身脚本外部控制脚本 应用效果结语 前言 前面我们学习了导弹的追踪的效果&#xff0c;但是在动画或游戏中&#xff0c;我们经常可以看到导弹发射后的弹道是不规则的&#xff0c;扭扭曲曲的飞行&#xff0c;然后击…

国产操作系统下使用dpkg命令管理软件包 _ 统信 _ 麒麟 _ 中科方德

往期好文&#xff1a;国产操作系统下Chrome的命令行使用 | 统信 | 麒麟 Hello&#xff0c;大家好啊&#xff01;在Linux系统中&#xff0c;dpkg是Debian包管理系统的基础命令工具&#xff0c;它允许用户安装、卸载、查询和管理软件包。在国产操作系统如统信UOS和麒麟KOS、中科方…

精选多个炫酷的数据可视化大屏(含源码),拿走就用~

末尾有链接 演示地址&#xff1a;可视化大数据展示中心 (null.fit) 可视化大数据展示模板-科技语者 (chgskj.cn)

Nginx 基于域名的虚拟主机的配置实验

目录 虚拟主机解释 实验介绍 修改配置文件 一&#xff1a;创建2个虚拟主机的网页根目录 二&#xff1a;修改2个虚拟主机的首页的内容 三&#xff1a;真实机器添加域名解析记录 四&#xff1a;测试 虚拟主机解释 Nginx的虚拟主机是指一台服务器上同时托管多个网站的能力。…

Mysql 日志(redolog, binlog, undoLog)

重做日志-redolog 是什么 innoDB存储引擎层面的日志&#xff0c;它的作用是当 数据更新过程中数据库发生异常导致提交的记录丢失 为什么 mysql的基本存储结构是页&#xff08;记录都在页里面&#xff09;&#xff0c;所以更新语句时&#xff0c;mysql需要先把要更新的语句找…

厉害了!12秒将百万数据通过EasyExcel导入MySQL数据库中

一、写在开头 我们在上一篇文章中提到了通过EasyExcel处理Mysql百万数据的导入功能&#xff08;一键看原文&#xff09;&#xff0c;当时我们经过测试数据的反复测验&#xff0c;100万条放在excel中的数据&#xff0c;4个字段的情况下&#xff0c;导入数据库&#xff0c;平均耗…

LibreNMS简介

目录 1 LibreNMS简单介绍1.1 LibreNMS介绍 2 安装2.1 Ubuntu安装1、安装依赖2、添加 librenms 用户3、下载 LibreNMS4、设置权限5、安装 PHP 依赖项6、设置时区7、配置 MariaDB8、配置 PHP-FPM9、配置 Web 服务器10、启用 lnms 命令11、配置 snmpd12、cron13、启用调度程序14、…

[Flutter GetX使用] Getx路由和状态管理-GetController使用过程中的踩坑记录

文章目录 问题 - Get.find() 报错!原因总结A:路由和控制器设计a1:项目中的Get路由aa1.项目路由结构aa2.本项目路由的注意点: B: GetController的冷知识C: 总结来看D: 一些参考资料 问题 - Get.find() 报错! 刚接触Getx, 遇到 Get.find()确找不到, 进而报错的问题, 一时间有点没…

AI算法-高数4-偏导数(理解梯度下降算法基础)

宋浩老师&#xff1a;6.3 偏导数_哔哩哔哩_bilibili 示例&#xff1a; 几何意义&#xff1a;

nodejs复习笔记

最近在复习nodejs&#xff0c;整理了一些笔记来记录和分享。 非常惭愧&#xff0c;我之前关于nodejs学习的一篇文章《nodejs全栈开发学习笔记》已经是2019年6月份的时候了&#xff0c;大概浏览了一下&#xff0c;发现当时很多不明白的地方&#xff0c;现在通过复习&#xff0c…

【大数据】HDFS、HBase操作教程(含指令和JAVA API)

目录 1.前言 2.HDFS 2.1.指令操作 2.2.JAVA API 3.HBase 3.1.指令操作 3.2.JAVA API 1.前言 本文是作者大数据专栏系列的其中一篇&#xff0c;前文中已经详细聊过分布式文件系统HDFS和分布式数据库HBase了&#xff0c;本文将会是它们的实操讲解。 HDFS相关前文&#x…

大学生体质测试|基于Springboot+vue的大学生体质测试管理系统设计与实现(源码+数据库+文档)

大学生体质测试管理系统 目录 基于Springboot&#xff0b;vue的大学生体质测试管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户功能模块 4教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算…

SEMI启动SiC专有技术项目

公司郑重声明&#xff0c;其正致力于筛选那些能够稳定输出、且可重复使用的关键参数性能。SEMI&#xff0c;这家SiC领域的佼佼者&#xff0c;已经启动了一项独具匠心的专有技术&#xff08;KGD&#xff09;筛选程序。该程序旨在为客户提供高品质的、经过严格电气分类与光学检验…

【谷粒商城】03创建商品模块

1.创建模块 2.创建项目微服务 商品服务、仓储服务、订单服务、优惠券服务、用户服务 共同&#xff1a; 1&#xff09;、web、openfeign 2&#xff09;、每一个服务&#xff0c;包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member) 3&#xff09;、模块名&#x…