nginx和ffmpeg 的安装请参考我的另一篇文章

Nginx+rtmp+ffmpeg搭建视频转码服务_nginx-rtmp-module-master-CSDN博客

目录

1、整体方案设计如图

2、nginx下目录创建和配置文件创建

3、创建视频流生成脚本

4、修改nginx配置

5、管理界面 (video.html)

6、ffmpeg后台启动

方案1:使用nohup和后台运行

6.1启动脚本编写

6.2停止脚本编写

方案2:使用systemd服务(推荐生产环境使用)


1、整体方案设计如图

其中config下是视频配置文件,live存放视频实时流,archive存放视频历史流

2、nginx下目录创建和配置文件创建

cd /usr/local/nginx/html
mkdir streams
cd streams
mkdir config
vi cameras.json在json文件中填充如下内容{"cameras": [{"id": "zl","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/101","name": "走廊监控"},{"id": "blm","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/201","name": "玻璃门监控"},{"id": "cg","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/301","name": "采购监控"},{"id": "yf","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/401","name": "研发监控"},{"id": "qt","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/501","name": "前台监控"}],"hls_time": 2,"max_archive_hours": 24,"live_segments": 2
}

    1:"hls_time": 2,#每个切片时长 2s
    2:"max_archive_hours": 24,#历史数据保留24小时

    3:live_segments 指定播放列表(m3u8文件)中保留的最新TS视频分片数量

    3.1:当设置为5时:

  • 播放列表始终保留最新的5个TS分片

  • 当第6个分片生成时,最旧的分片会被移除

  • 例如:segment_001.ts 到 segment_005.ts → 新分片产生 → segment_002.ts 到 segment_006.ts

    3.2:计算公式

  • 实时流延迟 ≈ live_segments × hls_time

  • 上面示例:5 × 2秒 = 约10秒延迟

3、创建视频流生成脚本

创建start_streams.sh脚本vi start_streams.sh在脚本中填充如下内容#!/bin/bashCONFIG_FILE="/usr/local/nginx/html/streams/config/cameras.json"
STREAMS_DIR="/home/streams"  #流媒体存储目录
#STREAMS_DIR="/usr/local/nginx/html/streams"
NGINX_USER="nginx"                   # Nginx运行用户# 创建目录并设置权限
mkdir -p $STREAMS_DIR/{live,archive}
chown -R $NGINX_USER:$NGINX_USER $STREAMS_DIR
chmod -R 755 $STREAMS_DIR# 读取配置
CAMERAS=$(jq -r '.cameras[] | .id' $CONFIG_FILE)
HLS_TIME=$(jq -r '.hls_time' $CONFIG_FILE)
MAX_HOURS=$(jq -r '.max_archive_hours' $CONFIG_FILE)
LIVE_SEGMENTS=$(jq -r '.live_segments' $CONFIG_FILE)# 为每个摄像头启动FFmpeg进程
for CAMERA in $CAMERAS; doRTSP_URL=$(jq -r --arg id "$CAMERA" '.cameras[] | select(.id==$id) | .rtsp' $CONFIG_FILE)# 创建目录mkdir -p $STREAMS_DIR/live/$CAMERAmkdir -p $STREAMS_DIR/archive/$CAMERA# 启动实时流ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size $LIVE_SEGMENTS \-hls_flags delete_segments \-hls_segment_filename "$STREAMS_DIR/live/$CAMERA/segment_%03d.ts" \"$STREAMS_DIR/live/$CAMERA/live.m3u8" &# 启动历史流ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size 0 \-hls_flags append_list \-hls_segment_filename "$STREAMS_DIR/archive/$CAMERA/%Y%m%d_%H%M%S.ts" \-strftime 1 \"$STREAMS_DIR/archive/$CAMERA/archive.m3u8" &
done# 定时清理旧历史文件
while true; dofor CAMERA in $CAMERAS; dofind "$STREAMS_DIR/archive/$CAMERA" -name "*.ts" -mmin +$(($MAX_HOURS*60)) -deletedonesleep 3600  # 每小时清理一次
done

该脚本启动时如果系统没有安装 jq,启动脚本提示提示 jq: 未找到命令

需要安装一下jq

wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
mv jq-1.6 jq
chmod +x jq          # 添加可执行权限
sudo mv jq /usr/local/bin/  # 移动到系统路径查看jq是否安装成功
jq --version

脚本如果继续执行报错 这行报错parse error: Invalid numeric literal
把json配置文件中的#注释去掉

启动脚本

4、修改nginx配置

nginx全局配置如下


#user  nobody;
worker_processes  1;#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;#pid        logs/nginx.pid;events {worker_connections  1024;
}rtmp {  server {  listen 1935;      #监听的端口号application myapp {     #自定义的名字live on;  }  application hls {  live on;  hls on;  hls_path /tmp/hls;   hls_fragment 1s;hls_playlist_length 3s;  }  } 
}http {include       mime.types;default_type  application/octet-stream;#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;sendfile        on;#tcp_nopush     on;#keepalive_timeout  0;keepalive_timeout  65;#gzip  on;server {listen       80;server_name  localhost;#charset koi8-r;#access_log  logs/host.access.log  main;location / {root   html;index  index.html index.htm;}# 实时流访问location /live {#alias /usr/local/nginx/html/streams/live;#根据自己文视频流生成的位置配置,我这边因为要存历史视频,占用空间大,所以把视频流放到/home/streams 目录下去了alias /home/streams/live;types {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}add_header Cache-Control no-cache;}# 历史流访问location /archive {#alias /usr/local/nginx/html/streams/archive;alias /home/streams/archive;types {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}add_header Cache-Control no-cache;}# 配置API  访问json配置文件location /api/cameras {alias /usr/local/nginx/html/streams/config/cameras.json;default_type application/json;}#error_page  404              /404.html;# redirect server error pages to the static page /50x.html#error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}# proxy the PHP scripts to Apache listening on 127.0.0.1:80##location ~ \.php$ {#    proxy_pass   http://127.0.0.1;#}# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000##location ~ \.php$ {#    root           html;#    fastcgi_pass   127.0.0.1:9000;#    fastcgi_index  index.php;#    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;#    include        fastcgi_params;#}# deny access to .htaccess files, if Apache's document root# concurs with nginx's one##location ~ /\.ht {#    deny  all;#}}# another virtual host using mix of IP-, name-, and port-based configuration##server {#    listen       8000;#    listen       somename:8080;#    server_name  somename  alias  another.alias;#    location / {#        root   html;#        index  index.html index.htm;#    }#}# HTTPS server##server {#    listen       443 ssl;#    server_name  localhost;#    ssl_certificate      cert.pem;#    ssl_certificate_key  cert.key;#    ssl_session_cache    shared:SSL:1m;#    ssl_session_timeout  5m;#    ssl_ciphers  HIGH:!aNULL:!MD5;#    ssl_prefer_server_ciphers  on;#    location / {#        root   html;#        index  index.html index.htm;#    }#}}

配置完成后,启动nginx

5、管理界面 (video.html)

自己通过一个html页面查看视频是否正常播放,或者通过流媒体播放软件查看

video.html页面内容如下

<!DOCTYPE html>
<html>
<head><title>多路视频监控</title><!-- 替换为国内CDN -->
<script src="https://cdn.bootcdn.net/ajax/libs/hls.js/1.1.5/hls.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><style>.video-container {display: inline-block;margin: 10px;vertical-align: top;}.video-title {text-align: center;font-weight: bold;}</style>
</head>
<body><h1>视频监控系统</h1><div id="cameras-container"></div><script>$(document).ready(function() {// 获取摄像头列表$.getJSON("http://192.168.3.35/api/cameras", function(data) {const cameras = data.cameras;cameras.forEach(camera => {// 创建视频容器const container = $(`<div class="video-container"><div class="video-title">${camera.name}</div><div><button onclick="playLive('${camera.id}')">实时</button><input type="datetime-local" id="${camera.id}-start"><input type="datetime-local" id="${camera.id}-end"><button onclick="playArchive('${camera.id}')">回放</button></div><video id="${camera.id}-video" controls width="640" height="360"></video></div>`);$("#cameras-container").append(container);// 默认播放实时视频playLive(camera.id);});});});function playLive(cameraId) {const video = document.getElementById(`${cameraId}-video`);if(Hls.isSupported()) {const hls = new Hls();hls.loadSource(`/live/${cameraId}/live.m3u8`);hls.attachMedia(video);video.play();} else if (video.canPlayType('application/vnd.apple.mpegurl')) {video.src = `/live/${cameraId}/live.m3u8`;video.play();}}function playArchive(cameraId) {const start = document.getElementById(`${cameraId}-start`).value;const end = document.getElementById(`${cameraId}-end`).value;const video = document.getElementById(`${cameraId}-video`);// 简单实现 - 实际项目中应该调用后端API筛选时间范围if(Hls.isSupported()) {const hls = new Hls();hls.loadSource(`/archive/${cameraId}/archive.m3u8`);hls.attachMedia(video);video.play();}}</script>
</body>
</html>

在nginx html下面创建video文件夹,把video.html放进去,启动nginx,访问页面内容如下

6、ffmpeg后台启动

上面的启动脚本不是后台启动,关闭ssh连接后,服务会中断,

方案1:使用nohup和后台运行

6.1启动脚本编写

注意:脚本启动后,不要通过ctrl+c 方式退出启动命令,程序会终止,直接通过叉掉ssh页面即可

#!/bin/bashCONFIG_FILE="/usr/local/nginx/html/streams/config/cameras.json"
STREAMS_DIR="/home/streams"  #流媒体存储目录
#STREAMS_DIR="/usr/local/nginx/html/streams"
NGINX_USER="nginx"                   # Nginx运行用户
PID_FILE="$STREAMS_DIR/process_ids.txt"  # PID记录文件# 清空或创建PID文件
> "$PID_FILE"# 创建目录并设置权限
mkdir -p $STREAMS_DIR/{live,archive,logs}
chown -R $NGINX_USER:$NGINX_USER $STREAMS_DIR
chmod -R 755 $STREAMS_DIR# 读取配置
CAMERAS=$(jq -r '.cameras[] | .id' $CONFIG_FILE)
HLS_TIME=$(jq -r '.hls_time' $CONFIG_FILE)
MAX_HOURS=$(jq -r '.max_archive_hours' $CONFIG_FILE)
LIVE_SEGMENTS=$(jq -r '.live_segments' $CONFIG_FILE)# 为每个摄像头启动FFmpeg(使用nohup)
for CAMERA in $CAMERAS; doRTSP_URL=$(jq -r --arg id "$CAMERA" '.cameras[] | select(.id==$id) | .rtsp' $CONFIG_FILE)# 为每个摄像创建目录mkdir -p $STREAMS_DIR/live/$CAMERAmkdir -p $STREAMS_DIR/archive/$CAMERA# 实时流 - 使用nohup和后台运行nohup ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size $LIVE_SEGMENTS \-hls_flags delete_segments \-hls_segment_filename "$STREAMS_DIR/live/$CAMERA/segment_%03d.ts" \"$STREAMS_DIR/live/$CAMERA/live.m3u8" > "$STREAMS_DIR/logs/$CAMERA-live.log" 2>&1 &echo "$! camera_$CAMERA live" >> "$PID_FILE"  # 记录PID# 历史流 - 使用nohup和后台运行nohup ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size 0 \-hls_flags append_list \-hls_segment_filename "$STREAMS_DIR/archive/$CAMERA/%Y%m%d_%H%M%S.ts" \-strftime 1 \"$STREAMS_DIR/archive/$CAMERA/archive.m3u8" > "$STREAMS_DIR/logs/$CAMERA-archive.log" 2>&1 &echo "$! camera_$CAMERA archive" >> "$PID_FILE"  # 记录PID
done# 定时清理旧历史文件
while true; dofor CAMERA in $CAMERAS; dofind "$STREAMS_DIR/archive/$CAMERA" -name "*.ts" -mmin +$(($MAX_HOURS*60)) -deletedonesleep 3600  # 每小时清理一次
done

脚本执行如果提示 无效的用户: "nginx:nginx"  则创建nginx用户

# 创建nginx用户和组
sudo groupadd nginx
sudo useradd -g nginx -s /sbin/nologin -d /var/nginx -M nginx# 验证用户
id nginx

6.2停止脚本编写

在home目录创建停止脚本 vi stop_streams.sh

#!/bin/bash# 文件用来存储进程ID
PID_FILE="/home/streams/process_ids.txt"
LOG_FILE="/home/streams/stop.log"# 记录日志函数
log() {echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}# 脚本结束前,关闭所有记录的进程
cleanup() {if [ ! -f "$PID_FILE" ]; thenlog "错误:PID文件 $PID_FILE 不存在"return 1filog "开始停止所有FFmpeg进程..."total=0killed=0while read -r line; do# 解析PID和描述信息pid=$(echo "$line" | awk '{print $1}')desc=$(echo "$line" | cut -d' ' -f2-)((total++))# 检查PID是否有效if kill -0 "$pid" 2>/dev/null; thenlog "正在停止进程 $pid ($desc)"if kill "$pid"; then((killed++))log "成功停止进程 $pid"elselog "警告:无法停止进程 $pid"fielselog "进程 $pid 已停止或不存在"fidone < "$PID_FILE"log "操作完成:共找到 $total 个记录,成功停止 $killed 个进程"# 清空PID文件(可选)> "$PID_FILE"
}# 确保脚本退出时调用cleanup函数
trap cleanup EXIT# 主执行
log "====== 开始执行停止脚本 ======"
cleanup
exit 0

方案2:使用systemd服务(推荐生产环境使用)

  1. 创建systemd服务文件 /etc/systemd/system/rtsp_to_hls.service:

[Unit]
Description=RTSP to HLS Stream Service
After=network.target[Service]
Type=forking
User=nginx
WorkingDirectory=/home/streams
ExecStart=/path/to/your/start_streams.sh
Restart=always
RestartSec=10
StandardOutput=append:/home/streams/logs/service.log
StandardError=append:/home/streams/logs/service-error.log[Install]
WantedBy=multi-user.target
  1. 建日志目录并设置权限:

    sudo mkdir -p /home/streams/logs
    sudo chown nginx:nginx /home/streams/logs
  2. 启用并启动服务:

sudo systemctl daemon-reload
sudo systemctl enable rtsp_to_hls.service
sudo systemctl start rtsp_to_hls.service

验证服务是否正常运行

# 检查systemd服务状态
systemctl status rtsp_to_hls.service# 检查进程
pgrep -a ffmpeg# 检查日志
tail -f /home/streams/logs/*.log

停止服务的正确方式

sudo systemctl stop rtsp_to_hls.service

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全国产!瑞芯微 RK3576 ARM 八核 2.2GHz 工业核心板—硬件说明书

前 言 本文为创龙科技 SOM-TL3576 工业核心板硬件说明书,主要提供 SOM-TL3576 工业 核心板的产品功能特点、技术参数、引脚定义等内容,以及为用户提供相关电路设计指导。 为便于阅读,下表对文档出现的部分术语进行解释;对于广泛认同释义的术语,在此不做注释。 硬件参考…

web3 浏览器注入 (如 MetaMask)

以下是关于 浏览器注入方式(如 MetaMask) 的完整详解,包括原理、使用方法、安全注意事项及常见问题解决方案: 1. 核心原理 当用户安装 MetaMask 等以太坊钱包扩展时,钱包会向浏览器的 window 对象注入一个全局变量 window.ethereum,这个对象遵循 EIP-1193 标准,提供与区…

解密提示词工程师:AI 时代的新兴职业

大家好!在人工智能飞速发展的当下&#xff0c;有一个新兴职业正悄然崛起——提示词工程师。他们虽不如数据科学家般广为人知&#xff0c;却在 AI 应用领域发挥着独特且关键的作用。 何为提示词工程师&#xff1f; 提示词工程师专注于设计和优化与 AI 模型进行交互的提示词&…

linux 下 jenkins 构建 uniapp node-sass 报错

背景: jenkins 中构建 uniapp 应用 配置: 1. 将windows HbuilderX 插件目录下的 uniapp-cli 文件夹复制到 服务器 /var/jenkins_home/uniapp-cli 2. jenkins 构建步骤增加 执行 shell ,内容如下 echo ">> 构建中..."# 打包前端 export LANGen_US.UTF-8…

QT常见问题(1)

QT常见问题&#xff08;1&#xff09; 1.问题描述 Qt在编译器中直接运行没有任何问题&#xff0c;但是进入exe生成目录直接双击运行就报错&#xff1a;文件无法定位程序输入点_zn10qarraydata10deallocateepsyy于动态链接库。 2.问题原因 这个错误通常是由于程序运行时找不…

『大模型笔记』第2篇:并发请求中的 Prefill 与 Decode:优化大语言模型性能

『大模型笔记』并发请求中的 Prefill 与 Decode:优化大语言模型性能 文章目录 一. Token 生成的两个阶段:Prefill 和 Decode1.1. 指标分析1.2. 资源利用率分析二. 并发处理机制2.1. 静态批处理 vs 持续批处理(Static Batching vs. Continuous Batching)2.2. Prefill 优先策略…

JVM(7)——详解标记-整理算法

核心思想 标记-整理算法同样分为两个主要阶段&#xff0c;但第二个阶段有所不同&#xff1a; 标记阶段&#xff1a; 与标记-清除算法完全一致。遍历所有可达对象&#xff08;从 GC Roots 开始&#xff09;&#xff0c;标记它们为“存活”。 整理阶段&#xff1a; 不再简单地清…

进程虚拟地址空间

1. 程序地址空间回顾 我们在学习语言层面时&#xff0c;会了解到这样的空间布局图&#xff0c;我们先对他进行分区了解&#xff1a; 如果以静态static修饰的变量就会当成已初始化全局变量来看待&#xff0c;存放在已初始化数据区和未初始化数据区之前。 如果不用static修饰test…

C语言学习day17-----位运算

目录 1.位运算 1.1基础知识 1.1.1定义 1.1.2用途 1.1.3软件控制硬件 1.2运算符 1.2.1与 & 1.2.2或 | 1.2.3非 ~ 1.2.4异或 ^ 1.2.5左移 << 1.2.6右移 >> 1.2.7代码实现 1.2.8置0 1.2.9置1 1.2.10不借助第三方变量&#xff0c;实现两个数的交换…

【linux】简单的shell脚本练习

简单易学 解释性语言&#xff0c;不需要编译即可执行 对于一个合格的系统管理员来说&#xff0c;学习和掌握Shell编程是非常重要的&#xff0c;通过shell程序&#xff0c;可以在很大程度上简化日常的维护工作&#xff0c;使得管理员从简单的重复劳动中解脱出来 用户输入任意两…

机构运动分析系统开发(Python实现)

机构运动分析系统开发(Python实现) 一、引言 机构运动分析是机械工程的核心内容,涉及位置、速度和加速度分析。本系统基于Python开发,实现了平面连杆机构的完整运动学分析,包含数学建模、数值计算和可视化功能。 二、系统架构设计 #mermaid-svg-bT8TPKQ98UU9ERet {font…

工程师生活:清除电热水壶(锅)水垢方法

清除电热水壶&#xff08;锅&#xff09;水垢方法 水垢是水加热时自然形成的钙质沉淀物&#xff0c;常粘附在水壶内壁及发热盘上。它不仅影响水的品质&#xff0c;还会缩短水壶的使用寿命&#xff0c;因此需要定期清除。建议根据各地水质不同&#xff0c;每年除垢 2 至 4 次。…

[分布式并行策略] 数据并行 DP/DDP/FSDP/ZeRO

上篇文章【[论文品鉴] DeepSeek V3 最新论文 之 DeepEP】 介绍了分布式并行策略中的EP&#xff0c;简单的提到了其他几种并行策略&#xff0c;但碍于精力和篇幅限制决定将内容分几期&#xff0c;本期首先介绍DP&#xff0c;但并不是因为DP简单&#xff0c;相反DP的水也很深&…

LeeCode144二叉树的前序遍历

项目场景&#xff1a; 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3] 解释&#xff1a; 示例 2&#xff1a; 输入&#xff1a;root [1,2,3,4,5,null,8,null,null,6,7…

日本生活:日语语言学校-日语作文-沟通无国界(3)-题目:わたしの友達

日本生活&#xff1a;日语语言学校-日语作文-沟通无国界&#xff08;&#xff13;&#xff09;-题目&#xff1a;わたしの友達 1-前言2-作文原稿3-作文日语和译本&#xff08;1&#xff09;日文原文&#xff08;2&#xff09;对应中文&#xff08;3&#xff09;对应英文 4-老师…

使用 rsync 拉取文件(从远程服务器同步到本地)

最近在做服务器迁移&#xff0c;文件好几个T。。。。只能单向访问&#xff0c;服务器。怎么办&#xff01;&#xff01;&#xff01; 之前一直是使用rsync 服务器和服务器之间的双向同步、备份&#xff08;这是推的&#xff09;。现在服务器要迁移&#xff0c;只能单向访问&am…

Linux 并发编程:从线程池到单例模式的深度实践

文章目录 一、普通线程池&#xff1a;高效线程管理的核心方案1. 线程池概念&#xff1a;为什么需要 "线程工厂"&#xff1f;2. 线程池的实现&#xff1a;从 0 到 1 构建基础框架 二、模式封装&#xff1a;跨语言线程库实现1. C 模板化实现&#xff1a;类型安全的泛型…

2013年SEVC SCI2区,自适应变领域搜索算法Adaptive VNS+多目标设施布局,深度解析+性能实测

目录 1.摘要2.自适应局部搜索原理3.自适应变领域搜索算法Adaptive VNS4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流 1.摘要 VNS是一种探索性的局部搜索方法&#xff0c;其基本思想是在局部搜索过程中系统性地更换邻域。传统局部搜索应用于进化算法每一代的解上&…

详细介绍医学影像显示中窗位和窗宽

在医学影像&#xff08;如DICOM格式的CT图像&#xff09;中&#xff0c;**窗宽&#xff08;Window Width, WW&#xff09;和窗位&#xff08;Window Level, WL&#xff09;**是两个核心参数&#xff0c;用于调整图像的显示对比度和亮度&#xff0c;从而优化不同组织的可视化效果…

Unity_VR_如何用键鼠模拟VR输入

文章目录 [TOC] 一、创建项目1.直接创建VR核心模板&#xff08;简单&#xff09;2.创建3D核心模板导入XR包 二、添加XR设备模拟器1.打开包管理器2.添加XR设备模拟器3.将XR设备模拟器拖到场景中4.运行即可用键盘模拟VR输入 一、创建项目 1.直接创建VR核心模板&#xff08;简单&…