ESP32-CAM多客户端MJPEG视频流服务器:低成本智能家居监控方案
1. 项目概述与核心价值最近在折腾一个智能家居的监控项目手头有几块闲置的ESP32-CAM模组想实现一个低功耗、低成本的多路视频流服务器。找了一圈开源方案发现arkhipenko/esp32-mjpeg-multiclient-espcam-drivers这个项目非常对胃口。它不是一个简单的摄像头示例而是一个专门为ESP32-CAM设计的、支持多客户端同时访问的MJPEG视频流服务器驱动库。简单来说它让你能用一块小小的ESP32板子通过Wi-Fi同时向多个手机、电脑或服务器推送实时的摄像头画面而且画面是压缩成MJPEG格式的对网络带宽和客户端解码压力都比较友好。这个项目的核心价值在于解决了ESP32-CAM在视频流应用中的一个痛点并发访问能力弱。很多基础的ESP32摄像头例程一次只能服务一个客户端连接第二个设备想连上去看画面要么连不上要么会把前一个挤掉。这在需要多屏查看比如家庭安防你和你家人都想同时看或者需要将视频流同时推送到多个后端服务比如本地存储和云端AI分析的场景下就显得捉襟见肘了。arkhipenko/esp32-mjpeg-multiclient-espcam-drivers通过优化的驱动和任务调度让一块ESP32-CAM能够稳定地同时处理多个TCP连接并高效地编码、分发MJPEG帧大大提升了其实用性。它非常适合那些对成本敏感、需要分布式部署、且对视频流实时性要求不是极端苛刻比如不是毫秒级延迟的机器视觉的物联网项目。无论是DIY一个家庭婴儿监视器、宠物摄像头还是构建一个小型的仓库货架监控系统甚至是作为创客项目的视觉输入模块这个库都能提供一个可靠、易用的基础。接下来我会结合自己的实际部署经验从设计思路、环境搭建、代码解析到问题排查完整地拆解这个项目。2. 核心设计思路与架构拆解2.1 为什么选择MJPEG而非H.264或JPEG快照这是理解该项目设计的第一个关键点。ESP32-CAM模组上的摄像头传感器通常是OV2640或OV7670输出的原始数据是YUV或RGB格式数据量巨大直接通过Wi-Fi传输是不可能的。因此必须压缩。JPEG快照模式这是最简单的方式ESP32的硬件JPEG编码器将一帧图像压缩成一张JPEG图片发送。优点是单帧图片质量高、兼容性极好。但缺点更明显为了获得流畅视频需要高频率如10-30帧每秒地抓拍和发送每次建立连接、传输、断开的过程开销巨大网络利用率低延迟高且不稳定更难以支持多客户端。H.264视频流这是高质量视频流的代表压缩率高。但ESP32的单核或双核240MHz主频进行软件H.264编码是不现实的。虽然有外部硬编码芯片的方案但会显著增加复杂度和成本。MJPEGMotion JPEG这正是本项目选择的方案。它本质上是将一系列独立的JPEG图片按顺序快速播放。ESP32的硬件JPEG编码器能力被充分利用可以较高帧率如10-20 FPS地编码出JPEG帧。然后通过HTTP协议以multipart/x-mixed-replace的Content-Type进行流式传输。客户端如浏览器、VLC播放器会保持TCP连接打开持续接收并刷新显示的JPEG图片。选择MJPEG的理由硬件加速完美利用ESP32内置的JPEG编码硬件CPU占用率低。低延迟每一帧都是独立完整的图片没有像H.264那样的帧间预测依赖网络抖动对单帧影响小端到端延迟相对稳定。客户端兼容性好任何支持显示JPEG图片并能够处理HTTP流式传输的客户端都能播放无需复杂解码库。实现多客户端相对简单服务器端只需要为每个连接的客户端独立执行“抓图-编码-发送”的循环共享同一个摄像头硬件资源通过合理的任务调度和内存管理即可实现。2.2 多客户端支持的架构实现项目名称中的“multiclient”是精髓。如何在资源有限的ESP32上实现基于任务的并发模型ESP32通常运行FreeRTOS。该项目会为每一个新接入的TCP客户端创建一个独立的FreeRTOS任务Task。这个任务负责管理与该客户端的整个Socket通信生命周期。摄像头驱动与帧缓冲管理这是关键。摄像头硬件是单一的不能同时被多个任务读取。因此需要一个“生产者-消费者”模型。生产者一个高优先级的专用任务或定时器中断以固定频率如每秒10次从摄像头硬件读取一帧原始图像并送入硬件JPEG编码器。编码完成后将得到的JPEG数据放入一个或多个帧缓冲区中。帧缓冲区通常是一个环状缓冲区Ring Buffer里面存放着最近编码好的几帧JPEG数据及其元数据如大小、时间戳。缓冲区大小是关键参数太小会导致客户端任务取不到新帧而等待太大则会耗尽宝贵的内存PSRAM。消费者每个客户端任务。它们独立运行当需要向自己的客户端发送下一帧时就从共享的帧缓冲区中取出当前最新的一帧或根据策略取一帧。这样就实现了一次编码多次分发。所有客户端看到的画面在时间上几乎是同步的略有微小延迟。非阻塞式网络传输每个客户端任务在发送JPEG数据时必须使用非阻塞的Socket操作并处理好send()函数可能只发送了部分数据的情况由于TCP窗口、网络拥塞。任务需要在while循环中持续发送直到整帧数据发送完毕期间不能长时间阻塞以免影响其他客户端任务或摄像头抓图任务。这种架构平衡了性能与资源消耗。摄像头以固定速率生产帧多个客户端消费这些帧通过共享内存避免了重复编码的巨大开销。2.3 与常见单客户端例程的对比常见的CameraWebServer示例也提供MJPEG流但其实现通常是“一个流式连接独占摄像头”的模式。当第一个浏览器连接上/stream端点时它进入一个循环cam.grab()-esp_camera_fb_get()-send to client。在此期间如果有第二个客户端尝试连接同一个端点服务器要么拒绝新连接要么需要等待第一个循环结束即第一个客户端断开无法实现真正的并发。而本项目的驱动库修改或封装了底层的摄像头访问逻辑使其具备上述的帧缓冲和多任务分发能力。从API层面它可能提供了类似get_latest_frame()这样的函数供各个客户端任务调用而不是直接调用阻塞式的抓图函数。3. 环境搭建与核心依赖解析3.1 硬件准备与选型建议核心板ESP32芯片是必须的。推荐使用带有PSRAM伪静态随机存储器的型号如ESP32-WROVER系列或ESP32-S3。OV2640摄像头输出一张UXGA1600x1200的图片未经压缩的RGB565格式就需要1600*1200*2 ≈ 3.66 MB。即使压缩成JPEG一帧也可能达到几十到上百KB。同时服务多个客户端需要多个帧缓冲区对内存需求很大。内置的520KB SRAM远远不够外置的4MB或8MB PSRAM至关重要。摄像头模组最常用的是OV2640它支持JPEG输出且ESP32的硬件JPEG编码器对其支持最好。OV7670虽然便宜但通常只输出YUV或RGB需要软件转换且不支持JPEG硬件加速帧率和分辨率会大打折扣不适合本项目追求多客户端流媒体的场景。供电ESP32-CAM在启动摄像头、打开闪光灯如果有和满负荷Wi-Fi传输时峰值电流可能超过500mA。务必使用足额5V/2A以上且稳定的电源。USB线连接电脑开发时有时会因为线材质量或USB口供电不足导致不断重启这是最常见的坑。3.2 软件开发环境配置项目通常以Arduino库或PlatformIO库的形式提供。以PlatformIO为例配置platformio.ini是关键。[env:esp32cam] platform espressif32 board esp32cam framework arduino monitor_speed 115200 ; 启用PSRAM支持这是重中之重 board_build.arduino.memory_type qio_opi board_build.partitions huge_app.csv ; 库依赖 lib_deps https://github.com/arkhipenko/esp32-mjpeg-multiclient-espcam-drivers.git ; 可能还需要依赖特定的摄像头驱动库根据项目README说明添加关键配置解读board esp32cam这指向一个通用的ESP32-CAM板型定义它通常已包含了正确的引脚映射。board_build.arduino.memory_type qio_opi此配置告知编译器链接时支持PSRAM并且使用正确的闪存访问模式。如果没有这一行代码可能无法使用PSRAM导致内存不足崩溃。board_build.partitions huge_app.csv使用更大的应用程序分区表因为包含摄像头驱动和网络库的固件可能超过默认的1.5MB分区大小。lib_deps直接引用Git仓库地址PlatformIO会自动克隆和编译该库。3.3 库的集成与初始化流程在Arduino IDE中你需要手动将库下载到libraries文件夹。无论哪种方式集成后代码中通常需要包含一个主头文件并遵循固定的初始化顺序引入Wi-Fi和摄像头驱动先包含必要的头文件。配置摄像头参数设置分辨率、像素格式、帧缓冲计数等。帧缓冲计数是核心参数它决定了在PSRAM中预分配多少个缓冲区来存放JPEG帧。建议初始值设置为同时连接的客户端数量2例如支持3个客户端就设5。设得太少生产帧速度可能快于消费速度导致丢帧设得太多浪费内存。初始化摄像头调用库提供的cam_init()函数传入配置参数。此函数会初始化摄像头传感器、分配PSRAM中的帧缓冲区。连接Wi-Fi启动STA模式连接到你家的路由器。启动HTTP服务器设置路由。通常库会提供一个处理MJPEG流的请求处理器Request Handler。你需要将其注册到服务器例如绑定到/mjpeg路径。启动服务器开始监听端口通常是80。这个初始化顺序不能乱特别是摄像头初始化必须在Wi-Fi连接之前或之后但务必确保PSRAM已经正确初始化并被驱动识别。4. 代码实操与关键参数调优4.1 基础示例代码拆解假设库提供了一个最简示例basic_mjpeg_server.ino其核心结构如下#include WiFi.h #include ESPmDNS.h #include “multicam_driver.h” // 假设的主头文件 // WiFi凭证 const char* ssid “your_SSID”; const char* password “your_PASSWORD”; // 摄像头配置结构体 camera_config_t config; // 初始化配置参数 config.ledc_channel LEDC_CHANNEL_0; config.ledc_timer LEDC_TIMER_0; config.pin_d0 5; config.pin_d1 18; // ... 其他引脚定义根据你的ESP32-CAM板型确定 config.pin_xclk 0; config.pin_pclk 22; config.pin_vsync 25; config.pin_href 23; config.pin_sscb_sda 26; config.pin_sscb_scl 27; config.pin_pwdn 32; config.pin_reset -1; config.xclk_freq_hz 20000000; // XCLK频率20MHz是常用稳定值 config.pixel_format PIXFORMAT_JPEG; // 必须为JPEG格式 // 关键参数分辨率与帧缓冲区 if(psramFound()){ // 有PSRAM可以使用更高分辨率 config.frame_size FRAMESIZE_UXGA; // 1600x1200 config.jpeg_quality 12; // 质量 (0-63, 越低越好) config.fb_count 5; // 帧缓冲数量根据客户端数调整 config.fb_location CAMERA_FB_IN_PSRAM; // 帧缓冲放在PSRAM } else { // 无PSRAM只能使用低分辨率和小缓冲区 config.frame_size FRAMESIZE_SVGA; // 800x600 config.jpeg_quality 30; config.fb_count 1; config.fb_location CAMERA_FB_IN_DRAM; } // 库提供的MJPEG流处理器 extern esp_err_t mjpeg_stream_handler(httpd_req_t *req); void setup() { Serial.begin(115200); // 1. 初始化摄像头 esp_err_t err esp_camera_init(config); if (err ! ESP_OK) { Serial.printf(“摄像头初始化失败: 0x%x”, err); return; } // 2. 连接Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(“.”); } Serial.println(“WiFi已连接”); Serial.print(“IP地址: “); Serial.println(WiFi.localIP()); // 3. 配置并启动HTTP服务器 httpd_config_t server_config HTTPD_DEFAULT_CONFIG(); // 提高堆栈大小以应对多客户端任务 server_config.stack_size 1024 * 8; // 例如8KB server_config.max_open_sockets 7; // 最大连接数至少大于客户端数 httpd_handle_t server NULL; if (httpd_start(server, server_config) ESP_OK) { // 4. 注册MJPEG流处理路由 httpd_uri_t mjpeg_uri { .uri “/stream”, .method HTTP_GET, .handler mjpeg_stream_handler, // 库提供的核心处理器 .user_ctx NULL }; httpd_register_uri_handler(server, mjpeg_uri); // 注册一个根路径用于显示测试页面 httpd_uri_t index_uri { .uri “/”, .method HTTP_GET, .handler [](httpd_req_t *req){ /* 返回一个简单的HTML页面内嵌img src\”/stream\” */ }, .user_ctx NULL }; httpd_register_uri_handler(server, index_uri); } } void loop() { // 主循环为空一切由任务和服务器处理 delay(1000); }4.2 关键参数深度调优指南上面的代码中有几个参数直接影响性能和稳定性需要根据实际情况调整config.frame_size(分辨率)UXGA (1600x1200)画面最清晰但单帧JPEG体积大可能200-500KB编码耗时稍长对网络带宽和客户端解码压力最大。适合客户端少1-2个、网络好、需要看清细节的场景。SXGA (1280x1024) / XGA (1024x768)平衡之选。清晰度足够帧体积适中是多数情况下的推荐设置。SVGA (800x600) / VGA (640x480)帧率可以做到更高网络带宽占用小延迟可能更低。适合对流畅度要求高、网络条件一般或客户端性能弱的场景如老旧手机。建议从XGA或SVGA开始测试。在串口日志中观察帧率和CPU占用率逐步调高。config.jpeg_quality(JPEG质量)范围通常是0-63数值越小质量越高图片体积越大。默认值12或10能提供很好的质量。如果网络带宽紧张可以尝试调到15-20画质损失在可接受范围内但帧体积能显著减小提升流畅度。注意质量设置对编码速度影响不大主要影响输出大小。config.fb_count(帧缓冲区数量)这是多客户端的核心。它决定了PSRAM中预分配的JPEG缓冲区个数。计算公式建议fb_count 最大预期客户端数 2。原理摄像头生产任务需要1个缓冲区用于编码每个活跃的客户端任务在发送时可能会占用1个缓冲区读取当前帧。额外的1-2个缓冲区作为缓冲防止因任务调度延迟导致的生产者摄像头没有空闲缓冲区可用的“饿死”情况。示例预计最多3个客户端同时看设置fb_count5。内存占用估算每个缓冲区大小 ≈ 分辨率对应的最大JPEG体积。UXGA下一个缓冲区可能占300KB5个就是1.5MB。务必确保你的PSRAM通常4MB或8MB足够还要为Wi-Fi、TCP栈等留出空间。config.xclk_freq_hz(摄像头主时钟频率)默认20MHz20000000在大多数OV2640模组上工作稳定。降低到10MHz可能有助于解决某些模组的图像噪点或条纹问题但可能会限制最高帧率。不建议随意提高可能导致摄像头工作不稳定。服务器配置server_config.stack_size和server_config.max_open_socketsstack_size每个客户端任务以及可能的摄像头任务都需要独立的堆栈空间。如果出现任务堆栈溢出崩溃可在日志中看到需要适当增加此值。81928KB是一个安全的起点。max_open_socketsHTTP服务器的最大并发连接数。至少要比你预期的视频流客户端数多几个因为还有HTTP页面请求等。4.3 高级功能动态配置与状态获取一个健壮的服务器应该提供一些控制接口。你可以在HTTP服务器上增加额外的路由GET /status返回JSON格式的状态信息如当前帧率、客户端连接数、内存使用情况、Wi-Fi信号强度等。这有助于远程监控设备健康度。POST /config接收JSON请求动态更改摄像头参数如分辨率、质量。注意更改分辨率通常需要先esp_camera_deinit()再以新配置esp_camera_init()这个过程会中断当前的视频流。需要谨慎处理最好在无客户端连接时进行或者先通知客户端断开。GET /snapshot提供一个单独的端点用于获取单张高质JPEG快照而不影响MJPEG流。这在需要抓图存档或AI识别时很有用。实现这些功能需要对库的内部API有更深入的了解可能需要调用库提供的get_current_framerate()、get_active_clients()等函数如果库提供了的话。5. 网络优化与客户端适配实践5.1 服务器端网络性能调优ESP32的Wi-Fi和TCP/IP栈性能有限优化传输效率至关重要。TCP发送窗口与缓冲区在ESP-IDF的底层可以调整TCP发送缓冲区大小。增大缓冲区可以减少因网络瞬时拥塞导致的发送阻塞但会消耗更多内存。这通常需要在menuconfig中配置如果使用Arduino框架可能封装较深不易调整。更实用的方法是在应用层确保send()操作是非阻塞的并处理好部分发送的情况。MJPEG的HTTP响应头正确的HTTP头能确保客户端正确解析流。static const char* _STREAM_CONTENT_TYPE “multipart/x-mixed-replace;boundaryframe”; static const char* _STREAM_BOUNDARY “\r\n--frame\r\n”; static const char* _STREAM_PART “Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n”;每一帧JPEG数据前都需要发送边界符和内容头。确保头信息格式正确特别是Content-Length必须准确反映紧接着的JPEG数据大小。降低默认帧率不一定需要摄像头全力输出。如果网络带宽不足可以在摄像头配置后通过传感器控制函数sensor_t * s esp_camera_sensor_get(); s-set_framesize(s, framesize); s-set_quality(s, quality);虽然直接设置帧率的API可能不直接但可以通过控制frame_time_ms帧间隔时间来间接实现。或者在生产帧的任务中通过vTaskDelay(pdMS_TO_TICKS(interval_ms))来控制抓图频率例如每秒10帧100ms间隔这能显著降低带宽消耗和CPU负载。5.2 客户端连接与播放指南网页浏览器最简单的方式。在HTML中嵌入一个img标签其src属性指向ESP32的IP和/stream路径例如img src”http://192.168.1.100/stream”。现代浏览器Chrome, Firefox, Edge都能自动处理multipart/x-mixed-replace流并持续刷新图像。VLC Media Player打开VLC点击“媒体” - “打开网络串流”。输入URLhttp://192.168.1.100/streamVLC能很好地播放MJPEG流并且可以录制、调整播放速度。Python OpenCVimport cv2 stream_url ‘http://192.168.1.100/stream’ cap cv2.VideoCapture(stream_url) while True: ret, frame cap.read() if not ret: break cv2.imshow(‘ESP32-CAM Stream’, frame) if cv2.waitKey(1) 0xFF ord(‘q’): break cap.release() cv2.destroyAllWindows()OpenCV的VideoCapture能直接读取MJPEG HTTP流方便进行计算机视觉处理。Home Assistant集成可以通过Home Assistant的“Generic IP Camera”平台集成。在configuration.yaml中添加camera: - platform: generic name: ESP32 Cam still_image_url: http://192.168.1.100/snapshot stream_source: http://192.168.1.100/stream这样就能在HA的仪表盘上看到实时流并可能进行人脸识别、移动检测等自动化。5.3 多客户端压力测试与行为观察部署好后需要进行压力测试用一台电脑的多个浏览器标签页同时打开/stream。同时用手机和电脑连接。观察串口日志如果库有输出看是否有连接成功/失败、任务创建/销毁的日志。在路由器后台或使用网络工具观察ESP32设备的网络上行速率。当多个客户端连接时上行带宽应接近单客户端带宽 * 客户端数。如果带宽没有线性增长可能遇到了ESP32 Wi-Fi吞吐量瓶颈或CPU瓶颈。观察每个客户端的画面流畅度。当客户端数增加时可能会出现全局帧率下降所有客户端都变卡。原因是摄像头生产帧的速度是瓶颈或者ESP32的CPU处理不过来。需要降低分辨率或JPEG质量。个别客户端卡顿可能是该客户端的网络状况不佳或者ESP32到该客户端的网络路径有问题。也可能是该客户端任务优先级较低获取帧的时机不稳定。客户端连接被拒绝达到max_open_sockets限制了。6. 常见问题排查与性能优化实录在实际部署中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案。6.1 编译与内存问题问题现象可能原因解决方案编译失败提示undefined reference to ‘psramInit’PSRAM未在框架中启用在platformio.ini中确保设置了board_build.arduino.memory_type qio_opi。在Arduino IDE中需要选择正确的带PSRAM的板型如“AI Thinker ESP32-CAM”。设备不断重启串口提示Guru Meditation Error: Core 0 panic’ed (LoadProhibited)内存访问错误。常因使用了未初始化的指针或访问了已释放的PSRAM内存。1. 检查摄像头引脚配置是否正确对应你的硬件。2. 确保psramFound()判断逻辑正确无PSRAM时不要配置fb_location CAMERA_FB_IN_PSRAM。3. 检查多任务间共享的帧缓冲区访问是否有竞态条件需用信号量或互斥锁保护。启动后很快崩溃提示malloc failed或Out of memoryPSRAM分配失败fb_count设置过大或总内存不足。1. 减少fb_count。2. 降低分辨率frame_size因为每个缓冲区大小与分辨率成正比。3. 检查是否有其他库或代码消耗了大量内存。6.2 图像质量与摄像头问题问题现象可能原因解决方案画面有彩色条纹、噪点严重摄像头时钟XCLK不稳定或频率不匹配电源噪声干扰。1. 尝试降低xclk_freq_hz到10MHz10000000。2. 确保给ESP32-CAM的电源是稳定且足额的尽量靠近模组增加一个100-220uF的电解电容。3. 检查摄像头排线是否连接牢固。画面颜色偏蓝、偏绿或失真摄像头白平衡或色彩饱和度设置不当。在初始化后获取传感器对象并调整参数sensor_t *s esp_camera_sensor_get();s-set_brightness(s, 0); // -2 to 2s-set_saturation(s, 0); // -2 to 2s-set_whitebal(s, 1); // 0 disable, 1 enable画面模糊摄像头镜头未对焦。手动旋转ESP32-CAM模块上的镜头直到画面清晰。需要一个很小的螺丝刀。对焦时最好让摄像头对准远处2米以上的物体。6.3 网络与流传输问题问题现象可能原因解决方案浏览器显示破碎的图片或无法持续播放HTTP响应头格式错误或帧数据发送不完整。1. 确保在发送每一帧JPEG数据前严格按照格式发送了边界符和Content-Length头。2. 检查发送JPEG数据的循环是否确保所有字节都被发送出去处理了send()返回值。延迟非常大2秒网络带宽不足或缓冲区堆积。1. 降低分辨率(frame_size)和JPEG质量(jpeg_quality)。2. 在服务器端增加帧间隔降低帧率。3. 检查客户端到ESP32的Wi-Fi信号强度。多客户端时后连接的客户端画面严重滞后帧缓冲区管理策略可能是“先进先出”(FIFO)后连接的任务取到了旧的缓冲帧。检查库的帧获取API。理想的API应该让每个客户端任务都能获取到最新的一帧而不是按顺序取。如果库是FIFO策略可能需要修改源码让生产者更新帧时同时更新一个“最新帧索引”消费者都去读这个索引指向的缓冲区需要做好读写保护。客户端偶尔断开连接Wi-Fi信号不稳定ESP32 Wi-Fi驱动或任务看门狗超时。1. 改善ESP32的放置位置远离金属物体和路由器。2. 在Arduino框架中可以尝试增加Wi-Fi的重连逻辑和看门狗超时时间。3. 在客户端增加重连机制。6.4 性能优化心得分辨率与帧率的权衡对于监控场景高帧率15-20 FPS比超高分辨率更重要。SVGA (800x600) 15fps 的体验通常比 UXGA (1600x1200) 5fps 要好得多而且网络和CPU压力小一个数量级。PSRAM是生命线没有PSRAM多客户端MJPEG流几乎不可行。务必购买带PSRAM的型号并在代码中正确启用它。监控系统资源在代码中添加简单的资源监控日志定期打印FreeRTOS的剩余堆内存、最小堆栈水位线等。这能帮你提前发现内存泄漏或堆栈溢出问题。散热考虑ESP32-CAM满负荷运行时芯片会发热。如果放在密闭空间长期运行可能导致不稳定。可以考虑添加一个小散热片。电源隔离如果摄像头用于监控电机、继电器等设备强烈的电源干扰可能导致ESP32重启或图像异常。使用独立的电源适配器或者为ESP32-CAM添加电源隔离模块。这个项目把ESP32-CAM这个小硬件的潜力挖掘得很深。经过合理的参数调优和问题规避它完全可以作为一个稳定、低成本的无线视频流源服务于各种有趣的物联网和智能家居项目。最关键的是理解其“生产-消费”多任务模型然后根据你的具体应用场景客户端数量、网络环境、对画质和延迟的要求去调整那些核心参数找到最适合的平衡点。