news 2026/6/2 3:46:58

在虚拟机中搭建srs服务器并且完成推流(Linux版)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在虚拟机中搭建srs服务器并且完成推流(Linux版)
(一)可以先从别人的服务器上面进行拉流然后完成推流测试形成一个闭环,熟悉好这个过程以后就可以自己搭建服务器,然后拉取网络流推送到自己的服务器上面!

1部署Ubuntu22.04版(浏览器直接下载)

2.浏览器下载VMware Workstation Pro(部署虚拟机)

3.虚拟机环境框架搭好以后配置必要工具:在虚拟机上配置OpenCV,FFmpeg(这里可能会需要依赖Python环境,所以可以先下好Python的相关依赖版本)等工具

在虚拟机终端执行:

sudo apt install build-essential cmake git libavcodec-dev libavformat-dev libswscale-dev

4.环境配好了,进行拉流测试:测试是为了验证网络的联通性!

接下来做一个转码调试然后把这个推到另外一个公网上面!

#include <opencv2/opencv.hpp> #include <iostream> #include <cstdio> #include <string> #include <thread> #include <chrono> #include <sstream> #include <csignal> #ifdef _WIN32 #define POPEN _popen #define PCLOSE _pclose #else #define POPEN popen #define PCLOSE pclose #endif static void sleep_ms(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } struct FfmpegPipe { FILE* pipe = nullptr; std::string cmd; bool open(const std::string& command) { close(); cmd = command; pipe = POPEN(cmd.c_str(), "w"); return pipe != nullptr; } void close() { if (pipe) { fflush(pipe); PCLOSE(pipe); pipe = nullptr; } } bool writeFrame(const cv::Mat& frame) { if (!pipe || frame.empty()) return false; const size_t bytes = frame.total() * frame.elemSize(); const size_t written = fwrite(frame.data, 1, bytes, pipe); return written == bytes; } bool isOpen() const { return pipe != nullptr; } }; static bool openInputStream(cv::VideoCapture& cap, const std::string& inputRtmp, int retryTimes = 5) { for (int i = 1; i <= retryTimes; ++i) { if (cap.open(inputRtmp, cv::CAP_FFMPEG)) { cap.set(cv::CAP_PROP_BUFFERSIZE, 1); return true; } std::cerr << "警告:第 " << i << " 次打开输入流失败,1 秒后重试...\n"; sleep_ms(1000); } return false; } static std::string buildFfmpegCmd(int width, int height, double fps, const std::string& outputRtmp) { if (fps <= 1.0 || fps > 120.0) { fps = 25.0; } std::ostringstream oss; oss << "ffmpeg -hide_banner -loglevel warning -y " << "-fflags nobuffer " << "-flags low_delay " << "-max_delay 0 " << "-f rawvideo " << "-pix_fmt gray " << "-s " << width << "x" << height << " " << "-r " << fps << " " << "-i pipe:0 " << "-an " << "-c:v libx264 " << "-preset veryfast " << "-tune zerolatency " << "-pix_fmt yuv420p " << "-g " << static_cast<int>(fps * 2) << " " << "-keyint_min " << static_cast<int>(fps) << " " << "-sc_threshold 0 " << "-f flv " << "-flvflags no_duration_filesize " << outputRtmp; return oss.str(); } int main(int argc, char** argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif // 默认输入 RTMP std::string inputRtmp = "rtmp://frp-hat.com:35903/live/A....(别人的公网地址)"; // 默认输出 RTMP std::string outputRtmp = "rtmp://frp-hat.com:35903/live/...(自己的公网地址)"; if (argc >= 2) { inputRtmp = argv[1]; } if (argc >= 3) { outputRtmp = argv[2]; } std::cout << "输入流: " << inputRtmp << std::endl; std::cout << "输出流: " << outputRtmp << std::endl; cv::VideoCapture cap; // 打开输入流 if (!openInputStream(cap, inputRtmp, 10)) { std::cerr << "错误:无法打开输入流: " << inputRtmp << std::endl; return -1; } std::cout << "成功打开输入流,读取第一帧..." << std::endl; cv::Mat frame; if (!cap.read(frame) || frame.empty()) { std::cerr << "错误:无法读取第一帧,输入流可能不可用。" << std::endl; return -1; } int width = frame.cols; int height = frame.rows; double fps = cap.get(cv::CAP_PROP_FPS); if (fps <= 1.0 || fps > 120.0) { fps = 25.0; // 兜底,一般是30. } std::cout << "输入流信息: " << width << "x" << height << ", fps=" << fps << std::endl; std::string ffmpegCmd = buildFfmpegCmd(width, height, fps, outputRtmp); std::cout << "启动 FFmpeg 推流命令:" << std::endl; std::cout << ffmpegCmd << std::endl; FfmpegPipe ffmpegPipe; if (!ffmpegPipe.open(ffmpegCmd)) { std::cerr << "错误:无法启动 ffmpeg 进程。" << std::endl; return -1; } cv::Mat grayFrame; cv::Mat resizedGrayFrame; int frameCount = 0; const int inputReconnectWaitMs = 2000; const int ffmpegReconnectWaitMs = 3000; while (true) { // 如果 FFmpeg 管道断了,重启 if (!ffmpegPipe.isOpen()) { std::cerr << "警告:FFmpeg 管道已断开,准备重启...\n"; sleep_ms(ffmpegReconnectWaitMs); if (!ffmpegPipe.open(ffmpegCmd)) { std::cerr << "错误:重启 FFmpeg 失败,继续等待...\n"; sleep_ms(ffmpegReconnectWaitMs); continue; } std::cout << "FFmpeg 重启成功。\n"; } // 读取输入帧 if (!cap.read(frame) || frame.empty()) { std::cerr << "警告:输入流断开或读取到空帧,准备重连...\n"; cap.release(); sleep_ms(inputReconnectWaitMs); if (!openInputStream(cap, inputRtmp, 5)) { std::cerr << "错误:输入流重连失败,继续重试...\n"; sleep_ms(inputReconnectWaitMs); continue; } std::cout << "输入流重连成功,重新读取第一帧...\n"; if (!cap.read(frame) || frame.empty()) { std::cerr << "警告:重连后仍然无法读取帧,继续等待...\n"; sleep_ms(inputReconnectWaitMs); continue; } } // 转灰度 cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY); // 如果尺寸变化,统一缩放到最初尺寸 if (grayFrame.cols != width || grayFrame.rows != height) { cv::resize(grayFrame, resizedGrayFrame, cv::Size(width, height)); } else { resizedGrayFrame = grayFrame; } // 直接写灰度单通道数据 if (!ffmpegPipe.writeFrame(resizedGrayFrame)) { std::cerr << "错误:写入 ffmpeg 管道失败,准备重启 FFmpeg...\n"; ffmpegPipe.close(); sleep_ms(ffmpegReconnectWaitMs); continue; } frameCount++; if (frameCount % 100 == 0) { std::cout << "已推送 " << frameCount << " 帧..." << std::endl; } } cap.release(); ffmpegPipe.close(); std::cout << "推流结束,共处理 " << frameCount << " 帧。" << std::endl; return 0; }

这是一个(已有拉流地址并且有推流地址)边拉边推,外加转码的的c++脚本文件,先桌面创建好这个脚本文件库,然后终端调用就可以完成拉流—转码—推流的闭环!

ok,这是测试的结果,那么这个推理的脚本文件程序是没有问题的!

(二)第一步:在 Linux 虚拟机中搭建并启动 SRS 服务器

  1. 安装依赖并下载源码
    在你的 Linux 虚拟机终端中,首先安装必要的编译工具,然后从 Gitee 拉取 SRS 的源码(很好,没有gitee账号只能用GitHub的官方源然后进行链接了):
sudo apt update && sudo apt install -y git gcc g++ make git clone -b 5.0release https://github.com/ossrs/srs.git cd srs/trunk
  1. 编译 SRS
    执行配置和编译命令,生成 SRS 的可执行程序:
./configure make -j$(nproc)
  1. 启动 SRS 服务
    编译完成后,使用默认的配置文件启动 SRS。SRS 默认会监听1935端口(用于 RTMP 推流)和8080端口(用于 HTTP-FLV/HLS 播放):(我的端口是1935)
  2. 以下是服务器的配置文件!
./objs/srs -c conf/srs.conf

启动后,可以通过./etc/init.d/srs status检查服务是否正常运行。

(很好,我的srs服务器平台已经搭建好了)

又是烦人的网络问题,真是难崩!(本来想做拉流程序的检测的,可惜!)

换了一个桥接方式,直接给它安排成物理连接网络,ok,没有问题了!

然后验证一下之前的推帧程序,ok,推帧程序是没有问题的!

将推帧的处理结果推送到本地 SRS:

2.获取虚拟机的 IP 地址
在虚拟机终端输入ip addrifconfig,找到我的局域网 IP(例如192.168.x.x)。

3.修改并执行推流命令
将目标地址替换为我的虚拟机服务器 IP。例如:

ffmpeg -i rtmp://frp-hat.com:35903/live/... -c:v copy -c:a copy -f flv rtmp://192.168.1.111:1935/live/livestream
  • -i rtmp://frp-hat.com:35903/live/...:指定输入源(也就是拉取的公网流)。
  • -c:v copy -c:a copy:表示直接复制视频和音频流,不做重新编码。这样 CPU 占用极低,且延迟最小。
  • -f flv:强制输出格式为 flv(RTMP 推流的标准格式)。
  • rtmp://192.168.1.111:1935/live/livestream:指定输出目标(本地 SRS 服务器)

(我的方法逻辑是把已经在我的公网上推流成功的转码流再推到我的srs服务器上)

但是现在遇到的监听端口出错的问题,那么就来解决一下问题:(终端运行)

ps aux | grep srs ss -lntp | grep 1935

先检查 SRS 有没有启动,检查 1935 端口有没有监听成功!

ok,监听的端口打开了,接下来可以按原来的思路跑一下!(建议使用-c copy命令,这样更快捷!)

4.检查 SRS 是否收到流:
tail -f ~/Desktop/srs/trunk/objs/srs.log

很好收的到流!

最终环节:验证成功后推流

推流成功后,可以在虚拟机内部,或者与虚拟机在同一局域网下的其他电脑/手机上进行拉流播放测试,确认黑白视频流是否成功送达本地 SRS:

使用 VLC 播放器测试:

rtmp://192.168.1.111/live/...(上文有提到的服务器地址)

ok,全过程结束!包含搭建,推理加最终测试!(如有什么不理解以及不同的观点和解法欢迎评论区指点!)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/2 3:46:03

Windows Server时间同步踩坑实录:除了配IP,别忘了检查这3个关键服务项(WinServer 2012/2016/2019通用)

Windows Server时间同步深度排查指南&#xff1a;超越基础配置的3个关键维度当你按照标准教程配置完Windows Server的NTP客户端后&#xff0c;发现系统时间依然顽固地停留在错误状态&#xff0c;这种看似简单的故障往往隐藏着更深层次的系统机制问题。作为经历过数十次时间同步…

作者头像 李华
网站建设 2026/6/2 3:38:22

深入Triton Server后端:手写一个自定义Backend来支持你的冷门模型框架

深入Triton Server后端&#xff1a;手写一个自定义Backend来支持你的冷门模型框架当主流深度学习框架如PyTorch和TensorFlow占据大部分市场份额时&#xff0c;许多创新模型却诞生在JAX、MindSpore或其他定制化C库中。这些"非主流"框架往往面临部署难题——缺乏成熟的…

作者头像 李华
网站建设 2026/6/2 3:38:06

Ansaldo 0000-9409-00功率限制控制器电路板

Ansaldo 0000-9409-00 功率限制控制器电路板作为工业电源系统的功率管理核心组件&#xff0c;其产品特点如下&#xff1a;中间15条专用于功率限制与过载保护控制。适用于直流调速装置及功率触发系统。采用工业级FR-4基板&#xff0c;适应恶劣环境。多层电路设计&#xff0c;提升…

作者头像 李华
网站建设 2026/6/2 3:38:02

别再乱点Menuconfig了!ESP-IDF项目文件结构保姆级拆解,新手避坑指南

ESP-IDF项目文件结构深度解析&#xff1a;从入门到精通的避坑手册第一次打开ESP-IDF项目时&#xff0c;那种面对满屏陌生文件的茫然感我至今记忆犹新。作为一个从Arduino转向ESP32开发的"过来人"&#xff0c;我完全理解新手看到CMakeLists.txt和sdkconfig时的困惑——…

作者头像 李华