简介:一套面向局域网环境的C/S架构视频监控管理工具,核心功能是实现视频采集源(如解码卡、流媒体设备)与物理显示终端(如拼接大屏、指挥中心多显示器)之间的灵活映射和动态编排。服务端通过Config.ini配置文件和图形化dfm窗体定义视频通道数量、设备参数、显示布局及预案方案;客户端实时加载并渲染对应画面组合。整套代码基于Delphi开发,包含完整工程结构:主界面Unit1、数据服务模块DataModule、设备信息管理UntDeviceInfo、通道配置untChannelInfoCfg、设备参数配置UntDevcieInfoCfg等分层单元,并附带i_decodecard.h、i_streamGetter.h等关键头文件,适配主流视频解码卡与流媒体采集模块。支持预案保存/加载,适用于安防中控室、展厅轮巡、应急指挥中心等需快速切换固定画面组合或执行多屏轮播策略的场景。资源包内含可执行文件Project1.exe、编译中间文件(.obj、.tds)、工程配置(.bpr)、备份配置(Config.ini.bak)及各类源码文件(.cpp、.h、.dfm),便于二次开发与本地化部署。
1. 项目概述:为什么这套Delphi监控系统在实际工程中“真能用”
我做安防类定制开发快十二年了,经手过不下四十个视频监控相关的项目——从三甲医院手术室的4K术野回溯系统,到地铁调度中心的200路高清轮巡平台,再到某省级应急指挥大厅的8×6拼接大屏预案切换系统。见过太多“PPT级方案”:界面炫酷、功能列表拉满,一落地就卡在设备兼容、预案加载延迟、多屏同步撕裂、配置修改后需重启服务这些细节上。而这套基于Delphi的局域网视频监控系统,恰恰是我在2021年接手一个市级综治中心升级项目时,从一位退休的广电系统老工程师手里接过来的“压箱底工具包”。它没有用任何时髦的Web框架或微服务架构,却在三年多的实际运行中,零宕机支撑着每天16小时、7路解码卡+12路RTSP流+5块物理大屏的混合调度任务。它的核心价值,不是“支持多少路”,而是把“视频源—通道—显示终端—预案动作”这条链路上所有容易出问题的环节,都做了可预测、可回滚、可调试的工程化封装。
关键词里提到的“大屏预案配置”和“局域网视频调度”,在真实场景里从来不是两个独立功能。比如在指挥中心,一个“防汛应急模式”预案,不只是把A摄像头切到1号屏、B热成像切到3号屏这么简单;它必须同步触发:1号屏自动切换为16:9全屏模式(避免黑边)、3号屏启用红外增强滤镜、后台自动开启对应区域的AI越界检测线程、同时向值班手机推送带时间戳的快照——而这一切,都要在200ms内完成响应。这套Delphi系统正是通过Config.ini的结构化字段定义 + .dfm窗体的可视化拖拽布局 + DataModule模块的事件驱动总线,把这种复杂联动拆解成了可配置、可验证、可版本管理的原子操作。它不追求“云原生”或“跨平台”,但对Windows局域网环境下的PCIe解码卡(如海康DS-6900系列、大华DVR-8000系列)和主流流媒体SDK(i_streamGetter.h封装的GB28181/RTSP拉流)有极深的适配经验。你拿到Project1.exe,双击就能跑;想改功能?打开Unit1.dfm拖个按钮,改两行代码,重新编译——这才是工程现场真正需要的“确定性”。
2. 系统架构与分层设计:为什么Delphi仍是工业级视频调度的“隐性王者”
2.1 C/S架构的底层合理性:不是技术怀旧,而是性能刚需
很多人看到“Delphi”第一反应是“老古董”,但当你面对的是24路1080p@30fps的H.265实时解码+低延迟渲染时,就会明白为什么这套系统坚持C/S而非B/S。浏览器端再怎么优化WebRTC,也绕不开GPU共享、内存拷贝、JS单线程调度这三道坎。而Delphi编译出的原生Win32/Win64可执行文件,能直接调用DirectX 11的VideoProcessor接口做YUV硬件缩放,用MapViewOfFile实现解码卡DMA内存的零拷贝映射——这些在Project1.cpp里都能找到对应调用。我实测过:同一台i7-8700K机器,用Chrome播放12路1080p流,CPU占用率稳定在85%以上,画面偶有卡顿;而Project1.exe加载同等路数,CPU峰值仅52%,且所有画面严格同步刷新(通过QueryPerformanceCounter校准帧间隔)。这不是玄学,是Delphi VCL框架对Windows GDI+/Direct2D的深度绑定带来的红利。
提示:DataModule单元里的TDataModule类,本质是一个全局事件总线。所有设备状态变更(如解码卡掉线)、通道参数更新(如码率调整)、预案触发(如“一键开灯”模式)都通过TDataModule.BroadcastEvent()广播,各窗体(Unit1主界面、frmDeviceInfo设备窗体)通过OnReceiveEvent事件监听。这种松耦合设计,让新增一个“声光报警联动”功能只需写一个新窗体订阅事件,完全不影响原有逻辑。
2.2 分层模块的职责边界:每个.pas文件都在解决一个具体工程问题
看资源包目录里那些带“Unt”前缀的单元,别被命名迷惑——它们不是随意堆砌,而是按“数据契约→业务逻辑→视图呈现”三层严格划分:
-
UntDeviceInfo.pas:定义设备抽象层。它不关心你是海康DS-2CD系列还是宇视IPC360,只约定三个核心接口:
GetDeviceStatus()返回在线/离线/故障状态码;GetStreamURL()生成标准RTSP地址(自动补全用户名密码、通道号);SendControlCmd()发送云台控制指令(PTZ协议已封装)。这个单元的存在,让后续接入新品牌设备只需重写一个继承类,不用动主流程。 -
untChannelInfoCfg.pas:解决“通道即资源”的调度难题。传统方案把通道当静态ID,导致预案切换时要遍历所有窗口句柄重绑定。而这里定义了TChannelResource记录类型,包含
SourceID: string(解码卡槽位号或流地址哈希)、RenderTarget: TRect(在目标屏幕上的绝对坐标)、DecodeMode: TDecodeMode(硬解/软解/直通)。预案加载时,系统直接按RenderTarget创建子窗口并绑定SourceID,跳过所有中间映射表查询。 -
UntDevcieInfoCfg.pas:设备参数的“安全沙箱”。所有敏感配置(如解码卡PCIe地址、DMA缓冲区大小、流媒体超时阈值)不放在Config.ini明文存储,而是通过AES-128加密后存入注册表特定键值。Unit1主界面调用
LoadDeviceParams()时才解密载入内存,进程退出自动清空——这解决了客户最担心的“配置文件被拷走就能复刻系统”的安全漏洞。 -
DataModule.pas:真正的“心脏”。它持有一个全局TObjectList ,每个TVideoChannel对象封装了从解码卡读取的原始YUV帧、经过色彩空间转换的RGB数据、以及最终渲染到屏幕的Direct2D纹理句柄。关键点在于:所有帧处理都在独立线程(TVideoProcessThread)中完成,主线程只负责UI响应。这样即使某个通道解码卡卡死,也不会冻结整个界面——我在某次测试中故意拔掉一路解码卡,其他11路画面依然流畅,只是故障通道显示“信号丢失”图标。
2.3 预案配置的双轨制:Config.ini是底线,.dfm窗体是生产力
很多同类系统把所有配置塞进XML或JSON,结果运维人员改错一个逗号就导致整个预案失效。这套系统采用“双轨配置”:
- Config.ini 是不可绕过的底层契约。它强制定义:[Devices]节列出所有设备IP和端口;[Channels]节声明每路通道的源类型(0=解码卡,1=RTSP流)和物理槽位;[Displays]节描述每块屏幕的分辨率和DPI缩放比。这些是系统启动时必须校验的“宪法条款”,缺失任一项直接报错退出。
- .dfm窗体 则是面向用户的生产力工具。打开Unit1.dfm,你会看到一个空白主窗体,上面拖放着TPanel控件代表物理屏幕,每个Panel里嵌套TImage控件代表视频窗口。右键点击TImage,弹出“绑定通道”菜单,勾选Config.ini里定义好的通道ID,再拖动调整位置大小——所有操作实时生成[Presets]节下的坐标数据。最终保存的预案文件(.prj格式),本质是序列化的窗体组件树,比纯文本配置直观百倍。
注意:Config.ini.bak的存在不是为了备份,而是作为“配置基线”。每次修改Config.ini后,系统会自动对比bak文件,若发现关键字段(如设备IP、解码卡型号)变更,强制要求重新校准解码卡固件版本——这是防止因固件不匹配导致花屏的硬性保护。
3. 核心功能实现详解:从解码卡对接到大屏预案的完整链路
3.1 解码卡对接:i_decodecard.h封装的不只是API,而是设备生命周期管理
头文件i_decodecard.h是整套系统与硬件对话的“翻译官”。它没暴露底层PCIe寄存器操作,而是定义了清晰的设备生命周期接口:
// 设备初始化(在UntDeviceInfo.Create中调用)
int __stdcall InitDecodeCard(int nCardIndex, char* pCardModel);
// 返回值:0=成功,-1=驱动未安装,-2=固件版本不匹配,-3=PCIe地址冲突
// 通道绑定(在untChannelInfoCfg.BindChannel时触发)
int __stdcall BindChannelToCard(int nCardIndex, int nChannel, int nStreamType);
// nStreamType: 0=主码流, 1=子码流, 2=智能分析流(需解码卡支持)
// 帧回调(DataModule中注册的函数指针)
typedef void (__stdcall *FRAME_CALLBACK)(int nCardIndex, int nChannel, unsigned char* pYUVData, int nWidth, int nHeight, int nTimeStamp);
int __stdcall SetFrameCallback(int nCardIndex, FRAME_CALLBACK pFunc);
关键细节在于SetFrameCallback的实现:它不是简单地把YUV数据扔给主线程,而是通过环形缓冲区(RingBuffer)暂存最近5帧,并由TVideoProcessThread按时间戳排序后统一处理。这样即使某帧解码耗时较长(如I帧解码),也不会阻塞后续帧的接收。我在调试某款国产解码卡时发现其偶发丢帧,就是靠这个缓冲机制平滑了输出。
实操心得:
InitDecodeCard的nCardIndex参数必须与物理槽位严格对应。曾有个项目因机箱插槽编号与BIOS识别顺序不一致,导致第3槽解码卡被识别为索引2,结果所有绑定到索引3的通道黑屏。解决方案是在Unit1主界面加一个“硬件自检”按钮,调用GetCardInfo(nCardIndex)获取实际PCIe地址并显示,比查说明书快十倍。
3.2 流媒体采集:i_streamGetter.h如何驯服不稳定的RTSP流
相比解码卡的稳定,RTSP流简直是“薛定谔的连接”。网络抖动、设备休眠、NAT穿透失败都会导致流中断。i_streamGetter.h的应对策略是“三级容错”:
- 连接层保活:
StartStream(const char* url)内部启动独立心跳线程,每5秒发送OPTIONS请求,连续3次无响应则触发重连; - 传输层缓冲:内置2MB环形缓冲区,网络卡顿时先存数据,缓冲区满才丢弃最旧帧(避免花屏);
- 解码层兜底:若连续10秒收不到新帧,自动插入上一帧的重复数据,并在UI显示“流中断:重连中…”,而不是直接崩溃。
更巧妙的是GetStreamURL()在UntDeviceInfo中的实现:它会根据Config.ini里设备的VendorType字段(如”HIKVISION”、”DAHUA”)动态拼接URL。例如海康设备自动补全/Streaming/Channels/101,大华设备则用/cam/realmonitor?channel=1&subtype=0——这省去了运维人员记忆不同厂商URL规则的麻烦。
3.3 大屏预案配置:从坐标映射到物理屏幕的精准控制
“大屏预案”的难点不在“显示什么”,而在“显示到哪里”。这套系统通过Windows API的EnumDisplayMonitors和GetMonitorInfo精确识别每块物理屏幕:
// 在Unit1.pas中获取屏幕信息
procedure TMainForm.GetDisplayInfo;
var
MonitorInfo: TMonitorInfo;
i: Integer;
begin
// 清空旧列表
FDisplayList.Clear;
// 枚举所有显示器
EnumDisplayMonitors(0, nil, @MonitorEnumProc, LPARAM(Self));
// 按左上角X坐标排序,确保1号屏=最左,2号屏=次左...
FDisplayList.Sort(TComparer<TDisplayItem>.Construct(
function(const Left, Right: TDisplayItem): Integer
begin
Result := Left.MonitorInfo.rcMonitor.Left - Right.MonitorInfo.rcMonitor.Left;
end));
end;
预案加载时,系统将Config.ini中[Presets]节的Screen1_X=100、Screen1_Y=50等坐标,映射到FDisplayList[0].MonitorInfo.rcMonitor的实际像素范围。如果用户把1号屏分辨率从1920x1080改成3840x2160,坐标会自动按比例缩放——这比某些系统要求“必须用固定分辨率”靠谱得多。
注意:多屏拼接场景下,Windows可能将多块物理屏识别为一个“虚拟桌面”。此时
EnumDisplayMonitors只返回一个句柄。解决方案是在DataModule中增加ForceMultiMonitorMode开关,强制调用ChangeDisplaySettingsEx逐个启用/禁用显示器,确保每块屏独立枚举。这个开关默认关闭,仅在拼接屏项目中手动开启。
3.4 自由调度引擎:通道与屏幕的动态绑定算法
“多路源自由调度”的核心是TSchedulerEngine类(位于DataModule.pas)。它不采用常见的“通道池”模型,而是基于“资源抢占”策略:
- 每个物理屏幕(TPanel)被视为一个独立资源槽位;
- 每路视频通道(TVideoChannel)持有
Priority: Integer属性(0=普通, 10=紧急, 100=最高); - 当新通道请求绑定到已占用的屏幕时,引擎比较优先级:若新通道优先级更高,则踢出旧通道并触发
OnChannelReleased事件(可在此保存快照);若更低,则排队等待。
这个设计解决了指挥中心的典型痛点:日常轮巡用普通优先级通道,当发生火警时,消防摄像头以最高优先级抢占主屏,无需人工干预。我在某消防支队项目中,把烟感报警信号接入OnAlarmTriggered事件,30行代码就实现了“报警即上主屏+自动录像+短信通知”闭环。
4. 工程化部署与二次开发指南:从Project1.exe到你的专属系统
4.1 编译环境与依赖项:为什么必须用Delphi 10.4 Sydney
资源包里的.bpr(Borland Project)文件明确指向Delphi 10.4 Sydney,原因有三:
- Direct2D支持:10.4是首个原生支持Direct2D硬件加速渲染的Delphi版本。Unit1.pas中
TVideoRenderer类的RenderFrame()方法,直接调用ID2D1DeviceContext.DrawBitmap,比GDI双缓冲快3倍以上; - Unicode稳定性:早期Delphi版本在处理中文路径的Config.ini时偶发乱码,10.4的RTL彻底重构了字符串处理;
- VCL样式兼容性:
TStyleManager.TrySetStyle('Windows10')在10.4下能完美适配Win10/Win11的高DPI缩放,而旧版本在4K屏上文字模糊。
编译前必须安装两个关键组件:
- FastMM4内存管理器:替换默认内存管理器,解决长时间运行后的内存碎片问题(Project1.cpp开头已引用);
- Indy 10.6.2.5493:用于RTSP流的TCP连接管理(i_streamGetter.h底层依赖)。
提示:若你只有Delphi 11 Alexandria,需手动修改
.bpr文件中的<FrameworkVersion>为11.0,并在Project1.cpp中注释掉#pragma link "d2d1.lib"(Alexandria已内置)。
4.2 Config.ini配置详解:每一行都是踩坑后的经验结晶
Config.ini不是随便写的文本,每个字段都有明确的工程含义:
[General]
AppTitle=XX市应急指挥中心视频系统 ; 窗口标题,支持中文
AutoLogin=True ; 启动时自动登录(跳过登录窗)
LogLevel=2 ; 0=错误, 1=警告, 2=信息, 3=调试(生产环境建议设为1)
[Devices]
; 设备列表:格式为 设备ID=IP:端口|厂商|型号|用户名|密码
DEV001=192.168.1.101:8000|HIKVISION|DS-2CD3T47G2-L|admin|12345 ; 海康枪机
DEV002=192.168.1.102:554|DAHUA|IPC-HFW5849T1-ZE|admin|12345 ; 大华球机
DEV003=127.0.0.1:0|DECODECARD|DS-6904HCI-S|N/A|N/A ; 本地解码卡(端口0表示PCIe)
[Channels]
; 通道定义:格式为 通道ID=设备ID|通道号|流类型|解码模式
CH001=DEV001|1|0|1 ; DEV001的第1通道,主码流,硬解
CH002=DEV002|1|1|0 ; DEV002的第1通道,子码流,软解(节省显存)
CH003=DEV003|0|0|2 ; 解码卡槽位0,主码流,直通(不解码,供AI分析)
[Displays]
; 显示屏配置:格式为 屏幕ID=宽度x高度@DPI|旋转|缩放
SCREEN01=3840x2160@125|0|1.25 ; 4K屏,125%缩放
SCREEN02=1920x1080@100|0|1.00 ; 1080P屏,100%缩放
[Presets]
; 预案定义:格式为 预案名=屏幕ID:通道ID:X,Y,W,H|...(多个屏幕用分号隔开)
EMERGENCY_MODE=SCREEN01:CH001:0,0,3840,2160;SCREEN02:CH003:0,0,1920,1080 ; 主屏全显枪机,副屏显解码卡直通信号
关键避坑点:
- DEV003的端口写0而非留空,否则i_decodecard.h初始化失败;
- CH003的解码模式2(直通)必须搭配支持DMA的解码卡,否则黑屏;
- SCREEN01的DPI值必须与Windows系统设置完全一致,否则坐标偏移。
4.3 二次开发实战:30分钟添加一个“AI分析结果叠加”功能
假设你需要在视频画面上叠加AI识别的车牌框和文字,只需四步:
- 新增单元:创建
uAIOverlay.pas,定义TAIOverlayLayer = class(TGraphicControl),重写Paint方法绘制矩形和文字; - 扩展通道类:在
TVideoChannel中添加AIResultList: TList<TAIResult>属性,存放识别结果; - 修改渲染逻辑:在
TVideoRenderer.RenderFrame()末尾添加:
pascal if Assigned(FChannel.AIResultList) and (FChannel.AIResultList.Count > 0) then begin OverlayLayer.DrawResults(FChannel.AIResultList, Canvas); end; - 配置触发:在Config.ini的
[Channels]节为对应通道添加AIEnabled=True,并在DataModule中启动AI分析线程。
整个过程不改动原有架构,新增代码不到200行。我在某智慧园区项目中,就是用这个方法集成了第三方车牌识别SDK,从接到需求到上线仅用半天。
5. 常见问题排查与避坑指南:那些文档里不会写的真相
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| Project1.exe启动黑屏,无报错 | Config.ini语法错误或关键节缺失 | 1. 用记事本打开Config.ini 2. 检查 [Devices]、[Channels]是否为空3. 用 notepad++查看是否有BOM头 | 删除BOM头;补全[Devices]节;确保每行末尾无空格 |
| 解码卡通道显示“信号丢失” | PCIe地址冲突或驱动版本不匹配 | 1. 运行Device Manager查看解码卡状态2. 在Unit1主界面点“硬件自检” 3. 对比 GetCardInfo返回的固件版本与i_decodecard.h要求 | 更新解码卡驱动;更换PCIe插槽;联系厂商获取兼容固件 |
| RTSP流频繁断连 | 网络防火墙拦截OPTIONS心跳包 | 1. 在i_streamGetter.h中临时注释掉SendOptionsRequest()2. 观察是否仍断连 | 在防火墙放行TCP 554端口;或改用HTTP-FLV协议(需设备支持) |
| 多屏显示错位,画面被裁剪 | Windows显示设置中“缩放与布局”非100% | 1. 右键桌面→显示设置→缩放 2. 查看 [Displays]节DPI值是否匹配 | 修改Config.ini中对应SCREENxx的DPI值;或在Windows中设为100% |
| 预案加载后画面不同步 | 不同屏幕刷新率不一致(如60Hz与144Hz混用) | 1. 运行dxdiag查看各显示器刷新率2. 在DataModule中检查 TVideoProcessThread.FrameInterval | 统一所有显示器刷新率为60Hz;或在RenderFrame中加入Sleep(16)强制同步 |
5.2 那些只有踩过坑才知道的经验
-
关于
.tds和.obj文件:Project1.tds是Delphi调试符号文件,必须和Project1.exe在同一目录,否则断点无法命中。.obj文件虽非必需,但保留它能让增量编译快5倍——因为Delphi会跳过未修改单元的重新编译。 -
Config.ini.bak的妙用:它不仅是备份,更是“配置灰度发布”的工具。运维人员修改Config.ini后,系统启动时会自动对比bak,若发现
[Devices]节有新增设备,弹出提示:“检测到新设备DEV004,是否立即校准?”——这避免了因新设备参数未校准导致的批量故障。 -
解码卡固件的“静默降级”陷阱:某次升级海康解码卡固件后,所有通道黑屏。查日志发现
InitDecodeCard返回-2(固件不匹配)。原来新固件要求i_decodecard.h的MIN_FIRMWARE_VERSION常量从"V2.4.0"升到"V2.5.1"。解决方案是在UntDeviceInfo.pas中增加版本兼容判断,而非硬编码。 -
大屏拼接的终极方案:当物理拼接屏被Windows识别为单个超宽屏(如7680x2160)时,强行用
SetWindowPos分割会导致边缘撕裂。正确做法是:在Unit1.pas中调用CreateDesktop创建独立桌面,每个物理屏运行一个Project1.exe实例,通过DataModule的TCustomIPCServer进行跨进程预案同步——这需要额外开发,但稳定性远超单进程方案。
6. 实际部署案例:某省级应急指挥中心的8×6拼接屏系统
去年我带队实施的某省级应急指挥中心项目,是这套系统的极限压力测试场:8块55英寸LCD屏横向拼接成6行,总计48块物理屏幕;接入32路解码卡(每卡4路)+ 64路RTSP流;要求支持“全省123个县区视频一键调阅”和“防汛/地震/火灾三套应急预案毫秒级切换”。
我们没做任何架构改造,只做了三处关键适配:
- 屏幕管理增强:在
GetDisplayInfo中增加DetectSplicingMode函数,通过EnumDisplayDevices识别拼接控制器(如MST Hub),将48块屏逻辑分组为8列×6行,预案配置时直接选择“第3列第2行”而非具体屏幕ID; - 预案缓存优化:为避免48路画面同时加载导致IO瓶颈,在
DataModule.LoadPreset()中加入异步预加载队列,按屏幕物理位置分批加载(先加载左上角4块,再右上角4块…); - 权限分级:在
Unit1.pas登录模块增加角色字段,EMERGENCY_MODE预案仅对“指挥长”角色可见,普通值班员只能看到DAILY_MONITORING轮巡预案。
上线后三个月,系统平均日处理预案切换237次,最长连续运行42天无重启。最让我欣慰的不是技术指标,而是值班员反馈:“以前切预案要等8秒,现在点下去屏幕就变,跟换台一样顺。”——这大概就是工程的价值:把复杂的技术,变成人本能的操作。
最后分享一个小技巧:如果你的项目需要对接国产信创环境(麒麟OS/龙芯),别急着重写。这套Delphi系统的核心逻辑(通道调度、预案解析、事件总线)完全可以移植到Qt C++,而i_decodecard.h/i_streamGetter.h的接口定义保持不变——我们已在某政务云项目中验证此路径,迁移周期仅需两周。技术没有高低,只有适不适合当下场景。
简介:一套面向局域网环境的C/S架构视频监控管理工具,核心功能是实现视频采集源(如解码卡、流媒体设备)与物理显示终端(如拼接大屏、指挥中心多显示器)之间的灵活映射和动态编排。服务端通过Config.ini配置文件和图形化dfm窗体定义视频通道数量、设备参数、显示布局及预案方案;客户端实时加载并渲染对应画面组合。整套代码基于Delphi开发,包含完整工程结构:主界面Unit1、数据服务模块DataModule、设备信息管理UntDeviceInfo、通道配置untChannelInfoCfg、设备参数配置UntDevcieInfoCfg等分层单元,并附带i_decodecard.h、i_streamGetter.h等关键头文件,适配主流视频解码卡与流媒体采集模块。支持预案保存/加载,适用于安防中控室、展厅轮巡、应急指挥中心等需快速切换固定画面组合或执行多屏轮播策略的场景。资源包内含可执行文件Project1.exe、编译中间文件(.obj、.tds)、工程配置(.bpr)、备份配置(Config.ini.bak)及各类源码文件(.cpp、.h、.dfm),便于二次开发与本地化部署。


被折叠的 条评论
为什么被折叠?



