简介:面向需要在 Windows 32 位环境下集成 Chromium 内核的桌面客户端开发者,提供一份已编译完成、可直接使用的 CEF Release 包,对应 CEF 81.2.24 与 Chromium 81.0.4044.113,并已开启 H.264 编码支持,适用于内嵌浏览器、音视频播放等场景。压缩包共 1514 个文件、约 223.51MB,包含 483 个 .h 头文件、327 个 .obj 与 312 个 .cc 编译中间产物与源码,58 个 .pak 资源包、14 个 .dll 与 8 个 .lib 构成可直接调用的二进制,另附 CMake 与 vcxproj 工程配置。作者按 rar 结构编译、以 zip 打包便于解压,解压后可直接定位核心动态库、pak 资源与头文件目录,省去从零拉取 Chromium 源码编译的漫长流程;已有 968 人学习下载。
1. 版本先对齐:81.0.4044.113 这套 x86 Release 包不是解压就能用
第一次接 cef chromium h264 这个组合,很多人以为是「解压、拷 dll、双击跑起来」三步走。真实情况是:一个只拷贝了 libcef.dll 的工程,双击后窗口闪一下就没影,事件查看器里只剩一条 0xc0000005 或者 0xc000007b。81.0.4044.113 这个版本号本身就带着信息——Chromium 81 分支、x86 三十二位、Release 构建、编进了 proprietary codecs,H.264 和 AAC 能解,但前提是你主机程序的位数、运行库、平台工具集全对上。装不好的表现不是弹个错框那么客气,而是白屏、子进程反复重启、mp4 黑屏只剩声音。这套环境适合两类人:给存量三十二位桌面软件嵌浏览器的,和必须在 Win7 SP1 上稳定放视频的。
2. 环境核对与包体落地:VS 工具集、x86 目标和目录布局
先把环境列清楚再动手,能省掉后面一半的排查时间。81 分支的 CEF 官方支持 VS2017 15.7 以上和 VS2019,平台工具集用 v141 或 v142 都行,Windows SDK 建议 10.0.17763 以上。系统侧,Chromium 81 是还能在 Win7 SP1 上跑的分支(110 之后才彻底断掉),但 Win7 上要确认装了 KB2533623 之类的补丁,否则 LoadLibraryEx 的标志位会失败。
2.1 主机工程的三项硬指标:平台工具集、字符集与 C++ 标准
为什么强调「三项」?因为这三项里任意一项错位,链接期和运行期给出的报错完全不指向真正原因。平台工具集错了,头文件里std::的 ABI 可能对不上;字符集错了,CefString到std::wstring的转换会出乱码到怀疑人生;C++ 标准低了,wrapper 头文件直接编译不过。
我一般把这些在项目属性里一次性钉死,再同步写进 CMakeLists,避免换机器重建时被默认值带跑:
| 属性页位置 | 配置项 | Release x86 取值 |
|---|---|---|
| 配置属性 → 常规 | 平台工具集 | Visual Studio 2017 (v141) 或 2019 (v142) |
| 配置属性 → 高级 | 字符集 | 使用 Unicode 字符集 |
| C/C++ → 语言 | C++ 语言标准 | ISO C++17 (/std:c++17) |
| C/C++ → 代码生成 | 运行库 | 多线程 DLL (/MD) |
| 链接器 → 系统 | 子系统 | Windows (/SUBSYSTEM:WINDOWS) |
| 清单工具 → 输入和输出 | DPI 识别 | 每个监视器高 DPI |
| 链接器 → 清单文件 | 生成清单 | 是 |
对应到 CMake 的写法是这样:
cmake_minimum_required(VERSION 3.15) project(cef81_host CXX) # CEF_ROOT 指向解压出来的 cef_binary_81.0.4044.113 根目录 set(CEF_ROOT "${CMAKE_SOURCE_DIR}/third_party/cef81") add_executable(cef81_host WIN32 src/main.cpp) # 关键点一:位数必须和包里的 libcef.dll 一致,x86 包配 Win32 平台 target_include_directories(cef81_host PRIVATE "${CEF_ROOT}") # 关键点二:C++ 标准拉到 17,81 分支的 wrapper 头文件已经用到了较新的语言特性 target_compile_features(cef81_host PRIVATE cxx_std_17) # 关键点三:Unicode + 开沙箱宏,_ITERATOR_DEBUG_LEVEL 由 /MD + Release 自然对齐 target_compile_definitions(cef81_host PRIVATE WIN32 _WINDOWS UNICODE _UNICODE NOMINMAX CEF_USE_SANDBOX) target_link_directories(cef81_host PRIVATE "${CEF_ROOT}/Release") target_link_libraries(cef81_host PRIVATE libcef cef_sandbox)WIN32告诉链接器按窗口程序处理,不给控制台;CEF_USE_SANDBOX是开关,只有定义了这个宏,CEF 才会去调沙箱初始化并找cef_sandbox.lib。把CEF_USE_SANDBOX注释掉也能编过,但那样浏览器进程是裸奔的,渲染进程的隔离级别掉一档,正式包不建议这么干。
2.2 解压后的目录布局:哪些文件必须跟 exe 同级
CEF 的二进制分发包有一个非常不友好的特点:文件散落在Release/、Resources/、Release/locales/、Release/swiftshader/四个位置,而运行时它期望这些东西全部平铺在你的 exe 旁边。很多人「装完打不开」就是把Release/里的 dll 拷了,忘了Resources/和locales/。
踩过几次之后我整理了一份清单,交付前逐项对:
| 文件或目录 | 作用 | 缺了会怎样 |
|---|---|---|
| libcef.dll | 核心运行时 | 启动直接 0xc0000135,进程起不来 |
| chrome_elf.dll | 早期加载的兼容层 | 在 CefInitialize 之前就崩 |
| libcef.lib | 链接期导入库 | 编译期 LNK2019 一堆未解析符号 |
| cef_sandbox.lib | 沙箱静态库 | 开了 CEF_USE_SANDBOX 却链接失败 |
| icudtl.dat | ICU 数据(排序、编码转换) | 中文乱码,或初始化阶段断言失败 |
| v8_context_snapshot.bin | V8 上下文快照 | 首屏变慢甚至直接崩(早期分支叫 snapshot_blob.bin、natives_blob.bin) |
| resources.pak、cef.pak、cef_100_percent.pak、cef_extensions.pak | 界面资源包 | 白屏、无样式、滚动条不见 |
| locales/*.pak | 语言包,至少 zh-CN.pak 与 en-US.pak | 界面退回英文,或报 locales 目录无效 |
| libEGL.dll、libGLESv2.dll、d3dcompiler_47.dll | ANGLE 与着色器编译 | 无独显的机器黑屏 |
| swiftshader/、vk_swiftshader.dll | 软件渲染兜底 | GPU 进程反复重启,页面空白 |
提示:文件名以你实际解压出来的为准。81 分支不同小版本之间
v8_context_snapshot.bin与snapshot_blob.bin有过交替,按包里有的拷,别照着老文章硬凑。
拷贝这一步我不用手点,写个脚本既快又能当交付检查:
#!/usr/bin/env bash # 把 CEF 分发包平铺到 exe 同级目录,并做一次完整性校验 set -u CEF_ROOT="${1:-./third_party/cef81}" APP_DIR="${2:-./bin/Release}" mkdir -p "$APP_DIR/locales" "$APP_DIR/swiftshader" # Release 下的 dll / dat / bin 平铺过去 cp -f "$CEF_ROOT/Release/"*.dll \ "$CEF_ROOT/Release/"*.dat \ "$CEF_ROOT/Release/"*.bin \ "$CEF_ROOT/Release/"*.pak "$APP_DIR/" 2>/dev/null # Resources 下的 pak 也必须在根目录 cp -f "$CEF_ROOT/Resources/"*.pak "$APP_DIR/" 2>/dev/null cp -f "$CEF_ROOT/Resources/"*.bin "$APP_DIR/" 2>/dev/null # 语言包只留中英,少一堆文件也少一堆更新负担 cp -f "$CEF_ROOT/Resources/locales/zh-CN.pak" "$APP_DIR/locales/" cp -f "$CEF_ROOT/Resources/locales/en-US.pak" "$APP_DIR/locales/" # 软渲染兜底目录 cp -rf "$CEF_ROOT/Release/swiftshader/." "$APP_DIR/swiftshader/" 2>/dev/null echo "--- 校验 ---" for f in libcef.dll chrome_elf.dll icudtl.dat cef.pak resources.pak locales/zh-CN.pak; do [[ -e "$APP_DIR/$f" ]] && echo "OK $f" || echo "MISS $f" done脚本里的2>/dev/null是为了容忍不同小版本之间文件名差异,比如没有vk_swiftshader.dll时不至于整条命令中断。最后那段校验输出比记性可靠,MISS一行都别放过去。注意locales只留两个语言包是有意的:每个.pak都要占地址空间,三十二位进程最缺的就是地址空间。
2.3 链接配置:libcef.lib、cef_sandbox.lib 与附加依赖项
libcef.lib是导入库,负责把CefInitialize、CefExecuteProcess这些符号指向libcef.dll;cef_sandbox.lib是静态库,负责沙箱的进程间通信初始化。两者角色不同,缺一不可。VS 里在「链接器 → 输入 → 附加依赖项」填libcef.lib;cef_sandbox.lib;,在「附加库目录」填$(CEF_ROOT)\Release。
有一个容易被忽略的细节:如果你把libcef.dll放在 exe 同目录之外的路径(比如runtime/),要么改PATH,要么在清单里声明。CEF 不会自己去找,Windows 的 DLL 搜索顺序也不认你的工程目录结构。
注意:不要试图用
/DELAYLOAD:libcef.dll来省启动时间。CEF 在静态初始化阶段就会用到部分符号,延迟加载大概率换来一个更难查的启动崩溃。
3. 初始化链路:CefExecuteProcess 和 CefInitialize 的先后顺序
如果只能记住一件事,那就是这个顺序:CefExecuteProcess必须在CefInitialize之前,而且必须在主进程和子进程里都调用。CEF 用一个可执行文件同时承担浏览器进程、渲染进程、GPU 进程、Utility 进程四种角色,靠命令行里的--type=区分。主机程序自己不解析这个参数,交给CefExecuteProcess,它会判断当前是不是子进程——是就执行完退出,不是就返回 -1 让你继续往下走。
3.1 入口函数的四段式骨架
我写 CEF 主机的main一直是四段式,不加花活。骨架如下:
#include "include/cef_app.h" #include "include/cef_command_line.h" int main(int argc, char* argv[]) { // 第一段:构造 CefMainArgs。Windows 下传当前模块句柄即可 CefMainArgs main_args(::GetModuleHandle(nullptr)); CefRefPtr<CefApp> app(new MyCefApp()); // 第二段:让 CEF 先判断自己是不是子进程(--type=renderer/gpu/utility) // 子进程在这里执行完就返回 >=0,主进程返回 -1 继续往下 int exit_code = CefExecuteProcess(main_args, app.get(), nullptr); if (exit_code >= 0) { return exit_code; } // 第三段:主进程。先声明 DPI 感知,再填 settings,再初始化 CefEnableHighDPISupport(); CefSettings settings; FillSettings(settings); // 见 3.2 if (!CefInitialize(main_args, settings, app.get(), nullptr)) { return 1; // 初始化失败,直接退出,日志在 debug.log 里 } // 第四段:建主窗口、跑消息循环、退出时收尾 CreateMainWindow(); CefRunMessageLoop(); CefShutdown(); return 0; }第二段的if (exit_code >= 0) return exit_code;是整个骨架里最不能省的两行。省掉的后果很典型:浏览器进程能起来,窗口也能显示,但渲染进程一启动就被当成了新的浏览器实例,于是你又开了一个窗口,再启动渲染进程,再开一个……任务管理器里进程数指数级增长,几十秒后整机卡死。
CefEnableHighDPISupport()要放在CefInitialize之前。它的作用是在 Chromium 初始化之前设置进程的 DPI 感知上下文,放后面调等于没调,界面会糊成一团。
3.2 CefSettings 里必须改的五个字段
CefSettings有几十个字段,全填是给自己找麻烦。Release 交付前我只会动这五个,其余保持默认:
void FillSettings(CefSettings& settings) { // 1) 子进程路径:指向自己这个 exe,最稳 CefString(&settings.browser_subprocess_path) .FromString(GetExeDir() + L"\\cef81_host.exe"); // 2) 资源目录与语言目录:绝对路径,不要依赖当前工作目录 CefString(&settings.resources_dir_path).FromString(GetExeDir()); CefString(&settings.locales_dir_path).FromString(GetExeDir() + L"\\locales"); CefString(&settings.locale).FromString("zh-CN"); // 3) 缓存目录:写进用户目录,别写程序目录(Program Files 无写权限) CefString(&settings.cache_path) .FromString(GetUserDataDir() + L"\\cef81_cache"); // 4) 日志:排错期 INFO,正式包退回 WARNING 并给日志文件落地 settings.log_severity = LOGSEVERITY_WARNING; CefString(&settings.log_file).FromString(GetUserDataDir() + L"\\cef_debug.log"); // 5) 循环模型:单线程,UI 线程自己跑 CefRunMessageLoop settings.multi_threaded_message_loop = false; settings.no_sandbox = false; // 正式包保留沙箱;定位问题时临时 true settings.remote_debugging_port = 0; // 调试端口只在内部构建里开 }第一个字段browser_subprocess_path值得多说一句。它允许你指定「子进程用哪个 exe 启动」,填自己的路径最直观。如果你的主程序体积很大、启动很慢,常见做法是单独编一个什么都不做的subprocess.exe,只链 CEF,专门用做子进程宿主,主程序启动能快一截。
第四个字段的日志是排错的黑匣子。CEF 自己不打日志的话,白屏了你只能猜。cef_debug.log里会写 GPU 进程为什么起不来、哪个 pak 没找到、沙箱初始化失败在哪一步。
3.3 消息循环:CefRunMessageLoop 与 CefDoMessageLoopWork 的取舍
multi_threaded_message_loop = false走的是「CEF 借用你的 UI 线程」模式,你调CefRunMessageLoop()阻塞在那里,CEF 内部消息和 Windows 消息一起泵。这种模式代码最简单,也最不容易出线程问题,缺点是主线程被占住,如果你原来有一大坨 MFC 或 Qt 的消息循环,两边会打架。
multi_threaded_message_loop = true则是 CEF 自己起一个 UI 线程,你原来怎么跑消息循环还怎么跑,只需要在退出时调CefShutdown()。这种模式适合往存量软件里嵌浏览器的场景。两种模式的启动成本差不多,真正影响体验的是CefDoMessageLoopWork()——那是「外部消息循环模式」下的手动泵,只在CefSettings.external_message_pump = true时才用。三十二位进程里我不建议开这个,手动泵的调用频率控制不好,页面会一顿一顿的。
// 多线程循环模式下的退出顺序,顺序错了会挂死在 CefShutdown void OnMainWindowClose() { CefQuitMessageLoop(); // 先让 CEF 退出自己的循环 // 主窗口的 DestroyWindow 走正常流程 // 进程退出前最后一步 CefShutdown(); // 必须在所有 CefRefPtr 释放之前 }CefShutdown()的位置是所有崩溃里最隐蔽的一类。它必须在所有CefRefPtr<CefBrowser>、CefRefPtr<CefApp>析构之前调用。如果某个全局变量还持有着浏览器对象的引用,CefShutdown之后的析构就会访问已释放的内存,表现是「关窗口时偶尔崩」,而且只在慢机器上复现。把持有点改成Release()显式释放,是常见的规避办法。
4. H.264 落地:从 canPlayType 到 mp4 实际播放
用了带 proprietary codecs 的包,不等于视频就一定能播。Chromium 里跟视频相关的链路至少有三段:容器与编解码器是否注册、MSE(Media Source Extensions)能不能用、渲染路径有没有被 GPU 黑名单拦住。这三段任意一段断掉,表现都是「不出画面」,但原因完全不同。
4.1 编码能力自检:三步确认 proprietary codecs 生效
标准发行版(不带专有编解码器的)里,video/mp4的canPlayType会返回空字符串;带 proprietary codecs 的构建会返回"probably"。这是最省事的判断依据。三步走:
第一步,确认版本号。编译期看宏,运行期看 UA:
#include "include/cef_version.h" #include <cstdio> void PrintCefVersion() { // 编译期常量:CEF_VERSION 是完整串,CHROME_VERSION_MAJOR 是主版本号 printf("CEF_VERSION = %s\n", CEF_VERSION); printf("CHROME_VERSION = %d.%d.%d.%d\n", CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR, CHROME_VERSION_BUILD, CHROME_VERSION_PATCH); // 81 分支期望看到 81.0.4044.113 }第二步,页面里跑一段探测脚本,把结果打回宿主:
// codec_probe.html —— 在 CEF 里加载这个页面,把结果回传给宿主 (function () { const cases = [ 'video/mp4; codecs="avc1.42E01E"', // H.264 Baseline 'video/mp4; codecs="avc1.4D401E"', // H.264 Main 'video/mp4; codecs="avc1.64001E"', // H.264 High 'video/mp4; codecs="mp4a.40.2"', // AAC-LC ]; const probe = document.createElement('video'); const result = {}; for (const t of cases) { // 返回值只有 '', 'maybe', 'probably' 三种,probably 才是确定支持 result[t] = probe.canPlayType(t); } result['mse'] = window.MediaSource ? 'available' : 'missing'; result['ua'] = navigator.userAgent; console.log('CODEC_PROBE=' + JSON.stringify(result)); })();第三步,把console消息接出来。在CefDisplayHandler::OnConsoleMessage里截获CODEC_PROBE=前缀的那一条,落日志或者弹到宿主界面。判断标准很简单:三个avc1全是probably,mp4a也是probably,mse是available,这条链就算通。maybe基本等于不支持——它表示「容器格式我认识,里面的编码器不一定」。
提示:探测页面别用远程 url,用本地
file://或者data:加载,省掉网络因素,结果才干净。
4.2 页面侧与请求侧的配合:MSE、CORS 与本地文件
能解 H.264 是一回事,页面能不能顺利拿到视频流是另一回事。三条常见的断点:
一是本地file://加载 mp4 被拦。Chromium 对file://的同源策略越来越紧,<video src="file:///...">在部分配置下会直接报 Not allowed to load local resource。做法是把视频目录加进CefSettings的允许列表,或者干脆起一个本地的小 HTTP 服务喂数据。
二是 MSE 分段流的 MIME 类型写错。SourceBuffer的type必须精确到video/mp4; codecs="avc1.64001F"这种粒度,写成video/mp4会抛NotSupportedError。用 fMP4 分段的话,还要确认包的构建带了对应 profile 的解码器——High profile 在 81 分支上没问题,但 10bit 的avc1.6E001F就不一定了。
三是跨域请求被拦在 preflight。如果你的视频走 HTTP,页面用fetch拿分片,服务端没回Access-Control-Allow-Origin,控制台会红一片但视频元素本身不报错,看起来就是「一直转圈」。这条在嵌入第三方播放器时最常见。
// 允许本地文件来源,避免 file:// 被同源策略拦掉 class MyRequestHandler : public CefRequestHandler { public: bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request, bool user_gesture, bool is_redirect) override { // 只在内部工具里放开;面向公网的宿主程序不要这么写 return false; } };4.3 视频不播时的三级定位:网络、解码、渲染
不播的时候不要一上来就怀疑编解码器。我的排查顺序固定是三级。
第一级看网络:打开 DevTools(remote_debugging_port临时开一下),Network 面板里看分片请求是不是 200。如果请求压根没发出去,问题在页面逻辑或者 CSP,不在 CEF。
第二级看解码:控制台里MediaSource.isTypeSupported('video/mp4; codecs="avc1.64001E"'),返回false就说明这个包的编码器注册有问题,回去查包是不是真的带了 proprietary codecs。这一级还能用chrome://media-internals辅助,看decoder一栏是不是FFmpegVideoDecoder。
第三级看渲染:chrome://gpu里看 Video Decode 是不是Hardware accelerated。如果是Software only且 CPU 占用很高,可能只是软解跑不动高码率,不是不支持。这时候要么降码率,要么在CefSettings里把 GPU 相关的开关调整一下,用--disable-gpu-compositing之类的命令行参数试。
5. 避坑与排查:五类上线前的翻车现场
下面五条全是我在真实交付里撞过的,按「现象 → 原因 → 解决」记,遇到对号入座就行。
5.1 启动期三类崩法:位数、CRT 与子进程
第一条,双击没反应,事件查看器 0xc000007b 或提示「不是有效的 Win32 应用程序」。
现象:编译一切正常,拷到测试机双击,进程瞬间消失,没有任何对话框。
原因:位数混用。x86 的主机 exe 配了 x64 的libcef.dll,或者反过来。也有一种隐藏情况:exe 是 Win32,但swiftshader/目录里拷进去的是 x64 的 dll,加载软渲染时才炸。
解决:dumpbin /headers libcef.dll | findstr machine,x86 应该显示14C machine (x86)。把这个检查写进交付脚本,别靠眼看。
第二条,编译报 LNK2038:_ITERATOR_DEBUG_LEVEL不匹配,或者RuntimeLibrary值冲突。
现象:一链接就红一片,报某个.obj与libcef.lib的_ITERATOR_DEBUG_LEVEL一个 0 一个 2。
原因:主机工程用了 Debug 配置(/MDd),而去链接的是 Release 版 CEF 包(/MD)。CEF 的 Release 包里的libcef.lib只提供 Release 运行库的符号,Debug 主机跟它对不上。
解决:把主机配置切到 Release,或者至少把「C/C++ → 代码生成 → 运行库」从/MDd改成/MD。别试图用/NODEFAULTLIB强压,压过去之后运行期堆会以更难查的方式崩。
第三条,窗口显示出来了,但任务管理器里进程数疯涨,几十秒后整机无响应。
现象:主窗口能开,几秒后卡死,进程列表里同名 exe 出现十几个。
原因:main里没调CefExecuteProcess,或者调了但没判断返回值。CEF 启动渲染进程时,命令行里带着--type=renderer,主机不处理这个参数,就把它当成第二个浏览器实例启动,形成无限递归。
解决:严格按 3.1 的四段式骨架,CefExecuteProcess的返回值>= 0时立刻return。
5.2 运行期两类异常:白屏与视频黑屏
第四条,页面白屏,cef_debug.log里反复出现Failed to load resource或者Could not find locales。
现象:浏览器壳子起来了,地址栏能输入,页面区域纯白,右键菜单能弹。
原因:resources.pak、cef.pak、cef_100_percent.pak这几个资源包没跟 exe 放一起,或者locales目录路径写成了相对路径,而进程的当前工作目录不是 exe 目录(从快捷方式启动和从命令行启动,工作目录不一样,这是玄学来源之一)。
解决:所有路径都用GetModuleFileName推出 exe 目录再拼绝对路径,绝不写./locales。
第五条,mp4 黑屏但有声音,或者控制台canPlayType返回"maybe"。
现象:视频元素显示了时长、能拖进度、有声音,画面区域全黑。
原因:两种可能。一是这个包并不是真的带 proprietary codecs,只是版本号看起来对,H.264 视频轨解不了但 AAC 音频轨能解,于是只剩声音。二是解得了但渲染路径挂了,GPU 进程被黑名单拦住又没启用软渲染。
解决:先按 4.1 的三步自检确认avc1三项都是probably;确认是渲染问题后,检查swiftshader/目录是否完整、libEGL.dll和libGLESv2.dll是否同位数,必要时用--disable-gpu命令行参数验证一下这个假设。
6. 装完怎么验证:一套照着跑的自检清单
装完不等于装好。CEF 这类组件的麻烦在于「大部分功能正常、个别能力缺失」时不会报错,只会在用户那边以「视频放不了」的形式暴露。我给这套 81.0.4044.113 的 x86 Release 包配了一份自检流程,交付前固定走一遍。
第一步,版本与位数对齐。宿主程序里打印CEF_VERSION和CHROME_VERSION_MAJOR,期望81.0.4044.113。同时在CefDisplayHandler::OnConsoleMessage里截一次navigator.userAgent,看 UA 里是不是Chrome/81.0.4044.113。这两个值对不上,说明你加载的libcef.dll不是你以为的那个版本——多版本共存时这是高频翻车点。
第二步,资源完整性。用前面那份bash校验脚本跑一次,MISS必须为零。这一步比看日志快得多。
第三步,编解码能力。加载 4.1 里的探测页面,三条判定:
| 检查项 | 期望值 | 不达标的含义 |
|---|---|---|
canPlayType('video/mp4; codecs="avc1.42E01E"') | probably | 包不带 proprietary codecs |
canPlayType('video/mp4; codecs="mp4a.40.2"') | probably | 音频轨也缺失 |
MediaSource存在 | available | 无法走分段流播放 |
UA 含Chrome/81.0.4044.113 | 是 | 加载了错误的 dll |
第四步,真播一次。别只测canPlayType,找一个 720p、H.264 High profile、带 AAC 的 mp4,用<video>播 10 秒,同时盯任务管理器:CPU 应该在 15% 以内(软解会高很多),GPU 进程内存稳定不涨。播完拖动进度条来回三次,如果拖动时黑屏超过 1 秒,说明解码缓存没配好。
第五步,压一压三十二位的老毛病。x86 进程的用户态地址空间只有 2GB(开了大地址感知也就 3GB 左右),页面开着、视频播着、再开三个标签,观察内存。超过 1.2GB 就该警惕了,CefSettings里把persist_session_cookies关掉、缓存目录定期清,都能省一点。
第六步,关窗口。这一步最容易被跳过。反复开关主窗口二十次,看有没有偶发崩溃。如果有,八成是CefShutdown()之前还有CefRefPtr没释放,按 3.3 里的做法把持有点显式Release()。
注意:这套自检要在 Win7 SP1 和 Win10 各跑一遍。Chromium 81 是横跨这两个系统的分支,同一份包在两边表现不一致的情况我遇到过不止一次,尤其是 GPU 那一段。
从那以后我每次交付 CEF 相关的包,都会把这份清单从头到尾走一遍,尤其是dumpbin看位数和探测页面看probably这两步——它们花不到两分钟,却能挡掉八成返工。这套已经编译好的 cef chromium 81.0.4044.113 x86 Release 包我放在资源位了,按上面的目录结构铺开、把CefSettings五个字段对齐,基本就是解压即用。希望帮到你。
本文还有配套的精品资源,点击获取