QML----RK3568上使用QML直接播放RTP流视频学习总结

发布于:2024-11-28 ⋅ 阅读:(15) ⋅ 点赞:(0)

部分代码github

1 目标

如何在qml窗口中直接播放RTP视频流

2 终端机直接通过Gstreamer播放RTP流视频

首先是尝试直接在终端机中拉取视频流并播放,确保我的视频流达到了终端机,能够进行后续的操作.

2.1 本地视频文件推拉流

windows虚拟机推流本地视频文件,终端机接受推流

windows虚拟机推流 gst-launch-1.0 filesrc location=/home/firefly/Desktop/test.mp4 ! qtdemux ! rtph264pay config-interval=-1 ! udpsink host=224.224.224.224 port=5000

终端机拉流 gst-launch-1.0 -ve udpsrc uri=udp://224.224.224.224:5000 ! application/x-rtp,media=video,encoding-name=H264 ! rtph264depay ! h264parse ! mppvideodec ! videoconvert ! autovideosink sync=false

2.2 终端机摄像头推拉流

在终端机上插上摄像头后推拉流,在linux虚拟机上也插上了摄像头进行尝试,无法获取到

终端机推流 gst-launch-1.0 -ve v4l2src device=/dev/video0 ! image/jpeg,width=800,height=600,framerate=15/1 ! jpegdec ! mpph264enc ! h264parse ! rtph264pay ! udpsink host=224.224.224.224 port=5000

终端机拉流 gst-launch-1.0 -ve udpsrc uri=udp://224.224.224.224:5000 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! h264parse ! mppvideodec fast-mode=true ! videoconvert ! autovideosink"

直接拉取摄像头的话只能够拉取一路,摄像头就会占用,所以只能通过推流的方式来获得多路摄像头流

gst-launch-1.0 -ve v4l2src device=/dev/video0 ! videoconvert ! autovideosink

2.3 testvideosrc推拉流

testvideosrc是gstreamer自带的视频流,虚拟机和终端机都能够进行推流

推流 gst-launch-1.0 videotestsrc ! video/x-raw ! vp8enc deadline=1 ! rtpvp8pay ! udpsink host=224.224.224.224 port=5000

终端机拉流 gst-launch-1.0 -ve udpsrc uri=udp://224.224.224.224:5000 ! application/x-rtp,media=video,encoding-name=VP8 ! rtpvp8depay ! mppvideodec ! videoscale ! video/x-raw,width=1200,height=800 ! videoconvert ! autovideosink sync=false

3 终端机在qml中播放RTP流视频

确定RTP流能够到达终端机后,就可以开始研究怎么在qml里播放RTP流视频

3.1 思路 1 使用MediaPlayer调用gstreamer

使用MediaPlayer+Videooutput组件来播放视频,可以直接拉取rtsp流,对于rtp流不支持(需要混用gstreamer)

代码就是将QT自带的MediaPlayer中的 source改为gstreamer的命令.在自己电脑的虚拟机上有 qtvideosink可以让视频显示在qml上,但是对于终端机没有这个插件,尝试安装后也还是没有,因此该思路无法进行下去

import QtQuick 2.15
import QtQuick.Window 2.15
import QtMultimedia 5.13

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle{
            anchors.fill: parent
            color: "gray"

            MediaPlayer{
                    id:mediaPlayer;
                    source:"gst-pipeline:udpsrc uri=udp://224.224.224.224:5000 ! application/x-rtp,media=video,encoding-name=H264 ! rtph264depay ! h264parse ! mppvideodec ! videoconvert ! autovideosink"
                    autoPlay: true

         onError: {
                        console.log(errorString);
                    }
                }
            VideoOutput{
                anchors.fill:parent;
                source: mediaPlayer;
                //flushMode: VideoOutput.LastFrame;
            }
        }
}

3.2 思路 2 使用vlc-qt库

VLC-Qt 是一个 Qt 库,它封装了 VLC(VideoLAN)播放器的功能,使得在 Qt 应用中可以轻松地集成视频播放功能。具体来说,VLC-Qt 提供了一个可以嵌入到 Qt 应用中的组件,允许你在 QML 中显示视频,处理播放、暂停、音量调节、播放控制等功能。查询资料,相当于在qt里装了vlc这个播放器,资源消耗会很大.

安装编译流程如下.在linux上编译vlc-qtgithub

下载源码,下载cmake,新建一个build文件夹,cd进去,执行代码 cmake .. -DCMAKE_BUILD_TYPE=Debug

遇到报错,没有qt5Coreconfig,运行 sudo apt-get install qtdeclarative5-dev进行安装

file

遇到报错 Could not find libVLC,执行以下代码

sudo apt-get update  
sudo apt-get install libvlc-dev libvlccore-dev

file

执行完 cmake .. -DCMAKE_BUILD_TYPE=Debug,生成了一些文件,接着执行

sudo make -j8
sudo make install

安装完成后就能在src文件夹里看到这几个文件夹,把这几个文件夹里的.so文件都移植到自己的项目

file

使用它的exmpale,克隆 https://github.com/vlc-qt/examples.git

把9个.so文件复制到lib文件夹,修改simpleplayer的src.pro文件为你编译的路径和lib文件夹,运行就能看到拉流视频

LIBS       += -lVLCQtCore -lVLCQtWidgets
# Edit below for custom library location
LIBS       += -L/home/bft/vlc-qt/build/lib -lVLCQtCore -lVLCQtWidgets
INCLUDEPATH += /home/bft/vlc-qt/build/include

file

3.3 思路 3 qmlsink

qmlglsink 基于 OpenGL 来渲染视频帧。它利用 QML 的 OpenGL 支持,将接收到的视频数据直接传输给 GPU 进行渲染。视频数据通常是通过 GStreamer 管道解码和转换为 OpenGL 可以处理的格式(如 NV12 或 RGBA)。它可以将 OpenGL 渲染的内容嵌入到 QML 中的 Item 上。

qmlsink官方demogithub,可以很容易的跑通,需要安装gstreamer-qt的相关库.

主要流程如下
file

主要用到的RTP拉流管道代码 "udpsrc uri=udp://224.224.224.224:5000 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! h264parse ! mppvideodec ! glupload ! glcolorconvert ! qmlglsink name=sink0"

摄像头拉流代码 "v4l2src device=/dev/video0 ! image/jpeg,width=1920,height=1080,framerate=15/1 ! mppjpegdec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink0"

由于摄像头直接来流只能够拉取一路,所以是把摄像头的流先推流在拉取9路,代码和上边RTP拉流的代码一样

3.3.1 显示多路RTP视频

多路显示RTP视频其实跟显示一个是一样的,就是把所有的步骤都在来一遍,同时在qml里创建多个VideoItem组件

3.3.1.1 方法一 使用一个函数createPipeline创建管道

main.cpp

void createPipeline(GstElement **pipeline, GstElement **src, GstElement **rtp_h264_depay, GstElement **h264_parse, GstElement **decoder, GstElement **videorate, GstElement **capsfilter1, GstElement **capsfilter2, GstElement **glupload, GstElement **glcolorconvert, GstElement **sink)
{
    // 创建一个新的 GStreamer 管道
    *pipeline = gst_pipeline_new(NULL);
  
    // 创建 UDP 源元素
    *src = gst_element_factory_make("udpsrc", NULL);
    g_assert(src);

    // 创建 RTP H.264 depayload 元素
    *rtp_h264_depay = gst_element_factory_make("rtph264depay", NULL);
    g_assert(rtp_h264_depay);

    // 创建 H.264 解析器
    *h264_parse = gst_element_factory_make("h264parse", NULL);
    g_assert(h264_parse);

    // 创建解码器(mpp 视频解码器)
    *decoder = gst_element_factory_make("mppvideodec", NULL);
    g_assert(decoder);

    // 创建两个 CapsFilter,用于设置过滤条件
    *capsfilter1 = gst_element_factory_make("capsfilter", NULL);
    *capsfilter2 = gst_element_factory_make("capsfilter", NULL);

    // 创建 OpenGL 上传和颜色转换元素
    *glupload = gst_element_factory_make("glupload", NULL);
    g_assert(glupload);
    *glcolorconvert = gst_element_factory_make("glcolorconvert", NULL);
    g_assert(glcolorconvert);

    // 创建 QML 的视频渲染元素
    *sink = gst_element_factory_make("qmlglsink", NULL);
    g_assert(sink);

    // 设置 sink 属性
    g_object_set(G_OBJECT(*sink), "sync", FALSE, NULL);

    // 设置 UDP 源的属性,包括 URI 和缓冲区大小
    g_object_set(G_OBJECT(*src), "uri", "udp://224.224.224.224:5000", NULL);
    g_object_set(G_OBJECT(*src), "buffer-size", "2097152", NULL);

    // 设置 RTP caps 的过滤条件
    GstCaps *rtpCaps = gst_caps_from_string("application/x-rtp,media=video,encoding-name=H264");
    g_object_set(*capsfilter1, "caps", rtpCaps, NULL);

    // 设置帧率过滤条件
    GstCaps *scaleCaps = gst_caps_from_string("video/x-raw,framerate=15/1");
    g_object_set(*capsfilter2, "caps", scaleCaps, NULL);

    // 创建 videorate 元素,用于调整视频帧率
    *videorate = gst_element_factory_make("videorate", NULL);
}

// 递归打印 QML 项目树
void printChildren(QQuickItem *item, int depth = 0)
{
    QString indent = QString(" ").repeated(depth * 2);
    qDebug() << indent + item->objectName();

    // 遍历所有子项并递归打印
    for (QQuickItem *child : item->childItems())
    {
        printChildren(child, depth + 1);
    }
}

int main(int argc, char *argv[])
{
    int ret;

    // 设置 QML 使用的 CPU 线程数
    setenv("QML_CPU_THREAD_COUNT", "4", 1);

    // 初始化 GStreamer 库
    gst_init(&argc, &argv);

    // 使用 Qt Quick 创建 GUI 应用
    QGuiApplication app(argc, argv);

    // 定义多个管道和其相关元素
    GstElement *pipeline1, *src1, *rtp_h264_depay1, *h264_parse1, *decoder1, *videorate1, *capsfilter1_1, *capsfilter1_2, *glupload1, *glcolorconvert1, *sink1;
    GstElement *pipeline2, *src2, *rtp_h264_depay2, *h264_parse2, *decoder2, *videorate2, *capsfilter2_1, *capsfilter2_2, *glupload2, *glcolorconvert2, *sink2;


    // 初始化多个管道
    createPipeline(&pipeline1, &src1, &rtp_h264_depay1, &h264_parse1, &decoder1, &videorate1, &capsfilter1_1, &capsfilter1_2, &glupload1, &glcolorconvert1, &sink1);
    createPipeline(&pipeline2, &src2, &rtp_h264_depay2, &h264_parse2, &decoder2, &videorate2, &capsfilter2_1, &capsfilter2_2, &glupload2, &glcolorconvert2, &sink2);


    // 将元素添加到相应管道中
    gst_bin_add_many(GST_BIN(pipeline1), src1, capsfilter1_1, rtp_h264_depay1, h264_parse1, decoder1, videorate1, capsfilter1_2, glupload1, glcolorconvert1, sink1, NULL);
    gst_bin_add_many(GST_BIN(pipeline2), src2, capsfilter2_1, rtp_h264_depay2, h264_parse2, decoder2, videorate2, capsfilter2_2, glupload2, glcolorconvert2, sink2, NULL);


    // 链接管道内的所有元素
    if (!gst_element_link_many(src1, capsfilter1_1, rtp_h264_depay1, h264_parse1, decoder1, videorate1, capsfilter1_2, glupload1, glcolorconvert1, sink1, NULL))
    {
        g_print("Failed to link elements in pipeline 1\n");
    }
    gst_element_link_many(src2, capsfilter2_1, rtp_h264_depay2, h264_parse2, decoder2, videorate2, capsfilter2_2, glupload2, glcolorconvert2, sink2, NULL);


    // 加载 QML 界面
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QQuickWindow *rootObject = static_cast<QQuickWindow *>(engine.rootObjects().first());
    printChildren(rootObject->contentItem()); // 打印 QML 项目结构

    QTimer::singleShot(10000, [&engine, &sink1, &sink2, 
                          &pipeline1, &pipeline2, 
                          &videoItem1, &videoItem2, ]() {

    // 获取 QML 应用的根对象
    QQuickWindow *rootObject = static_cast<QQuickWindow *>(engine.rootObjects().first());

    // 打印 QML 的子项结构,方便调试
    printChildren(rootObject->contentItem());

    // 从 QML 对象中查找名为 "videoItem1" 至 "videoItem4" 的 QQuickItem
    videoItem1 = rootObject->findChild<QQuickItem *>("videoItem1");
    g_assert(videoItem1); // 确保找到 videoItem1,否则程序会中止
    videoItem2 = rootObject->findChild<QQuickItem *>("videoItem2");


    // 将找到的 QQuickItem 设置到对应的 GStreamer sink 上
    g_object_set(sink1, "widget", videoItem1, NULL);
    g_object_set(sink2, "widget", videoItem2, NULL);


    // 使用 QQuickWindow 的渲染任务机制设置管道状态为播放
    // 在渲染同步阶段之前执行 SetPlaying 操作
    rootObject->scheduleRenderJob(new SetPlaying(pipeline1), QQuickWindow::BeforeSynchronizingStage);
    rootObject->scheduleRenderJob(new SetPlaying(pipeline2), QQuickWindow::BeforeSynchronizingStage);

	});

    // 启动应用程序事件循环
    ret = app.exec();

    // 退出事件循环后,清理 GStreamer 管道资源

    // 将所有管道设置为 NULL 状态以释放资源
    gst_element_set_state(pipeline1, GST_STATE_NULL);
    gst_element_set_state(pipeline2, GST_STATE_NULL);

    // 释放管道对象的引用
    gst_object_unref(pipeline1);
    gst_object_unref(pipeline2);

    // 关闭 GStreamer
    gst_deinit();

    return ret;

}
3.3.1.2 方法二 使用管道字符串(更简单)

通过循环来创建管道和元素,减少代码量,修改方便

main.cpp

#define MAX_CHANNEL (9)  // 定义最大的视频流通道数(9路视频流)

// 这些是GStreamer命令,用于从UDP流中拉取视频流并显示
const char gstLaunchCmd[][600]=
{
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! videoscale ! video/x-raw,width=640,height=480 ! glupload ! glcolorconvert ! qmlglsink name=sink0",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! videoscale ! video/x-raw,width=640,height=480 ! glupload ! glcolorconvert ! qmlglsink name=sink1",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! videoscale ! video/x-raw,width=640,height=480 ! glupload ! glcolorconvert ! qmlglsink name=sink2",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! videoscale ! video/x-raw,width=640,height=480 ! glupload ! glcolorconvert ! qmlglsink name=sink3",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink4",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink5",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink6",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink7",
    "udpsrc uri=udp://224.224.224.224:5000 buffer-size=2097152 caps=\"application/x-rtp,media=video,encoding-name=H264\" ! rtph264depay ! queue ! h264parse ! mppvideodec fast-mode=true ! glupload ! glcolorconvert ! qmlglsink name=sink8"
};

// 定义每个视频通道的GStreamer元素名称
const char sinkName[][20]=
{
    "sink0", "sink1", "sink2", "sink3", "sink4", "sink5", "sink6", "sink7", "sink8"
};

// 每个视频Item的名称,用于在QML中查找并绑定
const char videoItemName[][20]=
{
    "videoItem0", "videoItem1", "videoItem2", "videoItem3", "videoItem4", "videoItem5", "videoItem6", "videoItem7", "videoItem8"
};

// 用于设置视频流播放状态的线程类
class SetPlaying : public QRunnable
{
public:
  SetPlaying(GstElement *);  // 构造函数,接收GStreamer元素
  ~SetPlaying();             // 析构函数

  void run ();  // 执行设置播放状态的函数

private:
  GstElement * pipeline_;  // GStreamer管道
};

// 构造函数,初始化pipeline指针
SetPlaying::SetPlaying (GstElement * pipeline)
{
  this->pipeline_ = pipeline ? static_cast<GstElement *> (gst_object_ref (pipeline)) : NULL;
}

// 析构函数,释放资源
SetPlaying::~SetPlaying ()
{
  if (this->pipeline_)
    gst_object_unref (this->pipeline_);
}

// 运行方法,设置GStreamer管道的播放状态为`PLAYING`
void
SetPlaying::run ()
{
  if (this->pipeline_)
    gst_element_set_state (this->pipeline_, GST_STATE_PLAYING);
}

// 递归打印QQuickItem及其子项(用于调试查看QML树结构)
void printChildren(QQuickItem *item ,int depth = 0)
{
    QString indent = QString(" ").repeated(depth*2);  // 添加缩进
    qDebug() << indent + item->objectName();  // 打印当前项的名称

    // 递归打印所有子项
    for(QQuickItem *child : item->childItems())
    {
        printChildren(child, depth + 1);
    }
}

int main(int argc, char *argv[])
{
  int ret;

  // 设置多线程环境变量
  setenv("QML_CPU_THREAD_COUNT","4",1);

  gst_init (&argc, &argv);  // 初始化GStreamer

  {
    QGuiApplication app(argc, argv);  // 创建Qt应用程序
    GstElement *pipeline[MAX_CHANNEL];  // 用于存储每路视频流的管道
    GstElement *sink[MAX_CHANNEL];      // 用于存储每路视频流的渲染sink

    // 遍历所有视频通道,初始化管道并启动播放
    for(int i = 0; i < MAX_CHANNEL; i++)
    {
        // 通过解析GStreamer命令字符串来创建每个视频流的管道
        pipeline[i] = gst_parse_launch(gstLaunchCmd[i], NULL);
  
        // 获取视频渲染sink元素(与GStreamer命令中的sink名称一致)
        sink[i] = gst_bin_get_by_name(GST_BIN(pipeline[i]), sinkName[i]);
  
        // 启动视频流的播放
        gst_element_set_state(pipeline[i], GST_STATE_PLAYING);
    }

    QQmlApplicationEngine engine;  // 创建QML应用引擎
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));  // 加载QML文件

    QQuickItem *videoItem[MAX_CHANNEL];  // 用于存储每路视频的QML项
    QQuickWindow *rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());  // 获取QML的根对象
    printChildren(rootObject->contentItem());  // 打印QML树结构(调试)

    // 遍历所有视频通道,找到对应的QML项并绑定GStreamer的sink元素
    for(int i = 0; i < MAX_CHANNEL; i++)
    {
        // 查找QML中与视频通道相关的项
        videoItem[i] = rootObject->findChild<QQuickItem *>(videoItemName[i]);
        g_assert(videoItem[i]);  // 确保QML项被正确找到

        // 设置GStreamer的sink元素,指向对应的视频QML项
        g_object_set(sink[i], "widget", videoItem[i], NULL);

        // 在渲染前将播放状态设置为`PLAYING`,确保视频能够播放
        rootObject->scheduleRenderJob(new SetPlaying(pipeline[i]), QQuickWindow::BeforeRenderingStage);
    }

    ret = app.exec();  // 启动Qt应用事件循环

    // 退出时停止每路视频流的播放并释放资源
    for(int i = 0; i < MAX_CHANNEL; i++)
    {
        gst_element_set_state(pipeline[i], GST_STATE_NULL);  // 停止播放
        gst_object_unref(pipeline[i]);  // 释放GStreamer管道资源
    }

  }

  gst_deinit ();  // 清理GStreamer资源

  return ret;  // 返回程序的退出状态
}

3.3.2 popup弹窗显示RTP视频流

一开始直接在popup里嵌入videoItem运行代码不显示.就开始思考是不是main.cpp里没有绑定上videoitem

在main.cpp打印了rootObject初始的item,可以看到只有一个,popup和它里边的videoItem组件根本就不显示

void printChildren(QQuickItem *item, int depth = 0) {
    // 打印当前项的名称
    QString indent = QString(" ").repeated(depth * 2);
    qDebug() << indent + item->objectName();

    // 递归遍历所有子项
    for (QQuickItem *child : item->childItems()) {
        printChildren(child, depth + 1);
    }
}

QQuickWindow *rootObject = static_cast<QQuickWindow *>(engine.rootObjects().first());
        printChildren(rootObject->contentItem());

file

手动打开弹窗,延迟10s打印就是找到了,也就是说弹窗界面不出现,rootobject就找不到在popup的videoitem2,所以也就播放不了视频

file

将寻找,绑定和播放的代码放入延时里,我们打开弹窗,等待一会就可以看见popup显示了视频

QTimer::singleShot(10000, [&engine,&sink2,&pipeline2](){
            QQuickWindow *rootObject = static_cast<QQuickWindow *>(engine.rootObjects().first());
            printChildren(rootObject->contentItem());
            QQuickItem* videoItem2 = rootObject->findChild<QQuickItem*>("videoItem2");
            g_object_set(sink2, "widget", videoItem2, nullptr);
            rootObject->scheduleRenderJob(new SetPlaying(pipeline2), QQuickWindow::BeforeSynchronizingStage);
        });

file

通过Repeater重复生成的videItem,rootObject延时也拿不到,9路只能是一个个创建

main.qml

   Popup{
       id:videopopup
       width:parent.width*0.8
       height:parent.height*0.8
       focus:true;
       objectName:"popup"
       Column{
           spacing:10
           anchors.fill:parent
           Repeater{
               id:firstrepeater
               model:3
               delegate:Row{
                   property int rowIndex :modelData
                   spacing:10
                   Repeater{
                       model:3
                           GstGLVideoItem {
                               anchors.centerIn:parent
                               objectName: "videoItem"+(index+1+rowIndex*3)
                               width:parent.width/3
                               height:parent.height/3
                       }


                   }
               }
           }
       }

3.3.3 拉流视频优化

在拉流的过程中出现了视频卡顿,CPU占用率高的问题,一些优化会减少卡顿,但会增加cpu消耗

1.在src设置 buffer-size 来调整缓冲区大小 uri=udp://224.224.224.224:5000 buffer-size=1048576

2.rtpjitterbuffer 元素用于缓冲 RTP 数据包以应对网络抖动。latency 参数指定了缓冲区的大小(以毫秒为单位),其作用是允许一定时间的延迟来确保平滑播放.设置后视频会丝滑一些,但会增加cpu消耗

3.videoscale元素来调整视频分辨率和帧率.videoscale ! video/x-raw,width=640,height=480,framerate=15/1

4.使用 queue元素可以在不同的处理阶段之间引入缓冲区,减缓数据流的速度,避免瓶颈。! rtph264depay! queue ! h264parse !

5.设置 sink的时钟同步属性. sync=true:当渲染速度跟不上播放时,qmlglsink 会丢弃一些过时的帧,以保持与时间的同步,避免延迟堆积,cpu消耗小,看着流畅一些。sync=false:所有帧都尽可能渲染,可能导致视频卡顿或播放延迟逐渐累积,cpu消耗大,对于大分辨率的视频会直接卡住。

4 最终结果

qmlglsink使用 OpenGL 渲染帧,帧作为纹理嵌入到QML 场景中,与其他 QML 图形叠加,资源消耗大,适合1-4路视频。

waylandsink直接使用 Wayland 渲染帧,视频输出到独立的 Wayland 表面,直接调用系统播放器,资源消耗小。
相比于qmlglsink,waylandsink直接调用系统播放器,cpu消耗小,更流畅

分辨率,帧率 %cpu RES 视觉效果
一推9解,摄像头,qmlglsink 640*480,15 160 360M 流畅
800*600,15 230 430M 流畅
1280*720,15 260 600M 一些卡顿
1920*1080,15 320 1.1g 明显卡顿
2路1080p 120 350M 一些卡顿
+rtpjitterbuffer latency=100 250 350M 流畅
1推9解摄像头,waylandsink 1280*720,15 70 100M 流畅
1920*1080,15 110 170M 流畅

额外

开发版查看gpu频率

最高频率800Mhz cat /sys/devices/platform/fde60000.gpu/devfreq/fde60000.gpu/load

查看cpu等参数,top按下大写P参数列表会从高到低排列

在终端显示帧率
std::chrono::steady_clock::time_point last_frame_time;
int frame_count = 0;

static GstPadProbeReturn frame_probe_callback(GstPad *pad, GstPadProbeInfo *info, gpointer user_data) {
    auto now = std::chrono::steady_clock::now();
    frame_count++;

    if (last_frame_time.time_since_epoch().count() > 0) {
        auto duration = std::chrono::duration_cast[std::chrono::milliseconds](std::chrono::milliseconds)(now - last_frame_time).count();
        if (duration > 1000) {
            float fps = frame_count * 1000.0 / duration;
            g_print("FPS: %.2f\n", fps);
            frame_count = 0;
            last_frame_time = now;
        }
    } else {
        last_frame_time = now;
    }

    return GST_PAD_PROBE_OK;
}

// 在链接 glcolorconvert 和 sink 后添加帧探针
GstPad *pad = gst_element_get_static_pad(glcolorconvert, "src");
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, frame_probe_callback, NULL, NULL);
gst_object_unref(pad);
vlc推拉流

首先先能够获取到RTP流,VLC->媒体->串流->添加一个视屏文件->选择RTP点击添加一个

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ip输入224.224.224.224,取消激活转码转码->选择video-h264

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

播放的话,打开一个新的vlc,打开网络串流,输入 rtp://224.224.224.224:5004