GstOverlay 绑定多个gtk 窗口

news2024/10/2 3:21:43

主题说明

在spice stream 模式下,为了实现流畅的显示,利用gstvideooverlay 接口实现了gstreamer pipeline 的输出直接绑定到gtk 的窗口下。

然而spice客户端采用的是playbin 插件当前只能绑定一个窗口,当需要采用多窗口模式时,当前的流程机制使用不了,需要进一步探索

其他插件来实现overlay 多窗口。本文给出了多屏绑定的本地测试demo。

实现流程

gst pipeline 多路显示

要实现多屏方案,首先需要实现gstreamer pipeline 能够多路输出,并且每一路可以截取图像的一部分送到窗口显示。下图以两路显示说明

读取h264
解析h264
h264解码
video_crop1
play1
video_crop2
play2

gstreamer 组件选择

读取h264数据

测试采用本地读取h264文件的方式,插件采用 filesrc

解析h264

解析h264数据 插件采用 h264parse

h264 解码

h264 解码插件有很多(硬件解码,软件解码), 本次测试选择 avdec_h264, 后续在spice 客户端显示中可以根据不同的硬件采用不同的解码插件。

多路模式

多路流程采用 tee 和 queue 两种插件实现

VideoCrop

从一个图像中crop 出不同的部分送入到不同的窗口,针对 videocrop 和 videobox两种都进行了测试。

play

支持videooverlay 接口的playsink 有两种 ximagesink 和 xvimagesink。 对这两种在pipeline 中都进行了测试。

gstreamer pipeline

filesrc
h264parse
avdec_h264
tee
queue1
queue2
videocrop1
videocrop2
sink1
sink2

gstreamer 测试命令

crop 测试

单路

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! videocrop top=0 left=0 right=320 bottom=0 ! jpegenc ! filesink location=./img_t1.jpeg #左边一半 gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! videocrop top=0 left=320 right=0 bottom=0 ! jpegenc ! filesink location=./img_t2.jpeg #右边一半 gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! videocrop top=240 left=0 right=0 bottom=0 ! jpegenc ! filesink location=./img_t3.jpeg #下边一半 gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! videocrop top=0 left=0 right=0 bottom=240 ! jpegenc ! filesink location=./img_t3.jpeg #上边一半

多路

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videocrop top=0 left=0 right=320 bottom=0 ! jpegenc ! filesink location=./img_l.jpeg t. ! queue ! videocrop top=0 left=320 right=0 bottom=0 ! jpegenc ! filesink location=./img_r.jpeg #两路同时切分成两个通道

通过以上的命令验证,多路的crop 可以实现。

显示测试

单路
  • (videocrop + ximagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videocrop top=0 left=0 right=320 bottom=0 ! videoconvert ! ximagesink

  • (videobox + ximagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videobox top=0 left=0 right=320 bottom=0 ! videoconvert ! ximagesink

  • (videobox + xvimagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videobox top=0 left=0 right=320 bottom=0 ! xvimagesink

说明: videocrop + xvimagesink 会报错

多路
  • (videobox + ximagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videobox top=0 left=0 right=320 bottom=0 ! videoconvert ! ximagesink t. ! queue ! videobox top=0 left=320 right=0 bottom=0 ! videoconvert ! ximagesink

  • (videocrop + ximagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videocrop top=0 left=0 right=320 bottom=0 ! videoconvert ! ximagesink t. ! queue ! videocrop top=0 left=320 right=0 bottom=0 ! videoconvert ! ximagesink

  • (videobox + xvimagesink模式)

gst-launch-1.0 -v filesrc location=./h264.h264 ! h264parse ! avdec_h264 ! tee name=t t. ! queue ! videobox top=0 left=0 right=320 bottom=0 ! xvimagesink t. ! queue ! videobox top=0 left=320 right=0 bottom=0 ! xvimagesink

通过命令行测试有三种方案pipeline 可以完成多路crop 显示输出。然后需要进一步测试三个方案在绑定两个GTK窗口进行测试(目前以两个窗口进行测试)

gstreamer 加gtk 代码

#include <string.h>
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
#include <gdk/gdk.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#elif defined (GDK_WINDOWING_QUARTZ)
#include <gdk/gdkquartz.h>
#endif

#define MAX_WINDOWS  8
typedef struct PlayData
{
  GstElement *sink;
  guintptr    video_window_handle;
  GtkWidget *widget;
} PlayData;

typedef struct CustomData
{
    GstElement *pipeline;   
    GstState    state;         // Current stat of the pipeline
    int num_monitor;
    PlayData  play_datas[MAX_WINDOWS]; 
} CustomData;

static void init_custom_data(CustomData *data)
{
  if (NULL == data)
  {
    g_error("CustomData not allocate memory");
  }

  data->pipeline = NULL;
  data->state = GST_STATE_NULL;

  for (int i = 0; i< MAX_WINDOWS; i++)
  {
    data->play_datas[i].sink=NULL;
    data->play_datas[i].video_window_handle = 0;
    data->play_datas[i].widget = NULL;
  }  

}

static void realize_cb (GtkWidget *widget,  PlayData *data) {
  GdkWindow *window = gtk_widget_get_window (widget);
  guintptr window_handle;

  if (!gdk_window_ensure_native (window))
    g_error ("Couldn't create native window needed for GstVideoOverlay!");

  /* Retrieve window handler from GDK */
#if defined (GDK_WINDOWING_WIN32)
  window_handle = (guintptr)GDK_WINDOW_HWND (window);
#elif defined (GDK_WINDOWING_QUARTZ)
  window_handle = gdk_quartz_window_get_nsview (window);
#elif defined (GDK_WINDOWING_X11)
  window_handle = GDK_WINDOW_XID (window);
#endif
  /* Pass it to playbin, which implements VideoOverlay and will forward it to the video sink */
  data->video_window_handle = window_handle;
  gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->sink), data->video_window_handle);
 
}

static void create_ui(PlayData *data)
{

    GtkWidget *video_window;  // The drawing area where the video will be shown

    video_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    // gtk_window_set_title(main_window, "main_window");

    // video_window = gtk_drawing_area_new();
    gtk_window_set_title(video_window, "video_window");
    g_signal_connect(G_OBJECT(video_window), "realize",
                        G_CALLBACK(realize_cb), data);
    gtk_widget_set_double_buffered (video_window, FALSE);

    //   gtk_window_set_default_size(GTK_WINDOW(main_window), 640, 480);
    // gtk_container_add (GTK_CONTAINER (main_window), video_window);
  
    gtk_widget_show_all(video_window);

    gtk_widget_realize (video_window);

}

int main(int argc, char **argv)
{
  CustomData data = {};
  init_custom_data(&data);
  data.num_monitor = 2;
  GstElement *filesrc;
  GstStateChangeReturn ret;
  GstElement *pipeline, *h264parse ,  *avdec_h264, *tee,  *queue1, *queue2, *videocrop1, *videocrop2, *videoconvert1, *videoconvert2, *sink1, *sink2;
  GstBus *bus, *bus1;  
  GstCaps *caps;
  GstMessage *msg;
  /* Initialize GTK */
  gtk_init (&argc, &argv);

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  pipeline = gst_pipeline_new("pipeline");
  filesrc  = gst_element_factory_make ("filesrc", "filesrc");

  g_object_set (filesrc, "location", "/home/kylin/v2/screenCapture.h264", NULL);

  h264parse = gst_element_factory_make("h264parse", "h264parse");
  avdec_h264 = gst_element_factory_make("avdec_h264", "avdec_h264");

  tee = gst_element_factory_make ("tee", "tee");
  queue1 = gst_element_factory_make ("queue", "queue1");
  queue2 = gst_element_factory_make ("queue", "queue2");

  videocrop1 = gst_element_factory_make ("videobox", "videocrop1");
  videocrop2 = gst_element_factory_make ("videobox", "videocrop2");

//   videoconvert1 = gst_element_factory_make ("videoconvert", "videoconvert1");
//   videoconvert2 = gst_element_factory_make ("videoconvert", "videoconvert2");
  sink1 = gst_element_factory_make("xvimagesink", "sink1");
  sink2 = gst_element_factory_make("xvimagesink", "sink2");

  GstBin *bin = GST_BIN(pipeline);
  gst_bin_add(bin, filesrc); 
  gst_bin_add(bin, h264parse); 
  gst_bin_add(bin, avdec_h264); 
  gst_bin_add(bin, tee); 
  gst_bin_add(bin, queue1);
  gst_bin_add(bin, videocrop1);
  gst_bin_add(bin, sink1);
  gst_bin_add(bin, queue2);
  gst_bin_add(bin, videocrop2);
  gst_bin_add(bin, sink2);


  gboolean   link =  gst_element_link_many(filesrc, h264parse, NULL) && 
                     gst_element_link_many(h264parse, avdec_h264, NULL) &&
                     gst_element_link_many(avdec_h264, tee, NULL) &&
                     gst_element_link_many(queue1, videocrop1,NULL) &&       
                     gst_element_link_many(videocrop1, sink1, NULL) &&     
                     gst_element_link_many(queue2, videocrop2, NULL) &&      
                     gst_element_link_many(videocrop2, sink2, NULL) ;
                    
  if (!link)
  {
    g_printerr("link failed");
    return -1;
  }

  GstPadTemplate *templ = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(tee), "src_%u");

  GstPad* tee_pad1 =gst_element_request_pad (tee, templ, NULL, NULL);
  GstPad* queue_pad1 = gst_element_get_static_pad (queue1, "sink");
  GstPad* tee_pad2 = gst_element_request_pad (tee, templ, NULL, NULL);
  GstPad* queue_pad2 =gst_element_get_static_pad (queue2, "sink");

  if (gst_pad_link (tee_pad1, queue_pad1) != GST_PAD_LINK_OK ||
    gst_pad_link (tee_pad2, queue_pad2) != GST_PAD_LINK_OK) {
    g_printerr("pad link failed");
    return -1;
  }
  g_object_set (videocrop1,
    "left", 0,
    "top", 0,
    "right", 512,
    "bottom", 0, NULL);
 g_object_set (videocrop2,
    "left", 512,
    "top", 0,
    "right", 0,
    "bottom", 0, NULL);

   data.pipeline = pipeline;

   data.play_datas[0].sink = sink1;
   data.play_datas[1].sink = sink2;

   create_ui(&data.play_datas[0]);
   create_ui(&data.play_datas[1]);   

  ret = gst_element_set_state ( data.pipeline  , GST_STATE_PLAYING);

  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref ( data.pipeline );
    return -1;
  }
 

  gtk_main ();

  gst_element_set_state ( data.pipeline , GST_STATE_NULL);
  gst_object_unref ( data.pipeline );
  return 0;
}

编译指令

gcc -g test.c  `pkg-config --cflags --libs gtk+-3.0`  `pkg-config --cflags --libs  gstreamer-1.0`  `pkg-config --cflags --libs  gstreamer-video-1.0` -o   test

测试三种方案结果如下:

  • (videobox + xvimagesink模式)
    在这里插入图片描述+ (videobox + ximagesink模式)
    在这里插入图片描述+ (videocrop + ximagesink模式)
    在这里插入图片描述最终videobox + xvimagesink模式可以完成所需的功能。

结论

最终的demo 采用videobox + xvimagesink模式。

参考

https://gstreamer.freedesktop.org/documentation/xvimagesink/index.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/ximagesink/index.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/video/gstvideooverlay.html?gi-language=c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/544733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware安装win10虚拟机教程及常见问题

win10虚拟机安装教程 一、前言 1. 准备VMware虚拟机&#xff08;可自行到VMware官网 或 点击链接 下载正版VMware Workstation Pro&#xff09;。 2. 准备需要安装的win10镜像&#xff0c;下载地址&#xff1a;下载 Windows 10。 3. 文章不一定合适每位安装虚拟机的人&#x…

代理模式及对startActivity的Hook应用

定义 简而言之&#xff0c;就是让代理类和目标类&#xff08;被代理类&#xff09;实现同一个接口&#xff0c;并让代理类持有一个目标类的对象&#xff0c;这样对代理类的访问&#xff0c;可以转移到目标类的访问中去。我们可以在不修改目标类的情况下&#xff0c;在代理类中实…

Cesium源码分享--标绘

Cesium标绘插件 在线api文档说明 在线体验地址1&#xff08;三维框架内&#xff09; 在线体验地址2 更多案例地址 免费gis数据 ps&#xff1a;如果可以的话&#xff0c;希望大家能给我个star&#xff0c;好让我有更新下去的动力&#xff1b; 实现原理&#xff1a; 其中实…

RSS订阅无需代码连接飞书自建的方法

RSS订阅用户使用场景&#xff1a; 公司为倡导员工阅读文化以及提高自身素质&#xff0c;通过RSS订阅一些书籍&#xff0c;然后由相关部分负责人每日推送到部门群&#xff0c;便员工们能够随时获取到最新的读书消息。但负责人时常会忘记&#xff0c;且人工手动复制粘贴多项信息比…

使用底层代码(无框架)实现卷积神经网络理解CNN逻辑

首先将数据集放入和底下代码同一目录中&#xff0c;然后导入一些相关函数的文件cnn_utils.py&#xff1a; import math import numpy as np import h5py import matplotlib.pyplot as plt import tensorflow as tf from tensorflow.python.framework import ops def load_data…

Win10搭建Docker Desktop并安装vim

Win10搭建Docker Desktop 1 介绍 Docker Desktop是适用于Windows的Docker桌面&#xff0c;是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序&#xff0c;为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 Wi…

shell脚本基本操作及案例

本文介绍了shell脚本的基本建立过程&#xff0c;并举了4个案例。关键是例3的shell脚本检测局域网ip地址机器是否宕机&#xff0c;例4的shell脚本获取本机ip地址 一、第一个shell脚本 1、定义解释器 #&#xff01;/bin/bash echo "hello world" #! 是一个约定的标记…

今天面了个京东拿28K 出来的,让我见识到了测试界的天花板

今年的春招基本已经结束了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的软件测试面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&#…

【Android】基于Airtest实现大麦网app自动抢票程序

0x01 缘起 疫情结束的2023年5月&#xff0c;大家对出去玩都有点疯狂&#xff0c;歌手们也扎堆开演唱会。但演唱会多&#xff0c;票一点也不好抢&#xff0c;抢五月天的门票难度不亚于买五一的高铁票。所以想尝试找一些脚本来辅助抢票&#xff0c;之前经常用selenium和request做…

探索未来智能交通:网联汽车与汽车互联

网联汽车是指配备多种传感器和通信设备&#xff0c;并且能够接入互联网的汽车。这种汽车可以与外部环境进行交互&#xff0c;并利用各种技术&#xff08;如 GPS 导航、娱乐系统、诊断传感器和通信工具等&#xff09;实现数据的传输和接收。 网联汽车能够与其他车辆、交通基础设…

ChatGPT 引发AI服务器霸屏?AI服务器和普通服务器不同在哪?

​  近阶段&#xff0c;由于 ChatGPT 的横空问世&#xff0c;引发 AI 服务器再一次热潮来袭。随之而来的是&#xff0c;越来越多的企业和机构开始关注 AI 服务器和普通服务器之间的区别和对比。那么AI服务器到底是什么意思&#xff0c;它与普通服务器相比又有哪些差异呢? AI…

MySQL高级——第15章_锁

第15章_锁 1. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在程序开发中会存在多线程同步的问题&#xff0c;当多个线程并发访问某个数据的时候&#xff0c;尤其是针对一-些敏感的数据(比如订单、金额等)&#xff0c;我们就需要保证这个数据在任何 时刻最多只…

动态规划进阶

文章目录 状压dp小国王玉米田炮兵阵地 状压dp 小国王 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter;public class Main{static BufferedReader br new Bu…

Day25力扣刷題

131.分割回文串 此題思維和前幾題不一樣&#xff0c;思維容量更大&#xff0c;主要在於返回的時候&#xff0c;還是會取值。 運行代碼&#xff1a; class Solution:def partition(self, s: str) -> List[List[str]]:result[]tt[]def backtrack(s,index):if len(s)index:res…

node.js PM2部署项目

pm2 是什么 pm2 是一个守护进程管理工具,它能帮你守护和管理你的应用程序。通常一般会在服务上线的时候使用 pm2 进行管理。本文围绕以下重点进行讲解&#xff1a;安装pm2&#xff1b;命令行部署到PM2&#xff1b;PM2查看日志等命令&#xff1b;PM2进行负载均衡&#xff1b;PM…

Python心经(4)

这节记录一些内置模块的使用 目录 hashlib模块&#xff0c;&#xff0c;加密用 json模块 os模块 一个实用的案例&#xff1a; os模块的找文件的操作 随机生成random模块 时间相关模块 time&#xff0c;datetime hashlib模块&#xff0c;&#xff0c;加密用 以md5加密…

什么是Spring Cache?Spring项目如何使用?

前言 目前Spring Cloud微服务在Web项目中占据了主流地位&#xff0c;如果去面试关于Spring Cloud的岗位时&#xff0c;面试官一般都会提问你的项目是如何优化的&#xff0c;从哪些方面入手去优化。而缓存技术绝对是项目必不可少的&#xff0c;所以我们必须掌握好Java项目的缓存…

unity 3种办法实现血条效果并实现3d世界血条一直看向摄像机

普通血条栏: 渐变色血条栏: 缓冲血条栏: 3D场景血条栏跟随玩家移动: 普通血条栏: 在Canvas下创建一个空物体HP bar,在空物体下方创建3个Image,分别为血条框bar 黑色,最大HP maxHP 白色,和当前HP currentHP 红色。(PS:注意先后顺序以调整显示的图层) 效果: …

java中排序

1.传统比较器格式 2.stream 3.结果 4.源码 List<String> list Arrays.asList("201305", "200305", "199009", "200208");Collections.sort(list, new Comparator<String>() {Overridepublic int compare(String o1, Stri…

SpringMVC 万字通关

文章目录 1. 什么是 Spring MVC?1.1 MVC 定义1.2 MVC 和 Spring MVC 的关系 2. Spring MVC 有什么用 ?3. 如何学 Spring MVC ?3.1 Spring MVC 的创建3.2 实现连接功能3.2.1 RquestMapping 详解1. RequestMapping 支持什么请求?2. 请求限定3. GetMapping 和 PostMapping4. c…