[JS] Пример построения сервера потокового мультимедиа на базе node-media-server
[JS] Пример построения сервера потокового мультимедиа на базе node-media-server

😏1. Введение в узел-медиа-сервер.

node-media-server основан на Node.js Сервер потокового мультимедиа, предоставляющий функциональные возможности, необходимые для создания приложений потоковой передачи аудио и видео в реальном времени и управления ими. Это проект с открытым исходным кодом, обладающий гибкостью и масштабируемостью, подходящий для различных сценариев приложений потокового мультимедиа.

Вот некоторые node-media-server Особенности и функции:

1. Поддержка RTMP: узел-медиа-сервер поддерживает протокол RTMP (протокол обмена сообщениями в реальном времени), который используется для приема и передачи аудио- и видеопотоков в реальном времени. RTMP подходит для таких сценариев, как прямая трансляция в реальном времени и интерактивные приложения.

2. Поддержка многоканального одновременного потока: узел-медиа-сервер имеет несколько возможностей одновременной обработки потоков и может выполнять такие операции, как прием, транскодирование, потоковая передача и запись нескольких потоковых мультимедиа одновременно.

3. Высокая производительность и низкая задержка. Конструкция узла-медиа-сервера ориентирована на высокую производительность и низкую задержку, что делает его пригодным для сценариев приложений в реальном времени, таких как прямая трансляция в реальном времени, интерактивная прямая трансляция и видеочат.

4. Поддержка нескольких форматов кодирования: узел-медиа-сервер поддерживает множество часто используемых форматов кодирования аудио и видео, таких как H.264, AAC, VP8 и т. д., что позволяет ему обрабатывать различные типы потоковых медиаданных.

5. API с богатым набором функций: node-media-server предоставляет богатый API для облегчения настройки и управления разработчиками. Вы можете настроить и расширить функциональность сервера, написав код.

6. Широкие возможности настройки: узел-медиа-сервер имеет гибкие параметры конфигурации, позволяющие настроить его в соответствии с конкретными потребностями. Вы можете настроить порт сервера, путь потоковой передачи, метод аутентификации и т. д.

😊2. Установка и настройка среды.

Язык кода:javascript
копировать
# Установите nodejs и ffmpeg.
sudo apt install nodejs ffmpeg
# Установите узел-медиа-сервер
npm install node-media-server

😆3. Примеры приложений.

создаватьapp.js,Писать:

Язык кода:javascript
копировать
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Нажатие командной строки(Вы также можете написать свою собственную программу):

Язык кода:javascript
копировать
ffmpeg -re -i input.mp4 -c:v copy -c:a copy -f flv rtmp://localhost:1935/live/stream_name

Пример финального эффекта,Адрес находится по адресуhttp://localhost:8000/admin/

Кроме того, вы также можете использовать программы C++ для реализации потокового видео. Вот пример:

Язык кода:javascript
копировать
// 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
Язык кода:javascript
копировать
// 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;
}

Скомпилируйте и запустите:

Язык кода:javascript
копировать
g++ -o main main.cpp -lavformat -lavcodec -lavutil -lfmt -D__STDC_CONSTANT_MACROS
./main test.mp4 rtmp://127.0.0.1:1935/live/test
# Еще не идеально, можно улучшить
boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода