部分代码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
进行安装
遇到报错 Could not find libVLC,执行以下代码
sudo apt-get update
sudo apt-get install libvlc-dev libvlccore-dev
执行完 cmake .. -DCMAKE_BUILD_TYPE=Debug
,生成了一些文件,接着执行
sudo make -j8
sudo make install
安装完成后就能在src文件夹里看到这几个文件夹,把这几个文件夹里的.so文件都移植到自己的项目
使用它的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
3.3 思路 3 qmlsink
qmlglsink
基于 OpenGL 来渲染视频帧。它利用 QML 的 OpenGL 支持,将接收到的视频数据直接传输给 GPU 进行渲染。视频数据通常是通过 GStreamer 管道解码和转换为 OpenGL 可以处理的格式(如 NV12 或 RGBA)。它可以将 OpenGL 渲染的内容嵌入到 QML 中的 Item
上。
qmlsink官方demogithub,可以很容易的跑通,需要安装gstreamer-qt的相关库.
主要流程如下
主要用到的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());
手动打开弹窗,延迟10s打印就是找到了,也就是说弹窗界面不出现,rootobject就找不到在popup的videoitem2,所以也就播放不了视频
将寻找,绑定和播放的代码放入延时里,我们打开弹窗,等待一会就可以看见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);
});
通过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