【JS】基于node-media-server搭建流媒体服务器示例

news2024/10/5 17:23:18

😏★,°:.☆( ̄▽ ̄)/$:.°★ 😏
这篇文章主要介绍基于node-media-server搭建流媒体服务器示例。
学其所用,用其所学。——梁启超
欢迎来到我的博客,一起学习,共同进步。
喜欢的朋友可以关注一下,下次更新不迷路🥞

文章目录

    • :smirk:1. node-media-server介绍
    • :blush:2. 环境安装与配置
    • :satisfied:3. 应用示例

😏1. node-media-server介绍

node-media-server 是一个基于 Node.js 的流媒体服务器,它提供了构建和管理实时音视频流媒体应用程序所需的功能。它是一个开源项目,具有灵活性和可扩展性,适用于各种流媒体应用场景。

以下是一些 node-media-server 的特点和功能:

1.RTMP支持:node-media-server 支持 RTMP(Real-Time Messaging Protocol)协议,用于接收和传输实时的音视频流。RTMP 适用于实时直播和互动应用等场景。

2.多路并发流支持:node-media-server 具有多路并发流处理能力,可以同时处理多个流媒体的接收、转码、推流和录制等操作。

3.高性能和低延迟:node-media-server 的设计注重高性能和低延迟,使其适用于实时应用场景,如实时直播、互动直播和视频聊天等。

4.支持多种编码格式:node-media-server 支持多种常用的音视频编码格式,如 H.264、AAC、VP8 等,使其能够处理不同类型的流媒体数据。

5.功能丰富的 API:node-media-server 提供了丰富的 API,方便开发人员进行配置和管理。你可以通过编写代码来定制和扩展服务器的功能。

6.高度可配置:node-media-server 具有灵活的配置选项,允许你根据特定需求进行定制。你可以配置服务器的端口、流媒体路径、认证方式等。

😊2. 环境安装与配置

# 安装nodejs和ffmpeg
sudo apt install nodejs ffmpeg
# 安装node-media-server
npm install node-media-server

😆3. 应用示例

创建app.js,写入:

const NodeMediaServer= require('node-media-server');
const config = {
    rtmp: {
        port: 1935,
        chunk_size: 60000,
        gop_cache: true,
        ping: 60,
        ping_timeout: 30
    },
    http: {
        port: 8000,
        allow_origin: '*',
    }
};
 
var nms = new NodeMediaServer(config)
nms.run();

运行该程序:node app.js

准备好一个mp4视频,用ffmpeg命令行推流(也可自己写程序):

ffmpeg -re -i input.mp4 -c:v copy -c:a copy -f flv rtmp://localhost:1935/live/stream_name

最后效果示例,地址在http://localhost:8000/admin/

在这里插入图片描述

另外,也可以用C++程序来实现视频推流,下面是一个示例:

// defer.h
#ifndef FFMPEG_EXAMPLES_DEFER_H
#define FFMPEG_EXAMPLES_DEFER_H

#include <utility>

template<typename F> class defer_raii
{
public:
    // copy/move construction and any kind of assignment would lead to the cleanup function getting
    // called twice. We can't have that.
    defer_raii(defer_raii&&)                 = delete;
    defer_raii(const defer_raii&)            = delete;
    defer_raii& operator=(const defer_raii&) = delete;
    defer_raii& operator=(defer_raii&&)      = delete;

    // construct the object from the given callable
    template<typename FF> defer_raii(FF&& f) : cleanup_function(std::forward<FF>(f)) {}

    // when the object goes out of scope call the cleanup function
    ~defer_raii() { cleanup_function(); }

private:
    F cleanup_function;
};

template<typename F> defer_raii<F> defer_func(F&& f) { return { std::forward<F>(f) }; }

#define DEFER_ACTUALLY_JOIN(x, y) x##y
#define DEFER_JOIN(x, y)          DEFER_ACTUALLY_JOIN(x, y)
#ifdef __COUNTER__
#define DEFER_UNIQUE_VARNAME(x) DEFER_JOIN(x, __COUNTER__)
#else
#define DEFER_UNIQUE_VARNAME(x) DEFER_JOIN(x, __LINE__)
#endif

#define defer(lambda__)                                                                                    \
    [[maybe_unused]] const auto& DEFER_UNIQUE_VARNAME(_defer_) = defer_func([&]() { lambda__; })

#endif // !FFMPEG_EXAMPLES_DEFER_H
// main.cpp
extern "C" {
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
}

#define __STDC_CONSTANT_MACROS
#include <stdint.h>

#include "defer.h"
// #include "logging.h"
#include <iostream>
#include <chrono>
#include <thread>
#include "fmt/format.h"

int main(int argc, char* argv[])
{
    // Logger::init(argv[0]);

    if (argc < 3) {
        std::cout << "pushing <input_video> <rtmp_name>" << std::endl;
        return -1;
    }

    const char * in_filename = argv[1];
    const char * rtmp_name = argv[2];

    AVFormatContext * decoder_fmt_ctx = nullptr;
    avformat_open_input(&decoder_fmt_ctx, in_filename, nullptr, nullptr);
    avformat_find_stream_info(decoder_fmt_ctx, nullptr);

    int video_stream_idx = av_find_best_stream(decoder_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    // CHECK(video_stream_idx >= 0);

    // decoder
    auto decoder = avcodec_find_decoder(decoder_fmt_ctx->streams[video_stream_idx]->codecpar->codec_id);
    // CHECK_NOTNULL(decoder);

    // decoder context
    AVCodecContext * decoder_ctx = avcodec_alloc_context3(decoder);
    // CHECK_NOTNULL(decoder_ctx);

    avcodec_parameters_to_context(decoder_ctx, decoder_fmt_ctx->streams[video_stream_idx]->codecpar);
    avcodec_open2(decoder_ctx, decoder, nullptr);

    av_dump_format(decoder_fmt_ctx, 0, in_filename, 0);

    //
    // output
    //
    AVFormatContext * encoder_fmt_ctx = nullptr;
    avformat_alloc_output_context2(&encoder_fmt_ctx, nullptr, "flv", nullptr);
    avformat_new_stream(encoder_fmt_ctx, nullptr);

    // encoder
    auto encoder = avcodec_find_encoder_by_name("libx264");
    // CHECK_NOTNULL(encoder);

    AVCodecContext *encoder_ctx = avcodec_alloc_context3(encoder);
    // CHECK_NOTNULL(encoder_ctx);

    AVDictionary* encoder_options = nullptr;
    av_dict_set(&encoder_options, "crf", "23", AV_DICT_DONT_OVERWRITE);
    av_dict_set(&encoder_options, "threads", "auto", AV_DICT_DONT_OVERWRITE);
    defer(av_dict_free(&encoder_options));

    // encoder codec params
    encoder_ctx->height = decoder_ctx->height;
    encoder_ctx->width = decoder_ctx->width;
    encoder_ctx->pix_fmt = decoder_ctx->pix_fmt;
    encoder_ctx->sample_aspect_ratio = decoder_ctx->sample_aspect_ratio;
    encoder_ctx->framerate = av_guess_frame_rate(decoder_fmt_ctx, decoder_fmt_ctx->streams[video_stream_idx], nullptr);

    // time base
    encoder_ctx->time_base = av_inv_q(encoder_ctx->framerate);
    encoder_fmt_ctx->streams[0]->time_base = encoder_ctx->time_base;

    avcodec_open2(encoder_ctx, encoder, &encoder_options);
    avcodec_parameters_from_context(encoder_fmt_ctx->streams[0]->codecpar, encoder_ctx);

    if (!(encoder_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_open(&encoder_fmt_ctx->pb, rtmp_name, AVIO_FLAG_WRITE);
    }

    avformat_write_header(encoder_fmt_ctx, nullptr);

    av_dump_format(encoder_fmt_ctx, 0, rtmp_name, 1);

    AVPacket * in_packet = av_packet_alloc();
    defer(av_packet_free(&in_packet));
    AVPacket * out_packet = av_packet_alloc();
    defer(av_packet_free(&out_packet));
    AVFrame * in_frame = av_frame_alloc();
    defer(av_frame_free(&in_frame));

    int64_t first_pts = AV_NOPTS_VALUE;

    while(av_read_frame(decoder_fmt_ctx, in_packet) >= 0) {
        if (in_packet->stream_index != video_stream_idx) {
            continue;
        }

        int ret = avcodec_send_packet(decoder_ctx, in_packet);
        while(ret >= 0) {
            av_frame_unref(in_frame);
            ret = avcodec_receive_frame(decoder_ctx, in_frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                std::cout << "[PUSHING] avcodec_receive_frame()";
                return ret;
            }

            in_frame->pict_type = AV_PICTURE_TYPE_NONE;

            // sleep @{
            first_pts = first_pts == AV_NOPTS_VALUE ? av_gettime_relative() : first_pts;

            int64_t ts = av_gettime_relative() - first_pts;

            int64_t pts_us = av_rescale_q(in_frame->pts, encoder_fmt_ctx->streams[0]->time_base, { 1, AV_TIME_BASE });
            int64_t sleep_us = std::max<int64_t>(0, pts_us - ts);

            std::cout << fmt::format("[PUSHING] pts = {:>6.3f}s, ts = {:>6.3f}s, sleep = {:>4d}ms, frame = {:>5d}, fps = {:>5.2f}",
                                     pts_us / 1000000.0, ts / 1000000.0, sleep_us / 1000,
                                     encoder_ctx->frame_number, encoder_ctx->frame_number / (ts / 1000000.0));
            av_usleep(sleep_us);
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            // @}

            ret = avcodec_send_frame(encoder_ctx, in_frame);
            while(ret >= 0) {
                av_packet_unref(out_packet);
                ret = avcodec_receive_packet(encoder_ctx, out_packet);

                if(ret == AVERROR(EAGAIN)) {
                    break;
                } else if(ret == AVERROR_EOF) {
                    std::cout << "[PUSHING] EOF";
                    break;
                } else if(ret < 0) {
                    std::cout << "[PUSHING] avcodec_receive_packet()";
                    return ret;
                }

                out_packet->stream_index = 0;
                av_packet_rescale_ts(out_packet, decoder_fmt_ctx->streams[video_stream_idx]->time_base, encoder_fmt_ctx->streams[0]->time_base);

                if (av_interleaved_write_frame(encoder_fmt_ctx, out_packet) != 0) {
                    std::cout <<"[PUSHING] av_interleaved_write_frame()";
                    return -1;
                }
            }
        }
        av_packet_unref(in_packet);
    }

    avformat_close_input(&decoder_fmt_ctx);
    avformat_free_context(encoder_fmt_ctx);

    avcodec_free_context(&decoder_ctx);
    avcodec_free_context(&encoder_ctx);

    return 0;
}

编译与运行:

g++ -o main main.cpp -lavformat -lavcodec -lavutil -lfmt -D__STDC_CONSTANT_MACROS
./main test.mp4 rtmp://127.0.0.1:1935/live/test
# 还不太完善,可改进

请添加图片描述

以上。

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

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

相关文章

【Shell的运行原理以及Linux当中的权限问题】

Shell的运行原理以及Linux当中的权限问题 Shell的运行原理Linux当中的权限问题Linux权限的概念如何实现用户账号之间的切换如何仅提升当前指令的权限如何将普通用户添加到信任列表 Linux权限管理文件访问者的分类 (人)文件类型和访问权限 (事物属性)文件权限值的表示方法文件访…

少儿编程考级:智慧启迪还是智商税?

在当前科技日新月异的时代背景下&#xff0c;少儿编程教育日益受到家长和社会的广泛关注。与此同时&#xff0c;各类少儿编程考级应运而生&#xff0c;引发了公众对于其价值和意义的深度探讨。一部分人认为这是对孩子逻辑思维与创新能力的有效锻炼&#xff0c;是智慧启迪的重要…

业务拓展利器!跨境电商如何选对代理IP?IPIDEA 一键连接全球商机!

文章目录 一、跨境电商发展与海外代理IP的重要性1.1 跨境电商的发展现状1.2 海外代理IP在跨境电商中的重要性 二、选对代理IP品牌的关键因素三、IPIDEA海外IP代理的优势3.1 IPIDEA的优势3.2 IPIDEA提供的代理类型 四、使用IPIDEA爬虫实战五、总结 一、跨境电商发展与海外代理IP…

算法——二分查找算法

1. 二分算法是什么&#xff1f; 简单来说&#xff0c;"二分"指的是将查找的区间一分为二&#xff0c;通过比较目标值与中间元素的大小关系&#xff0c;确定目标值可能在哪一半区间内&#xff0c;从而缩小查找范围。这个过程不断重复&#xff0c;每次都将当前区间二分…

五、Redis之发布订阅及事务管理

5.1 发布订阅 5.1.1 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。下图展示了频道 channel1 &#xff0c;以及订阅这个频道的三个客户端 —— client1 、client2 …

2 月 5 日算法练习- 动态规划

DP&#xff08;动态规划&#xff09;全称Dynamic Programming&#xff0c;是运筹学的一个分支&#xff0c;是一种将复杂问题分解成很多重叠的子问题、并通过子问题的解得到整个问题的解的算法。 在动态规划中有一些概念&#xff1a; n<1e3 [][] &#xff0c;n<100 [][][…

Jenkins配置http请求github,发布release

学无止境&#xff0c;气有浩然&#xff01; Jenkins配置http请求github&#xff0c;发布release 前言Jenkins配置github配置在这里插入图片描述 打完收工! 前言 工作中进行了github迁移&#xff0c;原先的gitlab中配置的Jenkins的CI/CD步骤需要发布到Github发布release版本&am…

基于SpringBoot+Vue的电影影城购票管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

网站不收录,与服务器不备案有关吗

随着互联网的快速发展&#xff0c;网站已经成为企业、个人和机构宣传和展示自己的重要平台。然而&#xff0c;许多网站在建设完成后却面临着不收录的问题&#xff0c;这给网站的管理者和拥有者带来了很大的困扰。其中&#xff0c;一些人认为&#xff0c;网站不收录的原因与服务…

DBeaver连接人大金仓数据库

人大金仓的驱动 1. 打开DBeaver软件&#xff0c;点击“数据库”&#xff0c;选择“驱动管理器” 2. 点击“新建”进行达人大金仓驱动管理器配置。 3、创建驱动-设置&#xff1a;驱动名称、类名、url 驱动名称&#xff1a;人大金仓&#xff1b; 类名&#xff1a;com.kingbas…

MongoDB系列之WiredTiger引擎

概述 关系型数据库MySQL有InnoDB存储引擎&#xff0c;存储引擎很大程度上决定着数据库的性能。 在MongoDB早期版本中&#xff0c;默认使用MMapV1存储引擎&#xff0c;其索引就是一个B-树&#xff08;也称B树&#xff09;。 从MongoDB 3.0开始引入WiredTiger&#xff08;以下…

Linux Shell编程系列--开篇

一、目的 从本篇开始介绍Linux Shell脚本编程&#xff0c;为简单起见&#xff0c;本篇中以一个显示当前时间的shell脚本来帮助大家理解shell脚本的组成。 SHELL脚本中可以包含变量、函数、命令等部分。 二、介绍 我们通过vim新建一个myshell.sh的脚本&#xff0c;然后输入以下…

控制台npm start终止不了?

控制台npm start终止不了&#xff1f; 在开发的过程中我遇到了这样的问题&#xff0c;想结束控制台3002端口运行&#xff0c;但是ControlC不起作用&#xff0c;不管我敲多少遍&#xff0c;依旧没有任何动静&#xff1a; 再次启动的时候它又会自动启动3003端口&#xff0c;300…

指针的学习3

目录 字符指针变量 数组指针变量 二维数组传参的本质 函数指针变量 函数指针变量的创建 函数指针变量的使用 两段有趣的代码 typedef关键字 函数指针数组 转移表 回调函数&#xff1a; 字符指针变量 int main() {char arr[10] "abcdef";char* p1 arr;//…

面试经典150题——判断子序列

​"Success is not final, failure is not fatal: It is the courage to continue that counts." - Winston Churchill 1. 题目描述 2. 题目分析与解析 2.1 思路一——双指针 按照双指针的解法应该大家都能比较快的想出来&#xff0c;就是一个指针pointS指向字符…

消息中间件(消息队列)简介

MQ&#xff08;message queue&#xff09;消息队列&#xff0c;也叫消息中间件。消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;成为异步RPC的主要手段之一。它是类似于数据库一样需要独立部…

消息中间件之RocketMQ源码分析(六)

Consumer消费方式 RocketMQ的消费方式包含Pull和Push两种 Pull方式。 用户主动Pull消息&#xff0c;自主管理位点&#xff0c;可以灵活地掌控消费进度和消费速度&#xff0c;适合流计算、消费特别耗时等特殊的消费场景。 缺点也显而易见&#xff0c;需要从代码层面精准地控制…

【发票识别】新增针对图片发票的识别(升级中)

说明 为了完善发票识别的功能&#xff0c;目前发票识别支持发票图片格式的识别&#xff0c;增加可用性。 体验 体验地址&#xff1a;https://invoice.behappyto.cn/invoice-service/ 体验地址上面有示例的发票&#xff0c;可以下载上传识别或者复制url地址进行识别。 技术栈…

数据结构.二叉树

一、树的基本概念 二、树的常考性质 三、二叉树的基本概念 四、二叉树的顺序存储 五、二叉树的链式存储 六、二叉树的遍历

深入剖析 Cortex-M4 微控制器在嵌入式系统中的特性和优势

Cortex-M4 微控制器是 ARM Cortex-M 架构中的一种类型&#xff0c;它具有许多功能和特性&#xff0c;使其在嵌入式系统中具有显著的优势。本文将深入剖析 Cortex-M4 微控制器的特性和优势&#xff0c;并提供示例代码来演示其用法。 ✅作者简介&#xff1a;热爱科研的嵌入式开发…