HLS + ffmpeg 实现动态码流视频服务

news2024/11/25 20:41:09

一、简介

如下图,包含三部分,右边一列为边缘节点;中间一列代表数据中心;左边一列是项目为客户提供的一系列web管理工具:

具体来说在我们项目中有一堆边缘节点,每个节点上部署一台强大的GPU服务器及N个网络摄像头,服务器持续记录摄像头的高清码流,同时跑模型持续分析视频内容;边缘服务器与数据中心之间有一条网络链路,但带宽非常小,通常只作数据、控制命令下发用;数据中心部署了一系列web服务,为不同用户提供审阅系统运行情况及发布操作边缘节点命令的端口。

那么问题来了,终端用户通常并不关心摄像头录制到的视频,但偶尔需要抽检部分视频文件确定系统正在正常运行,出问题的时候算法团队需要导出原始高清视频作进一步分析,怎么破?提炼一下关键条件:

  1. 数量众多的边缘节点,每天生成海量视频

  2. 边缘节点到数据中心带宽有限

  3. 需要提供不同清晰度的视频满足不同场景需要

最直观的方案是,将节点上的视频不断推送到数据中心,客户端直接访问存储在数据中心的视频数据,但这明显不符合场景要求,因为边缘节点到数据中心的带宽非常小,没办法支持高清视频文件的持续传输,而且抽检频率很低,全传回来了大多数也是用不上的。

第二种方案可以选择按需调度,即由客户明确发出抽调命令,指定时间范围、边缘节点列表、摄像头列表、清晰度,数据中心按需同步。这种方案有两个问题,一是延迟大,命令从客户端发出后,得等数据中心到边缘节点捞完数据,才能开始推送视频,开始响应;二是需要实现一套调度系统,实现一堆提高可用性的逻辑,比如监控带宽防止打满、实现断点续输、异步任务生命周期管理等。

第三种,也就是本文阐述的技术方案:使用 ffmpeg 动态调整视频码率、分辨率;使用 HLS 分段传输视频内容。

如果读者想到更多可能性,还请联系作者,我们可以讨论讨论。

二、核心技术

2.1 ffmpeg 简介

ffmpeg 是一个非常有名的高性能音视频处理工具,它可以轻松实现视频转码、分割、码率调整、分辨率调整、元数据解析、帧包解析等等,能满足大多数视频处理场景。网上已经有很多相关的讨论文章,本文就不赘述了。

提示:

贴几个有用的入门链接:

  1. ffmpeg 下载安装

  2. ffmpeg 镜像,同时支持CPU、GPU版本

  3. 阮一峰的 《ffmpeg 视频处理入门教程》

  4. 使用GPU硬件加速ffmpeg视频转码

2.2 HLS 协议简介

HLS 全称 Http Live Stream,是苹果推出的基于http的流媒体传输协议,原理是将一个大的,完整的视频文件拆解成多个小文件,每次只播放其中的一个小文件。选用HLS主要有如下的考量:

  1. 协议底层使用http传输内容,这在大B环境下是个极大的利好,因为大多数企业内网防火墙有很严格的管控,唯独对80端口开放性较高

  2. 支持按需加载,也就是说播到那就加载那一块小文件,播多少下载多少,不用download整个视频

  3. 天然支持定点播放(seeking)

当时在做技术选型时也考虑过DASH,虽然DASH更有协议上的先进性,但当下覆盖度低,兼容性没HLS好,贸然选用怕是得给后面埋坑啊。

三、 代码实现

核心代码其实特别简单,只需实现两个接口:

  1. 获取索引文件接口,原理上使用 ffprobe 分析视频 I 帧分布时间点,生成 m3u8 文件

  2. 获取分片视频,原理上使用ffmpeg 分割、转码,再通过http输出切割后的视频文件

示例:

import Router from 'koa-router';
import { exec, spawn } from 'child_process';
import config from 'config';
import path from 'path';
​
const route = new Router();
const execPromise = (...arg) =>
  new Promise((r, j) => {
    exec(...arg, (err, stdout) => {
      if (err) {
        j(err);
      } else {
        r(stdout);
      }
    });
  });
​
// 视频 m3u8 索引文件接口
route.get('/videos/:videoFile', async (ctx) => {
  const { videoFile } = ctx.params;
  const videoFilePath = path.join(config.get('VIDEO_ROOT_DIR'), videoFile);
  // 调用 ffprobe 分析视频 I 帧分布
  const cmdReadKeyframe = `ffprobe -v error -skip_frame nokey -select_streams v:0 -show_entries frame=pkt_pts_time -of csv=print_section=0 ${videoFilePath}`;
  const keyframes = (await execPromise(cmdReadKeyframe))
    .split('\n')
    .filter((d) => /^[\d\.]+$/.test(d))
    .map((d) => +d);
​
  // 调用 ffprobe 解析视频原信息
  const cmdReadMeta = `ffprobe -v quiet -print_format json -show_format -show_streams ${videoFilePath}`;
  const res = JSON.parse(await execPromise(cmdReadMeta));
  const {
    format: { duration }
  } = res;
​
  // 根据关键帧的时间分布和视频时长,生成 m3u8 文件
  const fragments = keyframes.map((k, i) => ({
    duration: i === keyframes.length - 1 ? duration - k : keyframes[i + 1] - k,
    start: k
  }));
  ctx.body =
        '#EXTM3U\n' +
      '#EXT-X-PLAYLIST-TYPE:VOD\n' +
      `#EXT-X-TARGETDURATION:${Math.max(...fragments.map((f) => f.duration))}\n` +
        `${fragments
        .map(
          (f) =>
            `#EXTINF:${f.duration},\n/hls_video/${videoFile}?start=${f.start}&duration=${f.duration}`
        )
        .join('\n')}\n` +
            '#EXT-X-ENDLIST';
  ctx.type = 'application/x-mpegURL';
});
​
// 读取视频片段
route.get('/hls_video/:videoFile', (ctx) => {
  const {
    params: { videoFile },
    query: { start, duration }
  } = ctx;
  const videoFilePath = path.join(config.get('VIDEO_ROOT_DIR'), videoFile);
  // ffmpeg 截取视频片段
  const cmd = `-ss ${start} -i ${videoFilePath} -t ${duration} -vcodec copy -acodec copy -b:v 200k -f hls -bsf h264_mp4toannexb -output_ts_offset ${start} -`;
  const { stdout } = spawn('ffmpeg', cmd.split(' '));
​
  ctx.type = 'video/MP2T';
  // 流式输出转码字节序
  ctx.body = stdout;
});
​
export default route;

下面讲解关键步骤。

文末名片免费领取音视频开发学习资料,内容包括(C/C++,Linux 服务器开发,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

3.1 根据 I 帧分割视频

HLS 的原理是将一个大的视频拆解为多个小视频按需传输,拆解策略的好坏直接影响视频还原度、转码效率、响应时间、缓存命中率等。可选的分割方案有:

  1. 根据视频duration,均匀切割,好处是简单直观,但要么转换速度很慢导致响应时间高,要么还原度低,切出来的视频可能存在好几秒偏差(参考 《FFmpeg 视频分割和合并》)

  2. 根据视频 I 帧所在时间点进行切割,好处是还原度高,不容易掉帧;缺点是需要先用 ffprobe 遍历视频中所有关键帧的位置,可能存在性能问题。

提示:

简单科普一下,经过压缩编码的视频主要包含了连续n个帧,其中有一种叫做 I 帧(Intra-coded picture),特点是压缩率低但是内容完整,不需要其他帧辅助推算;除了I帧外常见的还有 P‑帧 (Predicted picture) —— 向前搜索帧,B‑frame (Bidirectional predicted picture) 双向搜索帧,它们的压缩率高,但是需要依靠附近的I帧内容推算图像。

通常 ffmpeg 截取视频时大致上不是按 -ss-t 指定的起始结束时间精确的裁剪,而是找到开始、结束时间点上最近的关键帧,截取两帧之间的数据,这会导致实际结果跟预期结果可能有好几秒的偏差。当然,为了精确裁剪,有一个变通的方法是将视频所有帧都先转成I帧再截取,但成本高,耗时大,无法满足实时响应需求。

所以本文才会选择根据I帧的时间点分割视频,虽然在遍历I帧时会有一些性能损失,但精确度跟总体响应速度都能满足需求。

分析I帧时间点的命令如下:

ffprobe -v error -skip_frame nokey -select_streams v:0 -show_entries frame=pkt_pts_time -of csv=print_section=0 ${videoFile}

关键参数:

  1. skip_frame: 指定跳过那些帧,这里的值是 nokey 也就忽略了除I帧外的情况

  2. select_streams: 选择下标为0的视频流

  3. frame: 输出帧包的 pkt_pts_time 字段

执行命令,会得到类似下面的输出:

ffprobe -v error -skip_frame nokey -select_streams v:0 -show_entries frame=pkt_pts_time -of csv=print_section=0 xxx.mp4
0.000000
10.000000
20.000000
30.000000
40.000000
50.000000
60.000000
70.000000
80.000000
90.000000
100.000000
110.000000
120.000000
130.000000
140.000000

每一行即为关键帧在视频出现的时间点。

提示:

在生产环境千万要注意,有时候拿到的视频 I 帧间隔极短,比如笔者就遇到过用 hiki SDK 从网络摄像头或NVR设备取回的视频,平均 0.1s 一个关键帧,所以分割时如果帧间间隔很短,应该考虑将多个关键帧时间点合并成一个切片输出。

这一步需要调用 child_processexec 函数运行 ffprobe 命令,并将结果格式化成 number 类型方便后续处理,核心代码:

const cmdReadKeyframe = `ffprobe -v error -skip_frame nokey -select_streams v:0 -show_entries frame=pkt_pts_time -of csv=print_section=0 ${videoFilePath}`;
const keyframes = (await execPromise(cmdReadKeyframe))
  .split('\n')
  .filter((d) => /^[\d\.]+$/.test(d))
  .map((d) => +d);

3.2 生成M3U8索引文件

M3U8 文件最主要的作用是提供了HLS视频分片的索引信息,播放器在处理HLS视频时首先访问这个索引文件,找出所有切片视频后再逐一按序播放。M3U8 常见格式如:

#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXTINF:10,
/hls_video/20200525221257_20200525222012.mp4?start=0&duration=10
#EXTINF:10,
/hls_video/20200525221257_20200525222012.mp4?start=10&duration=10
#EXTINF:10,
#EXTINF:5.319999999999993,
/hls_video/20200525221257_20200525222012.mp4?start=430&duration=5.319999999999993
#EXT-X-ENDLIST

简单介绍一下关键字段的含义:

  1. EXT-X-PLAYLIST-TYPE : 播放列表的类型,支持“EVENT”表示直播流,此时服务端可以不断往这个文件追加内容;“VOD”表示静态视频流 ,此时M3U8文件不会发生变更。在我们的场景中,选择使用“VOD”

  2. EXT-X-TARGETDURATION : 最大切片时长

  3. EXTINF :视频切片描述字段,需要指明切片地址、时长

  4. EXT-X-ENDLIST : 视频结束标志符,仅在 EXT-X-PLAYLIST-TYPE=VOID 时有效。

这一步需要根据上一步得到的关键帧时间点列表拼接出一个完整的 m3u8 文件,核心代码:

// 计算分片大小
// 实际应用中注意判断,当时长小于某个阈值时应该合并多个节点
const fragments = keyframes.map((k, i) => ({
  duration: i === keyframes.length - 1 ? duration - k : keyframes[i + 1] - k,
  start: k
}));
ctx.body =
  // m3u8 头部信息
  '#EXTM3U\n' +
  '#EXT-X-PLAYLIST-TYPE:VOD\n' +
  `#EXT-X-TARGETDURATION:${Math.max(...fragments.map((f) => f.duration))}\n` +
  // 遍历分片,生成分片描述
  `${fragments
  .map(
  (f) =>
    // 这里简单起见,直接用参数记录分片开始、结束时间
    `#EXTINF:${f.duration},\n/hls_video/${videoFile}?start=${f.start}&duration=${f.duration}`
  )
  .join('\n')}\n` +
  // 结束标志符
  '#EXT-X-ENDLIST';
ctx.type = 'application/x-mpegURL';

3.3 生成视频片段

最后,需要根据分片的开始结束时间裁剪视频,毫无疑问这里还是用的 ffempg ,关键命令:

ffmpeg -ss 10 -i xxx.mp4 -t 10 -vcodec copy -acodec copy -b:v 200k -f hls -bsf h264_mp4toannexb -output_ts_offset 10 -

命令参数:

  1. -ss :指定开始时间

  2. -t :指定裁剪时长

  3. -vcodec copy -acodec copy : 指定音视频编码规则,可根据实际场景调整,注意转码时间通常较长,建议尽量复制原始音视频流

  4. -f hls : 指定视频封装格式为 hls

  5. -bsf h264_mp4toannexb :指定字节流过滤器,用于将原始 mp4 封装的字节数据转换为适合 hls 格式的字节数据

  6. -output_ts_offset 10 : 这个很重要,指定分片开始时间,这个值必须严格按照分片在原视频所处的时间进行设定,否则播放器无法正常播放

提示:

整行命令的意思就是从 ss 开始截取 -t 长度的片段,音视频流直接copy,转封装为 hls 格式并对每个字节流使用 h264_mp4toannexb 过滤器,完事了再设定输出视频的开始时间。

对应的核心代码:

const cmd = `-ss ${start} -i ${videoFilePath} -t ${duration} -vcodec copy -acodec copy -b:v 200k -f hls -bsf h264_mp4toannexb -output_ts_offset ${start} -`;
const { stdout } = spawn('ffmpeg', cmd.split(' '));
​
ctx.type = 'video/MP2T';
ctx.body = stdout;

提示:

这里还有一个小知识点,命令最后指定的输出是 - ,ffmpeg 遇到这种输出指令时会边转码边将字节流输出到后面的管道上,对应上例中将 spawn 命令的 stdout 流直接赋值给 ctx.body ,从而尽快向客户端发出响应。

四、性能优化

视频处理过程非常耗费时间,在本文介绍的这种实时处理场景更需要特别关注性能,可以从尽量提升单个视频处理速度尽量减少视频处理 两个方面入手,下面罗列一些有效的优化手段:

4.1 使用GPU处理视频

ffmpeg 默认情况下会用CPU执行命令,CPU运行的缺点一是会大量占用CPU资源,降低系统效率;二是CPU更擅长执行逻辑运算,执行这种视频处理的速度远远比不上GPU。要用GPU跑ffmpeg 命令,必要条件:

  1. 系统带有支持对应视频编码格式的硬件加速功能的显卡

  2. 装好显卡驱动

  3. 编译GPU版本的ffmpeg

提示:

环境配置太繁琐了,建议有兴趣的读者在带GPU硬件的机器上跑 ffmpeg 镜像 试试。

满足上述条件后,只需在原本CPU版本的命令基础上增加一个参数,例如使用NVIDIA显卡加速时 -hwaccel nvdec ,上述命令对应GPU版本:

ffmpeg -hwaccel nvdec -ss 10 -i xxx.mp4 -t 1000 -vcodec copy -acodec copy -b:v 200k -f hls -bsf h264_mp4toannexb -output_ts_offset 10 -

尝试跑了几个视频,性能对比:

CPU 占用率平均耗时
CPU250% +2m41s
GPU (基于T4)75% - 90%55s

提示:

测试样本非常小,所以数据并不具有普适性,此处只是为了表达,GPU真的很快。

4.2 缓存

第二个优化策略是使用缓存,减少直接处理视频。

对服务端而言,可以将裁剪后的视频持久化为硬盘文件,下次访问相同参数时直接输出文件。如果视频内容有可能发生变化,建议每次匹配时校验视频MD5值。

对浏览器端而言则可以启用强缓存,推荐的方案是视频名称以MD5命名,然后通过 max-age 头设置很长的缓存过期时间。

提示:

注意尽量避免使用 must-revalidate ,因为每次到服务端验证,服务端还是得执行视频编解码操作,频繁验证性能反而会降低。

4.3 其他优化策略

还存在很多优化策略,可以根据实际情况启用,例如:

  • 使用CDN。CDN能够实现一个节点服务一群用户,那么理论上用户A访问某个视频后,同区内用户B/C/D 访问相同视频就可以直接取CDN节点上的副本,避免到源节点访问。

  • 使用H265编码传输。同一个视频,H265编码能比H264节省约1/3的空间,所以H265的网络性能相比之下会好很多。不过问题在于浏览器默认普遍不支持播放H265视频,所以这个方案对客户端要求较高,不具有普适性。

  • 避免视频转码。转码过程需要对视频每一帧执行解码、转码,性能消耗极大,相比视频裁剪、修改码率、修改分辨率、转封装等操作算是小弟了,因此建议在保存视频的时候就直接保存成最后用户使用的编码格式,避免实时转码。

作者:范文杰 链接:https://juejin.cn/post/6844904180914601991

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

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

相关文章

猴子也能学会的jQuery第十二期——jQuery遍历(下)

📚系列文章—目录🔥 猴子也能学会的jQuery第一期——什么是jQuery 猴子也能学会的jQuery第二期——引用jQuery 猴子也能学会的jQuery第三期——使用jQuery 猴子也能学会的jQuery第四期——jQuery选择器大全 猴子也能学会的jQuery第五期——jQuery样式操作…

0201导数的概念-导数与微分-高等数学

文章目录1 导数的定义2 常见函数的导数(导函数)3 单侧导数4 导数的几何意义5 可导和连续的关系6 后记1 导数的定义 设函数yf(x)yf(x)yf(x)在点x0x_0x0​的某个邻域内有定义,当自变量x在x0取得增量△xx在x_0取得增量\triangle xx在x0​取得增量△x(点x△xx\triangle …

品优购项目案例制作需要注意的内容笔记

个人在做的时候遇到的,自己觉得需要注意的内容 模块化 1.有些样式和结构在很多页面会出现,比如页面的头部和底部,大部分页面都有。此时可以把这些结构和样式单独作为一个模块,然后重复使用 2.这里最典型的应用就是common.css公…

虚拟内存系统【多级页表】

多级页表🏝️1. 考虑使用更大的页🏖️2. 使用段页式管理📖2.1 为什么采用段页式管理?📖2.2 段页式管理的缺点🏞️3. 多级页表📖3.1 多级页表的优点📖3.2 多级页表的缺点&#x1f4d6…

文本匹配实战:基于Glove+RNN实现文本匹配 详细教程

任务描述: 文本匹配是自然语言处理中一个非常核心的任务,主要目的是研究两段文本之间的关系。许多自然语言处理任务在很大程度上都可以抽象成文本匹配问题,比如信息检索可以归结为搜索词和文档资源的匹配,问答系统可以归结为问题和候选答案的匹配,复述问题可以归结为两个同…

数商云SRM系统招标流程分享,助力建筑材料企业降低采购成本,提高采购效率

近年来,随着主管部门对房地产市场的监管非常严格,房地产业的发展已进入瓶颈期,这对与房地产业密切相关的建材行业产生了很大的影响。同时,我国城市化进入成熟期,行业规模发展动力减弱,建材行业增长压力明显…

谷粒商城1.项目简介和项目环境预搭建(项目概述和环境搭建代码)

一.商城项目总体架构 从讲课篇看 从分块来看 项目知识概述 二.环境搭建代码 1.项目架构 建立父工程 pom文件 <description>聚合服务</description><packaging>pom</packaging><modules><module>gulimall-coupon</module><mo…

H2数据库端口占用

因为服务已经起来了&#xff0c;然后自己再想测试的时候&#xff0c;发现端口已经占用&#xff0c;找了好久在官网文档找到了对应的解决方案 意思是在服务端上&#xff08;就是我们的配置文件application.yml&#xff09;我们得加上tcp://localhost/也就是你的主机地址tcp://12…

Lambert (兰伯特)光照模型

漫反射的定义 漫反射是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时&#xff0c;表面会把光线向着四面八方反射&#xff0c;所以入射线虽然互相平行&#xff0c;由于各点的法线方向不一致&#xff0c;造成反射光线向不同的方向无规则地反…

小程序赋能生鲜食品进销存,线上+物流系统两手抓

互联网、物联网和消费升级的多重影响下&#xff0c;生鲜食品市场的流通更加便捷。在国内外的生鲜产品的可用性不再受季节和地区的限制&#xff0c;需求也逐渐增加。 那么随着生鲜食品商城小程序和网上商城购物系统平台的数量逐渐增加&#xff0c;如何体现其价值在企业进销存系统…

基于EasyExcel锁定指定列导出数据到excel

基于EasyExcel锁定指定列导出数据到excel 大家好&#xff0c;我是llp。最近在做系统报表时中有一个需求时这样的&#xff0c;需要查询系统数据导出excel&#xff0c;并要求导出的excel列中有一些时锁定的有一些时不锁定的&#xff0c;即使实现动态列锁定的效果。看上去应该是一…

AWS 中文入门开发教学 39- AWS CLI - AWS认证 必须会的命令行工具

AWS CLI是什么 AWS Command Line Interface (AWS CLI) 是一种开源工具&#xff0c; 让您能够在命令行 Shell 中使用命令与 AWS 服务进行交互。 仅需最少的配置&#xff0c;即可使用 AWS CLI 开始运行命令&#xff0c;以便从终端 程序中的命令提示符实现与基于浏览器的 AWS 管理…

mysql基础知识篇(四)

1.MySQL 索引用的什么数据结构了解吗&#xff1f; MySQL 的默认存储引擎是 InnoDB&#xff0c;它采用的是 B树结构的索引。 B树&#xff1a;只有叶子节点才会存储数据&#xff0c;非叶子节点只存储键值。叶子节点之间使用双向指针连接&#xff0c;最底层的叶子节点形成了一个…

FTP服务配置和使用

FTP介绍 FTP&#xff08;文件传输协议20、21端口&#xff09;是典型的C/S架构的应用层协议&#xff0c;需要由服务端软件、客户端软件两个部分共同实现文件传输协议。FTP是客户端和服务器之间的连接是可靠的保证。 FTP是一种文件传输协议&#xff0c;它支持两种模式&#xff…

集合框架----源码解读LikedHashSet篇

1.官方介绍 Hash表和链表实现了Set接口&#xff0c;具有可预测的迭代顺序。该实现与HashSet的不同之处在于它维护了一个贯穿其所有条目的双向链表。该链表定义了迭代顺序&#xff0c;即元素插入集合的顺序(插入顺序)。注意&#xff0c;如果一个元素重新插入到集合中&#xff0c…

Git---idea中git的基本操作

idea中使用git仓库 idea中配置git仓库&#xff1a; 首先idea配置git仓库的位置 配置完成之后&#xff0c;有两种创建仓库的方式 从本地配置git仓库&#xff1a; idea本身设置好的&#xff0c;直接下一步就好 从远程克隆仓库&#xff1a; 如果远程仓库没有的话可以绑定完…

如何从 FastReport VCL 中将报表导出为PNG格式?

FastReport VCL是用于在软件中集成商务智能的现代解决方案。它提供了可视化模板设计器&#xff0c;可以访问最受欢迎的数据源&#xff0c;报告引擎&#xff0c;预览&#xff0c;将过滤器导出为30多种格式&#xff0c;并可以部署到云&#xff0c;Web&#xff0c;电子邮件和打印中…

openvswitch group hash实现代码分析

代码分析 ovs版本是2.11.0&#xff0c;linux版本是linux-3.10.0-693.21.1.el7。 只拿ovs实现的group hash和dp_hash举例分析代码&#xff0c;通过一个点一个功能切入代码&#xff0c;漫无目的看代码是很难看懂的&#xff0c;必须带着一个疑问看代码&#xff0c;点多了全面开花…

降低点云密度的几种方法(含python代码)

本文只是对学习过程中的点云密度降采样的几种方法做一个记录&#xff0c;原文参考知乎Python点云数据处理(四)点云下采样 - 知乎 (zhihu.com) 本文介绍python点云数据处理中的点云下采样算法和关键点算法以及在点云工具箱软件中的实现。由于点云的海量和无序性&#xff0c;直接…

Java基于springboot+vue的房屋出租租房系统 前后端分离

伴随着全球信息化发展&#xff0c;行行业业都与计算机技术相衔接&#xff0c;计算机技术普遍运用于各大行业&#xff0c;房屋出租管理系统便是其中一种。实施计算机系统来管理可以降低大学生租房管理的成本&#xff0c;使整个大学生租房的发展和服务水平有显著提升。 本论文主要…