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

news2024/11/23 23:46:23

总结

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

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)

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

下周

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中霖教育:税务师考试可以申请免试吗?

符合下列相应条件之一的&#xff0c;可报名参加税务师职业资格考试&#xff1a; 1.取得经济学、法学、管理学学科门类大学本科及以上学历(学位);或者取得其他学科门类大学本科学历&#xff0c;从事经济、法律相关工作满1年。 2.取得经济学、法学、管理学学科门类大学专科学历…

【HCIP学习】BGP选路、过滤及属性

一、BGP路由选路原则&#xff08;13条&#xff09; 1、首先丢弃下一跳&#xff08;NEXT_HOP&#xff09;不可达的路由&#xff1b; 2、优选Preferred-value值最大的路由&#xff1b;默认为0&#xff1b; Preferred-value&#xff1a;定义&#xff1a;首选项。 属性值&#…

windows系统安装Ubuntu子系统

安装前先在 控制面板 中打开 程序与功能选项 &#xff0c;点击 启用或关闭Windows功能&#xff1a; 勾选 适用于 Linux的Windows子系统 和 虚拟机平台 、 Hyper-v 。 重启电脑后再 Microsoft Store Windows应用商店 中下载合适的Ubuntu版本。 运行Ubuntu程序&#xff0c;如出现…

在xAnyLabeling中加载自己训练的yolov8s-obb模型进行半自动化标注

任务思路&#xff1a; 先使用xAnyLabeling标注一部分样本&#xff0c;训练出v1版本的yolov8-obb模型&#xff0c;然后加载yolov8-obb模型到xAnyLabeling中对其余样本进行半自动化标注。节省工作量。 任务流程&#xff1a; 1.准备xAnyLabeling标注工具 下载代码&#xff0c;…

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

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

零基础学MySQL

1. 零基础学MySQL 1.1 数据库简介 1.1.1 数据库三层结构 1. 所谓安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database manage system) 2. 一个数据库中可以创建多个表,以保存数据(信息)。 3. 数据…

OpenCompass 大模型评测实战学习笔记

大模型开源开放评测体系 “司南” (OpenCompass2.0)&#xff0c;用于为大语言模型、多模态模型等提供一站式评测服务。其主要特点如下&#xff1a; 开源可复现&#xff1a;提供公平、公开、可复现的大模型评测方案 全面的能力维度&#xff1a;五大维度设计&#xff0c;提供 70…

iLogtail 社区开源之夏活动来了!

作者&#xff1a;玄飏 在这个充满活力的夏日&#xff0c;随着阳光一同灿烂的是开源精神的光辉与创新的火花。iLogtail 社区高兴地宣布&#xff0c;我们正式加入开源之夏 2024 的行列&#xff0c;诚邀每一位怀揣梦想与激情的学生开发者&#xff0c;共同开启一场探索技术前沿、贡…

2024年,Web开发新趋势!

随着我们迈入新的一年&#xff0c;现在正是审视2024年网页开发领域开始流行哪些趋势的绝佳时机。回顾2023年的一系列更新&#xff0c;以下是来年一些热门话题的概览。 自主托管有回归的趋势 近些年&#xff0c;自主托管一直是网页开发者和公司托管其应用程序的默认方式。开发…

【本地部署及云化部署】

文章目录 本地部署及云化部署介绍 文章目录 文章目录一、本地部署模式二、云化部署模式总结 一、本地部署模式 需建设专业化机房&#xff0c;系统应用、前端软件全部安装到本地服务器上。需要专业的IT、网络安全、DBA、电气化工程师进行维护。近些年勒索病毒安全事件频发&am…

【MySQL】SQL基本知识点DML(2)

目录 1.DML添加数据 2.DML-修改数据 &#xff08;1&#xff09;改​编辑 &#xff08;2&#xff09;删​编辑​编辑 3.DQL-基本查询 &#xff08;1&#xff09;查询多个字段​编辑​编辑​编辑 &#xff08;2&#xff09;设置别名 &#xff08;3&#xff09;去重操作 4…

跟TED演讲学英文:Teachers need real feedback by Bill Gates

Teachers need real feedback Link: https://www.ted.com/talks/bill_gates_teachers_need_real_feedback Speaker: Bill Gates Date: May 2013 文章目录 Teachers need real feedbackIntroductionVocabularyTranscriptSummary后记 Introduction Until recently, many teach…

云动态摘要 2024-05-12

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 [免费试用]即刻畅享自研SaaS产品 腾讯云 2024-04-25 涵盖办公协同、营销拓客、上云安全保障、数据分析处理等多场景 云服务器ECS试用产品续用 阿里云 2024-04-14 云服务器ECS试用产品续用…

汽车IVI中控开发入门及进阶(十七):IVI的功耗管理

汽车人机界面(HMI)系统旨在使驾驶员能够在不分心的情况下与车辆互动。HMI可以通过触摸板、按钮或语音系统在人和机器之间建立更自然的互动。对连接解决方案、低成本HMI软件和增强的用户体验(UX)的需求不断增加,使得平视显示器(HUD)、后座娱乐系统、基于转向的控制、仪表…

Sass深度解析:性能优化的秘密

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

文件相关api

File 代表操作系统的文件对象&#xff0c;它提供了定位文件位置&#xff0c;获取文件中的信息&#xff0c;创建文件&#xff0c;删除文件等,但不能读写内容。 构造器&#xff1a; public File(String pathname) 根据文件路径&#xff0c;创建文件对象。&#xff08;只会根据…

JeeSite 平台 Spring Boot 3 体验版发布,一个 Java 快速开发平台

引言 是时候为 Spring Boot 3 做准备了&#xff0c;2018年2月 Spring Boot 进入 2.0 时代&#xff0c;距今已经 5 年了。2022 年 11 月 Spring Boot 3.0 正式发布&#xff0c;它将基于 Spring Framework 6.0&#xff0c;并且需要 Java 17 版本&#xff0c;同时它也将是 Jakart…

YOLOv5改进 | 注意力机制 | 通道和空间的双重作用的CBAM注意力机制

在深度学习目标检测领域&#xff0c;YOLOv5成为了备受关注的模型之一。本文给大家带来的是通道和空间的双重作用的CBAM注意力机制。文章在介绍主要的原理后&#xff0c;将手把手教学如何进行模块的代码添加和修改&#xff0c;并将修改后的完整代码放在文章的最后&#xff0c;方…

Kafka效率篇-提升效率三板斧

kafka在效率上做了很多的努力。最初的一个使用场景是处理网页上活跃的数据&#xff0c;它往往有非常大的体量&#xff0c;每个页面都能产生数十条写入。而且我们假设每条消息都会被至少一个消费者消费&#xff08;通常是多个&#xff09;&#xff0c;因此&#xff0c;我们努力让…

对称加密介绍

一、什么是对称加密 对称密钥算法(Symmetric-key algorithm)&#xff0c;又称为对称加密、私钥加密、共享密钥加密&#xff0c;是密码学中的一类加密算法。 对称加密的特点是&#xff0c;在加密和解密时使用相同的密钥&#xff0c;或是使用两个可以简单地相互推算的密钥。 这…