Linux(含麒麟操作系统)如何实现多显示器屏幕采集录制

技术背景

在操作系统领域,很多核心技术掌握在国外企业手中。如果过度依赖国外技术,在国际形势变化、贸易摩擦等情况下,可能面临技术封锁和断供风险。开发国产操作系统可以降低这种风险,确保国家关键信息基础设施的稳定运行。在一些敏感行业如国防、金融等,对技术的自主可控要求极高。

音视频信息在很多场合涉及国家安全和敏感内容。如果操作系统的音视频模块依赖国外技术,可能存在安全漏洞被利用的风险,导致国家机密信息泄露。自主开发的音视频模块可以进行更严格的安全管控,增强国家信息安全防护能力。

在这样的背景下,我们实现了Linux平台下的以屏幕采集、摄像头采集、麦克风扬声器采集为数据源的RTMP推送模块、轻量级RTSP服务模块,和RTMP播放器和RTSP播放器模块,并同时覆盖了x86-64架构和aarch64架构。

技术实现

xrandr

本文我们要讨论的是,如何在Linux平台实现多显示器的屏幕采集录制。我们知道,Linux下,X Window Sysem支持多显示器的配置和显示器列表获取。可以使用xrandr查看显示器列表:

xrandr --listactivemonitors” 可在Linux 系统中用于显示当前活动监视器信息的命令。

命令作用

  1. 显示连接状态

    • 该命令可以列出当前连接到系统的所有活动监视器,包括其名称、分辨率、刷新率以及位置信息等。通过查看这些信息,你可以了解到每个监视器的连接状态和基本参数。
    • 例如,如果你连接了多个显示器,这个命令可以帮助你确定哪些显示器是处于活动状态的,以及它们的具体配置。
  2. 帮助配置多显示器

    • 对于使用多显示器的用户来说,这个命令非常有用。它可以让你了解当前的显示器布局,以便更好地进行配置和调整。
    • 你可以根据命令输出的信息,使用其他 xrandr 命令来设置显示器的分辨率、位置、旋转等参数,实现个性化的多显示器设置。

如何使用libXrandr获取显示器列表

先看看Xrandr.h

/*
 * Copyright © 2000 Compaq Computer Corporation, Inc.
 * Copyright © 2002 Hewlett-Packard Company, Inc.
 * Copyright © 2006 Intel Corporation
 * Copyright © 2008 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 *
 * Author:  Jim Gettys, HP Labs, Hewlett-Packard, Inc.
 *	    Keith Packard, Intel Corporation
 */
 
#ifndef _XRANDR_H_
#define _XRANDR_H_
 
#include <X11/extensions/randr.h>
#include <X11/extensions/Xrender.h>
 
#include <X11/Xfuncproto.h>
 
_XFUNCPROTOBEGIN
 
 
typedef struct _XRRMonitorInfo {
    Atom name;
    Bool primary;
    Bool automatic;
    int noutput;
    int x;
    int y;
    int width;
    int height;
    int mwidth;
    int mheight;
    RROutput *outputs;
} XRRMonitorInfo;
 
 
XRRMonitorInfo *
XRRAllocateMonitor(Display *dpy, int noutput);
 
XRRMonitorInfo *
XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors);
 
void
XRRSetMonitor(Display *dpy, Window window, XRRMonitorInfo *monitor);
 
void
XRRDeleteMonitor(Display *dpy, Window window, Atom name);
 
void
XRRFreeMonitors(XRRMonitorInfo *monitors);
 
_XFUNCPROTOEND
 
#endif /* _XRANDR_H_ */

我们Linux平台RTMP推送、轻量级RTSP服务设计的选择接口如下:

/*
 * nt_liunx_smart_publisher_sdk.h
 * Author: daniusdk.com
 * WeChat: xinsheng120
 */
/*
* 获取active XRR-Monitor列表, X RandR 1.5及以上版本支持(可以用:"xrandr --version"查看版本)
* x_display_name:传入实际使用的名称或者NULL,调用XOpenDisplay时使用.
* monitors: 预分配的XRR-Monitor数组
* monitors_array_size: 预分配的XRR-Monitor数组长度
* out_count: 返回实际的XRR-Monitor数量
* 成功返回:NT_ERC_OK, 如果返回值是:NT_ERC_MORE_DATA, 说明预分配的数组长度不够, 请分配更大的数组再尝试.
*/
NT_UINT32 NT_API NT_PB_GetXRRMonitors(NT_PCSTR x_display_name, NT_PB_XRR_MonitorBaseInfo* monitors, NT_INT32 monitors_array_size, NT_INT32* out_count);


/*
* 设置要采集的XRRMonitor id, 采集X屏幕时使用
* xrr_monitor_id: -1:采集所有屏幕, SDK默认为-1.
*/
NT_UINT32 NT_API NT_PB_SetCaptureXRRMonitor(NT_HANDLE handle, NT_INT64 xrr_monitor_id);

 NT_PB_GetXRRMonitors()可以获取active XRR-Monitor列表。

NT_PB_SetCaptureXRRMonitor()设置要采集的XRRMonitor id, 采集X屏幕时使用。

以RTMP推送模块为例,目前我们的功能设计如下:

Linux平台x64_64架构|aarch64架构RTMP直播推送模块

  • 音频编码:AAC/SPEEX;
  • 视频编码:H.264;
  • 推流协议:RTMP;
  • [音视频]支持纯音频/纯视频/音视频推送;
  • 支持X11屏幕采集;
  • 支持部分V4L2摄像头设备采集;
  • [屏幕/V4L2摄像头]支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • [V4L2摄像头]支持V4L2摄像头设备选择(设备文件名范围:[/dev/video0, /dev/video63])、分辨率设置、帧率设置;
  • [V4L2摄像头]支持水平反转、垂直反转、0° 90° 180° 270°旋转;
  • [音频]支持基于alsa-lib接口的音频采集;
  • [音频]支持基于libpulse接口采集本机PulseAudio服务音频;
  • [预览]支持推送端实时预览;
  • [对接服务器]支持自建标准RTMP服务器或CDN;
  • 支持断网自动重连、网络状态回调;
  • 屏幕和摄像头合成/多层合成;
  • 支持窗口采集(一般不建议使用);
  • 支持实时快照;
  • 支持降噪处理、自动增益控制、VAD端点检测;
  • 支持扬声器和麦克风混音;
  • 支持外部编码前音视频数据对接;
  • 支持外部编码后音视频数据对接;
  • 支持实时音量调节;
  • 支持扩展录像模块;
  • 支持Unity接口;
  • 支持H.264扩展SEI发送模块;
  • 支持x64_64架构、aarch64架构(需要glibc-2.21及以上版本的Linux系统, 需要libX11.so.6, 需要GLib–2.0, 需安装 libstdc++.so.6.0.21、GLIBCXX_3.4.21、 CXXABI_1.3.9)。

RTMP推送调用示例

以大牛直播SDK的Linux平台RTMP直播推送模块为例,本Demo实现的是Linux上实现桌面和系统声音采集,然后使用RTMP协议推出去的一个SDK. 集成调用非常简单。

int main(int argc, char *argv[])
{
	signal(SIGINT, &OnSigIntHandler);

	//printf("sizeof(NT_SmartPublisherSDKAPI)=%d\n", sizeof(NT_SmartPublisherSDKAPI));

	LogInit();

	NT_SmartPublisherSDKAPI push_api;
	if (!PushSDKInit(push_api))
	{
		return 0;
	}

	auto push_handle = StartPush(&push_api, "rtmp://192.168.0.154:1935/live/test1", 30);
	if (!push_handle)
	{
		fprintf(stderr, "start push failed.\n");
		push_api.UnInit();
		return 0;
	}

	while (!g_is_exit)
	{
		sleep(2);
	}

	fprintf(stdout, "Skip run loop, is_exit:%d\n", g_is_exit);

	push_api.StopPublisher(push_handle);

	push_api.Close(push_handle);

	push_handle = nullptr;
	
	push_api.UnInit();

	fprintf(stdout, "SDK UnInit..\n");
	
	return 0;
}

相关初始化

	void OnSigIntHandler(int sig)
	{
		if (SIGINT == sig)
		{
			g_is_exit = true;
		}
	}
	
	void LogInit()
	{
		SmartLogAPI log_api;
		memset(&log_api, 0, sizeof(log_api));
		GetSmartLogAPI(&log_api);

		log_api.SetLevel(SL_INFO_LEVEL);
		log_api.SetPath((NT_PVOID)"./");
	}

	bool PushSDKInit(NT_SmartPublisherSDKAPI& push_api)
	{
		memset(&push_api, 0, sizeof(push_api));
		NT_GetSmartPublisherSDKAPI(&push_api);

		auto ret = push_api.Init(0, nullptr);
		if (NT_ERC_OK != ret)
		{
			fprintf(stderr, "push_api.Init failed!\n");
			return false;
		}
		else
		{
			fprintf(stdout, "push_api.Init ok!\n");
		}

		return true;
	}

推送接口封装

	NT_HANDLE StartPush(NT_SmartPublisherSDKAPI* push_api, const std::string& rtmp_url, int dst_fps)
	{
		NT_INT32 pulse_device_number = 0;
		if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(2, &pulse_device_number))
		{
			fprintf(stdout, "Pulse device num:%d\n", pulse_device_number);
			char device_name[512];

			for (auto i = 0; i < pulse_device_number; ++i)
			{
				if (NT_ERC_OK == push_api->GetAuidoInputDeviceName(2, i, device_name, 512))
				{
					fprintf(stdout, "index:%d name:%s\n", i, device_name);
				}
			}
		}

		NT_INT32 alsa_device_number = 0;
		if (pulse_device_number < 1)
		{
			if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(1, &alsa_device_number))
			{
				fprintf(stdout, "Alsa device num:%d\n", alsa_device_number);
				char device_name[512];
				for (auto i = 0; i < alsa_device_number; ++i)
				{
					if (NT_ERC_OK == push_api->GetAuidoInputDeviceName(1, i, device_name, 512))
					{
						fprintf(stdout, "index:%d name:%s\n", i, device_name);
					}
				}
			}
		}

		NT_INT32 capture_speaker_flag = 0;
		if ( NT_ERC_OK == push_api->IsCanCaptureSpeaker(2, &capture_speaker_flag) )
		{
			if (capture_speaker_flag)
				fprintf(stdout, "Support speaker capture\n");
			else
				fprintf(stdout, "UnSupport speaker capture\n");
		}

		NT_INT32 is_support_window_capture = 0;
		if (NT_ERC_OK == push_api->IsCaptureWindowSupported(NULL, &is_support_window_capture))
		{
			if (is_support_window_capture)
				fprintf(stdout, "Support window capture\n");
			else
				fprintf(stdout, "UnSupport window capture\n");
		}

		NT_HANDLE push_handle = nullptr;

		// if (NT_ERC_OK != push_api->Open(&push_handle, NT_PB_E_VIDEO_OPTION_LAYER, NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER, 0, NULL))
		if (NT_ERC_OK != push_api->Open(&push_handle, NT_PB_E_VIDEO_OPTION_SCREEN, NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER, 0, NULL))
		{
			return nullptr;
		}

		//push_api->SetXDisplayName(push_handle, ":0");
		//push_api->SetXDisplayName(push_handle, NULL);
		
		// 视频层配置方式

		//std::vector<std::shared_ptr<nt_pb_sdk::layer_conf_wrapper_base> > layer_confs;

		//auto index = 0;

		 第0层填充RGBA矩形, 目的是保证帧率, 颜色就填充全黑
		//auto rgba_layer_c0 = std::make_shared<nt_pb_sdk::RGBARectangleLayerConfigWrapper>(index++, true, 0, 0, 1280, 720);

		//rgba_layer_c0->conf_.red_ = 0;
		//rgba_layer_c0->conf_.green_ = 0;
		//rgba_layer_c0->conf_.blue_ = 0;
		//rgba_layer_c0->conf_.alpha_ = 255;

		//layer_confs.push_back(rgba_layer_c0);

		 第一层为桌面层
		//auto screen_layer_c1 = std::make_shared<nt_pb_sdk::ScreenLayerConfigWrapper>(index++, true, 0, 0, 1280, 720);
		//
		//screen_layer_c1->conf_.scale_filter_mode_ = 3;

		//layer_confs.push_back(screen_layer_c1);

		//std::vector<const NT_PB_LayerBaseConfig* > layer_base_confs;

		//for (const auto& i : layer_confs)
		//{
		//	layer_base_confs.push_back(i->getBase());
		//}

		//if (NT_ERC_OK != push_api->SetLayersConfig(push_handle, 0, layer_base_confs.data(),
		//	layer_base_confs.size(), 0, nullptr))
		//{
		//	push_api->Close(push_handle);
		//	push_handle = nullptr;
		//	return nullptr;
		//}

		// push_api->SetScreenClip(push_handle, 0, 0, 1280, 720);

		push_api->SetFrameRate(push_handle, dst_fps); // 帧率设置
			
		push_api->SetVideoBitRate(push_handle, 2000);  // 平均码率2000kbps
		push_api->SetVideoQualityV2(push_handle, 26); 
		push_api->SetVideoMaxBitRate(push_handle, 4000); // 最大码率4000kbps
		push_api->SetVideoKeyFrameInterval(push_handle, dst_fps*2); // 关键帧间隔
		push_api->SetVideoEncoderProfile(push_handle, 3); // h264 baseline
		push_api->SetVideoEncoderSpeed(push_handle, 3); // 编码速度设置到3

		if (pulse_device_number > 0)
		{
			push_api->SetAudioInputLayer(push_handle, 2);
			push_api->SetAuidoInputDeviceId(push_handle, 0);
		}
		else if (alsa_device_number > 0)
		{
			push_api->SetAudioInputLayer(push_handle, 1);
			push_api->SetAuidoInputDeviceId(push_handle, 0);
		}

		// 音频配置
		push_api->SetPublisherAudioCodecType(push_handle, 1);
		//push_api->SetMute(push_handle, 1);

		if ( NT_ERC_OK != push_api->SetURL(push_handle, rtmp_url.c_str(), NULL) )
		{
			push_api->Close(push_handle);
			push_handle = nullptr;
			return nullptr;
		}

		if ( NT_ERC_OK != push_api->StartPublisher(push_handle, NULL) )
		{
			push_api->Close(push_handle);
			push_handle = nullptr;
			return nullptr;
		}

		return push_handle;
	}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java文件上传同时传入JSON参数

前言 此篇文章用于解决一个接口内同时完成文件的上传及JSON参数的传入(生产环境已验证); 1.准备接口 import cn.cdjs.vo.UserVO; import cn.hutool.json.JSONUtil; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFi…

虚拟社交的新时代:探索Facebook的元宇宙愿景

随着技术的不断进步&#xff0c;社交媒体的形态也在悄然变化。Facebook&#xff08;现名Meta&#xff09;正站在这一变革的前沿&#xff0c;积极探索元宇宙的愿景。元宇宙不仅是虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;的结合&#xff0c;更是…

Qt_对话框QDialog的介绍

目录 1、新建项目对话框 2、非模态对话框 3、模态对话框 4、自定义对话框 5、Qt内置对话框 5.1 消息对话框QMessageBox 5.2 颜色对话框QColorDialog 5.3 文件对话框QFileDialog 5.4 字体对话框QFontDialog 5.5 输入对话框QInputDialog 结语 前言: 在Qt中&…

【数据结构】排序算法---桶排序

文章目录 1. 定义2. 算法步骤3. 演示3.1 动态演示13.2 动态演示23.3 图片演示13.4 图片演示2 4. 性质5. 算法分析6. 代码实现C语言PythonJavaCGo 结语 1. 定义 桶排序&#xff08;英文&#xff1a;Bucket sort&#xff09;是计数排序的升级版&#xff0c;适用于待排序数据值域…

C# 利用simd比较两个文件是否相等(高性能)

主要用到两个指令集&#xff0c;CompareEqual指令与MoveMask指令&#xff0c;因为电脑cpu原因&#xff0c;我们采用Avx2。 Avx2.CompareEqual&#xff0c;比较两个Vector256<byte>向量&#xff0c;如果元素相同返回255&#xff0c;否则返回0。 Avx2.MoveMask如果Vector…

【初阶数据结构】详解二叉树 - 树和二叉树(三)(递归的魅力时刻)

文章目录 前言1. 二叉树链式结构的意义2. 手搓一棵二叉树3. 二叉树的遍历&#xff08;重要&#xff09;3.1 遍历的规则3.2 先序遍历3.3 中序遍历3.4 后序遍历3.5 遍历的代码实现3.5.1 先序遍历代码实现3.5.2 中序遍历代码实现3.5.3 后序遍历代码实现 4. 统计二叉树结点的个数5.…

2024年9月26日--- Spring-AOP

SpringAOP 在学习编程过程中&#xff0c;我们对于公共方法的处理应该是这样的一个过程&#xff0c;初期阶段如下 f1(){Date now new Date();System.out.println("功能执行之前的各种前置工作"now)//...功能代码//...功能代码System.out.println("功能执行之前…

Fyne ( go跨平台GUI )中文文档-入门(一)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI )…

SpringSecurity-用户认证

1、用户认证 1.1 用户认证核心组件 我们系统中会有许多用户&#xff0c;确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念&#xff1a;当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的&#xff0c;这个不难理…

【若依RuoYi-Vue | 项目实战】帝可得后台管理系统(二)

文章目录 一、人员管理1、需求说明2、生成基础代码&#xff08;1&#xff09;创建目录菜单&#xff08;2&#xff09;添加数据字典&#xff08;3&#xff09;配置代码生成信息&#xff08;4&#xff09;下载代码并导入项目 3、人员列表改造&#xff08;1&#xff09;基础页面&a…

Latex和Vscode安装和配置

一、Latex安装教程 打开清华大学开源软件镜像站&#xff0c;下载texlive.iso文件 右键点击ios文件&#xff0c;点击装载 配置latex安装 4. 安装过程 二、VScode安装和配置教程 打开Vscode官网&#xff0c;下载安装包 2.右键&#xff0c;以管理员身份运行VSCode安装包&#…

基于深度学习的药品三期OCR字符识别

在药品生产线上,药品三期的喷码与条形码识别是保证药品追溯和安全管理的重要环节。传统的识别方法依赖于人工操作,不仅效率低下且容易出错。随着深度学习技术的不断发展,基于OCR(Optical Character Recognition,光学字符识别)的自动化识别系统逐渐成为主流。本文将以哪吒…

微服务注册中⼼1

1. 微服务的注册中⼼ 注册中⼼可以说是微服务架构中的”通讯录“ &#xff0c;它记录了服务和服务地址的映射关系。在分布式架构中&#xff0c; 服务会注册到这⾥&#xff0c;当服务需要调⽤其它服务时&#xff0c;就这⾥找到服务的地址&#xff0c;进⾏调⽤。 1.1 注册中⼼的…

linux信号 | 学习信号三步走 | 全解析信号的产生方式

前言&#xff1a;本节内容是信号&#xff0c; 主要讲解的是信号的产生。信号的产生是我们学习信号的第二个阶段。 我们已经学习过第一个阶段——信号的概念与预备知识&#xff08;没有学过的友友可以查看我的前一篇文章&#xff09;。 以及我们还没有学习信号的第三个阶段——信…

CleanMyMac X 评价、介绍、使用教学|Mac系统最推荐的系统优化和清理软件工具!

本篇文章要带大家看一款知名的Mac系统优化、清理工具– CleanMyMac X &#xff0c;并且也会附上详细的介绍和使用教学。 链接: https://pan.baidu.com/s/1_TFnrIVH1NGsZPsA3lpwAA 提取码: dpjw CleanMyMac X-安装包&#xff1a;https://souurl.cn/QUYb57 为什么Mac电脑需要装系…

Cocos 3.8.3 实现外描边效果(逃课玩法)

本来想着用Cocos 的Shader Graph照搬Unity的思路来加外描边&#xff0c;发现不行&#xff0c;然后我就想弄两个物体不就行了吗&#xff0c;一个是放大的版本&#xff0c;再放大的版本上加一个材质&#xff0c;这个材质面剔除选择前面的面剔除就行了&#xff0c;果不其然还真行。…

前端开发必备:实用Tool封装工具类方法大全

程序员必备宝典网站https://tmxkj.top/#/ 1.判断空值 /*** 判断是否是空值*/ export function isEmpty(value) {if (typeof value string) {return value.length 0 || value "";} else if (typeof value number) {return value 0;} else if (Array.isArray(va…

Kafka 面试题

参考&#xff1a; https://javabetter.cn/interview/kafka-40.htmlhttps://javaguide.cn/high-performance/message-queue/kafka-questions-01.html Kafka 架构 名词概念 Producer&#xff08;生产者&#xff09; : 产生消息的一方。 Consumer&#xff08;消费者&#xff09; …

MySQL---创建数据库(基于SQLyog)

目录 0.前言 1.基本认识 1.1编码集 1.2检验规则 2.库的创建和销毁 2.1指令介绍 2.2你可能会出现的问题 3.查看数据库属性 4.创建指定数据库 5.创建表操作 0.前言 之前写过一篇这个关于表的创建和销毁的操作&#xff0c;但是当时是第一次学习&#xff0c;肯定有些地方…

failed to load steamui.dll的多种处理方法,steamui.dll的作用

在使用Steam平台时&#xff0c;不少玩家可能会遇到“failed to load steamui.dll”这样令人头疼的错误提示。这个错误会阻碍Steam客户端的正常运行&#xff0c;影响我们享受游戏和Steam平台的各种服务。不过&#xff0c;不必过于担心&#xff0c;因为有多种方法可以尝试解决这个…