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

在虚拟机中搭建srs服务器并且完成推流(Linux版) 一可以先从别人的服务器上面进行拉流然后完成推流测试形成一个闭环熟悉好这个过程以后就可以自己搭建服务器然后拉取网络流推送到自己的服务器上面1部署Ubuntu22.04版(浏览器直接下载2.浏览器下载VMware Workstation Pro部署虚拟机3.虚拟机环境框架搭好以后配置必要工具在虚拟机上配置OpenCVFFmpeg这里可能会需要依赖Python环境所以可以先下好Python的相关依赖版本等工具在虚拟机终端执行sudo apt install build-essential cmake git libavcodec-dev libavformat-dev libswscale-dev4.环境配好了进行拉流测试测试是为了验证网络的联通性接下来做一个转码调试然后把这个推到另外一个公网上面#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_castint(fps * 2) -keyint_min static_castint(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 服务器安装依赖并下载源码在你的 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编译 SRS执行配置和编译命令生成 SRS 的可执行程序./configure make -j$(nproc)启动 SRS 服务编译完成后使用默认的配置文件启动 SRS。SRS 默认会监听1935端口用于 RTMP 推流和8080端口用于 HTTP-FLV/HLS 播放我的端口是1935以下是服务器的配置文件./objs/srs -c conf/srs.conf启动后可以通过./etc/init.d/srs status检查服务是否正常运行。(很好我的srs服务器平台已经搭建好了又是烦人的网络问题真是难崩本来想做拉流程序的检测的可惜换了一个桥接方式直接给它安排成物理连接网络ok没有问题了然后验证一下之前的推帧程序ok推帧程序是没有问题的将推帧的处理结果推送到本地 SRS2.获取虚拟机的 IP 地址在虚拟机终端输入ip addr或ifconfig找到我的局域网 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强制输出格式为 flvRTMP 推流的标准格式。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全过程结束包含搭建推理加最终测试如有什么不理解以及不同的观点和解法欢迎评论区指点