云计算百科
云计算领域专业知识百科平台

算法仿真平台搭建1-FFMPEG+RtspSever快速搭建一个RTSP服务器

一、前言

本文相关的全部源码和RtspSever库,我已打包上传,欢迎大家免费下载,testRTSPSever。 每一个嵌入式视觉算法工程师,都应该有一套属于自己的算法仿真和测试环境。可以方便地进行视频、图像等素材进行在线导入,可以方便地展示算法结果,可以快速地模拟应用场景,进行算法开发。在视频、图像文件等素材的在线导入模块,搭建一个属于自己的RTSP服务器,是一个理想的选择。 作为实时流媒体传输协议,RTSP服务器可无缝对接各类视觉采集设备,支持H.264/H.265编码格式的实时视频流传输,为算法验证提供稳定可靠的输入,降低算法开发过程中由于输入原因带来的困扰。

二、如何选择开源库

经过调研,使用C++搭建RTSP服务器,在安防领域使用频率最高的是Live555开源库。

2.1 Live555

Live555是一个为实时流媒体传输提供完整解决方案的跨平台C++开源框架,其特点可概括如下:

  • 完整实现RTSP(实时流控制协议)、RTP/RTCP(实时传输与控制协议)标准协议栈,支持HTTP、SIP等辅助协议扩展。
  • 原生支持H.264/H.265、MPEG、JPEG等20+种音视频格式,可通过派生类扩展新型编码。
  • 基于RTP协议实现<200ms级低延迟传输,支持自适应码率调节与网络抖动。
  • 支持搭建支持100+并发连接的RTSP服务器,应用于网络摄像头推流、视频会议系统。
  • 凭借<500KB的轻量化内存占用,适配智能家居、车载终端等资源受限场景。

功能越强大,其框架的开放程度也越高,但是,随之而来的学习成本也变高了。因此,在这里我没有选择Live555,而是选择了一个开放程度较低的RtspSever的开源项目。

2.2 RtspSever

在这里插入图片描述

RtspSever是C++11实现的RTSP服务器和推流器,源代码完全开放,可用于嵌入式的交叉编译,也可以在Windows下进编译开发。 在这里插入图片描述 RtspSever提供了一个例子,可以截取屏幕进行推流,这正好符合我的需求。因为,截取屏幕就肯定涉及到从内存中获取视频和图像,进行推流。

2.3 FFMPEG

对于视频的编码部分,我选择的是FFMPEG。FFMPEG是一个跨平台开源多媒体处理框架,由多个核心库组成,主要用于音视频编解码、封装转换、流媒体传输等场景。其核心功能与模块包括:

  • libavcode: 提供音视频编解码功能,支持H.264、AAC等主流格式,支持GPU加速。

  • libavformat: 处理音视频封装格式(如MP4、FLV),实现解封装(Demuxing)与封装(Muxing)。

  • libavfilter: 支持音视频滤镜处理,例如添加水印、调整分辨率或音频变声。

  • 其他核心模块: libswscale:图像格式转换(如YUV与RGB互转); libavdevice:硬件设备采集与渲染(如摄像头、屏幕录制) libswresample:音频重采样与格式处理。

FFMPEG广泛应用于视频转码、直播推流、音视频编辑等领域,支持Windows、Linux、macOS等系统,并可通过命令行工具(ffmpeg、ffplay等)快速实现多媒体处理。 FFMPEG的安装和配置,在我的文章VS2022配置FFMPEG有详细介绍,这里不再重复说明。

三、代码详解

在确定好开源库后,接下来的事情就是开工撸代码了。超出了我的预期,我仅仅使用了不到200行的代码就完成了RTSP的服务器。

3.1 代码设计

  • 初始化阶段:

    • 创建RTSP服务器并绑定端口。
    • 定义媒体会话,配置H264视频源。
    • 初始化FFmpeg图像转换和H264编码器。
  • 主循环阶段:

    • 生成测试图像(绿色圆圈动画)。
    • 将BGR图像转换为NV12格式。
    • 使用H264编码器压缩视频帧。
    • 将编码后的数据通过RTSP协议推送。
    • 本地窗口显示实时画面。
  • 资源释放:

    • 退出循环后释放编码器、帧内存等资源。
  • 代码逻辑流程如下图所示:

    #mermaid-svg-86QNj0kG3yvq5SRR {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .error-icon{fill:#552222;}#mermaid-svg-86QNj0kG3yvq5SRR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-86QNj0kG3yvq5SRR .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-86QNj0kG3yvq5SRR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-86QNj0kG3yvq5SRR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-86QNj0kG3yvq5SRR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-86QNj0kG3yvq5SRR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-86QNj0kG3yvq5SRR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-86QNj0kG3yvq5SRR .marker.cross{stroke:#333333;}#mermaid-svg-86QNj0kG3yvq5SRR svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-86QNj0kG3yvq5SRR .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .cluster-label text{fill:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .cluster-label span{color:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .label text,#mermaid-svg-86QNj0kG3yvq5SRR span{fill:#333;color:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .node rect,#mermaid-svg-86QNj0kG3yvq5SRR .node circle,#mermaid-svg-86QNj0kG3yvq5SRR .node ellipse,#mermaid-svg-86QNj0kG3yvq5SRR .node polygon,#mermaid-svg-86QNj0kG3yvq5SRR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-86QNj0kG3yvq5SRR .node .label{text-align:center;}#mermaid-svg-86QNj0kG3yvq5SRR .node.clickable{cursor:pointer;}#mermaid-svg-86QNj0kG3yvq5SRR .arrowheadPath{fill:#333333;}#mermaid-svg-86QNj0kG3yvq5SRR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-86QNj0kG3yvq5SRR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-86QNj0kG3yvq5SRR .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-86QNj0kG3yvq5SRR .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-86QNj0kG3yvq5SRR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-86QNj0kG3yvq5SRR .cluster text{fill:#333;}#mermaid-svg-86QNj0kG3yvq5SRR .cluster span{color:#333;}#mermaid-svg-86QNj0kG3yvq5SRR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-86QNj0kG3yvq5SRR :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    开始

    初始化RTSP服务器

    创建媒体会话并绑定H264源

    配置FFmpeg转换和编码器

    进入主循环

    生成测试图像

    BGR转NV12

    H264编码

    推送RTSP数据

    显示本地预览

    循环直到退出?

    释放资源

    3.2 代码详解

    /* 头文件引入 */
    #include <fstream>
    #include <iostream>
    #include <queue>
    #include <mutex>
    #include <atomic>
    #include <thread>
    #include <winsock2.h> // Windows网络库
    #include <iphlpapi.h> // IP辅助库
    #pragma comment(lib, "ws2_32.lib") // 链接Winsock库
    #pragma comment(lib, "iphlpapi.lib") // 链接IP辅助库

    extern "C" { // FFmpeg库使用C语言接口
    #include <libavcodec/avcodec.h> // 音视频编解码
    #include <libavformat/avformat.h> // 媒体格式处理
    #include <libswscale/swscale.h> // 图像缩放与格式转换
    }

    #include "xop/RtspServer.h" // RTSP服务器库
    #include "net/Timer.h" // 定时器
    #include "opencv2/opencv.hpp" // OpenCV图像处理库
    int main() {
    /* RTSP服务配置 */
    std::string suffix = "live"; // 流后缀
    std::string ip = "192.168.1.14"; // 服务器IP
    std::string port = "554"; // RTSP默认端口
    std::string rtsp_url = "rtsp://" + ip + ":" + port + "/" + suffix; // 完整URL

    /* 初始化事件循环和RTSP服务器 */
    std::shared_ptr<xop::EventLoop> event_loop(new xop::EventLoop()); // 创建事件循环
    std::shared_ptr<xop::RtspServer> server = xop::RtspServer::Create(event_loop.get()); // 创建RTSP服务器实例
    if (!server->Start("0.0.0.0", atoi(port.c_str()))) { // 启动服务器(监听所有网卡)
    printf("RTSP Server启动失败,端口:%s\\n", port.c_str());
    return 0;
    }

    /* 创建媒体会话 */
    xop::MediaSession* session = xop::MediaSession::CreateNew("live"); // 创建会话
    session->AddSource(xop::channel_0, xop::H264Source::CreateNew()); // 添加H264源

    // 客户端连接/断开回调
    session->AddNotifyConnectedCallback([](xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port) {
    printf("客户端连接: IP=%s 端口=%hu\\n", peer_ip.c_str(), peer_port);
    });
    session->AddNotifyDisconnectedCallback([](...) { /* 类似处理断开事件 */ });

    xop::MediaSessionId session_id = server->AddSession(session); // 注册会话
    std::cout << "播放地址: " << rtsp_url << std::endl;

    /* 视频参数配置 */
    const int frame_width = 512, frame_height = 512;

    /* FFmpeg图像转换配置 */
    SwsContext* sws_ctx = sws_getContext( // 创建颜色空间转换上下文
    frame_width, frame_height, AV_PIX_FMT_BGR24, // 输入参数(BGR格式)
    frame_width, frame_height, AV_PIX_FMT_NV12, // 输出参数(NV12格式)
    SWS_BILINEAR, nullptr, nullptr, nullptr); // 转换算法

    AVFrame* sws_frame = av_frame_alloc(); // 分配转换后的帧内存
    // …(设置sws_frame参数并分配缓冲区)

    /* H264编码器初始化 */
    const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264); // 查找编码器
    AVCodecContext* enc_ctx = avcodec_alloc_context3(encoder); // 创建编码器上下文
    enc_ctx->width = frame_width; // 分辨率
    enc_ctx->height = frame_height;
    enc_ctx->time_base = {1, 25}; // 时间基(25FPS)
    // …(其他编码参数设置)
    avcodec_open2(enc_ctx, encoder, nullptr); // 打开编码器

    /* 主循环 */
    while (true) {
    // 生成测试图像(绿色圆圈)
    cv::Mat frame = cv::Mat::zeros(frame_height, frame_width, CV_8UC3);
    // …(绘制动态圆圈)

    /* BGR转NV12 */
    sws_scale(sws_ctx, bgr_frame->data, ..., sws_frame->data, ...);

    /* H264编码 */
    avcodec_send_frame(enc_ctx, sws_frame); // 发送帧到编码器
    while (avcodec_receive_packet(enc_ctx, enc_pkt) == 0) { // 接收编码后的包
    // 封装视频帧并推送到RTSP服务器
    xop::AVFrame videoFrame = {0};
    // …(填充数据并调用PushFrame)
    }

    cv::imshow("server", frame); // 显示本地预览
    cv::waitKey(3); // 等待3ms
    }

    /* 资源释放 */
    av_packet_free(&enc_pkt);
    av_frame_free(&sws_frame);
    // …(其他资源清理)
    }


    3.3、关键API说明表格

    3.3.1. sws_getContext()

    参数类型说明
    srcW int 源图像宽度
    srcH int 源图像高度
    srcFormat AVPixelFormat 源像素格式(如AV_PIX_FMT_BGR24)
    dstW int 目标图像宽度
    dstH int 目标图像高度
    dstFormat AVPixelFormat 目标像素格式(如AV_PIX_FMT_NV12)
    flags int 缩放算法(如SWS_BILINEAR)
    srcFilter SwsFilter* 源滤波器(通常为nullptr)
    dstFilter SwsFilter* 目标滤波器(通常为nullptr)
    param const double* 算法参数(通常为nullptr)
    功能 创建图像缩放/格式转换上下文
    返回值 SwsContext* 转换上下文句柄

    3.3. 2. avcodec_find_encoder()

    参数类型说明
    id enum AVCodecID 编码器ID(如AV_CODEC_ID_H264)
    功能 根据ID查找已注册的编码器
    返回值 const AVCodec* 编码器指针(失败返回NULL)

    3.3.3. avcodec_open2()

    参数类型说明
    avctx AVCodecContext* 编码器上下文
    codec const AVCodec* 目标编码器
    options AVDictionary** 附加选项(通常为nullptr)
    功能 使用指定编码器初始化上下文
    返回值 int 0成功,负数错误码

    四、测试

    运行程序,使用VLC进行拉流测试: 在这里插入图片描述 延时有点大,但是,视频流还是很稳定的。有懂的小伙伴,可以在评论区留言,怎么解决这个延时的问题。

    五、小结

    • 对小白来说,开源库,选择好入门的,封装程度高的。
    • 可以做下一步了,在开发板完成RTSP流的接收和解码。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 算法仿真平台搭建1-FFMPEG+RtspSever快速搭建一个RTSP服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!