Linux CEF 离屏渲染详解
1.下载linux版本的cef源码
下载地址:https://cef-builds.spotifycdn.com/index.html 下载对应的源码
tar -xjvf 文件名.tar.bz2 // 解压到当前文件夹
2. 编译cef
1、自行安装cmake make gcc g++ gdb等 工具 2、首先 cmake 编译 cef文件夹内的CMakeLists.txt 文件生成 MakeFile 文件 3、再用make 编译 MakeFile 文件 若出现某些lib找不到的问题 可以 通过 apt-get install xxx.dev 来进行安装或者yum install xxxx 安装解决, 可自行百度,然后重新继续编译 编译 首先会编译libcef_dll_wrapper这个文件夹 主要生成libcef_dll_wrapper.a文件 自动编译/tests/ceftests 文件夹 自动编译 /tests/cefsimples/ 自动编译 /tests/cefclient/ 我在编译这个文件夹时 就出现include<gtk/gtk.h>找不到 安装了gtk也不行 ,也不太清楚, 不过离屏渲染只需要cefsimple这个例子就能实现, 就没再深究。 4.完成编译后,会在libcef_dll_wrapper 文件夹内生成 libcef_dll_wrapper.a 文件 , /tests/cefsimple/ReleaseorDebug 生成CefSimple 可执行文件, 可以尝试执行cefsimple 文件 进入当前文件夹内./cefsimple 这时会发现出错了, 经历痛苦的寻找发现需要加上--no-sandbox 也就是 ./cefsimple --no-sandbox 没有报错了可以出现一个窗体了但是加载不出来网页,是因为源码里的网页是google.com 没有科学上网是不行的,所以我们要改源码,将simple_app.cc 文件里的 url 改为 url = "http://www.baidu.com";再次执行就能显示出百度的简单网页窗口了。
3. 自己实现简单的离屏渲染
1.在linux 下编码 我开始准备用vscode 进行编码,但是当我将cef 头文件加载进来时发现,vscode 有个问题导致编译无法进行,网上也没找到方法解决, 简单来说就是,vscode 是以文件夹来作为代码的起始目录, 当我在文件夹内再创建一个文件夹include, 在include文件夹内创建两个 test1.cpp 和test2.cpp, 当test2.cpp需要引入test1.cpp时 需要加入头文件 此时如果
#include “include/test1.cpp” 就会出错, 必须变成 #include "test1.cpp" 就不会出错, 而最外层的main.cpp 引入 test1.cpp 就要加上 #include”include/test1.cpp" 才行, 我感觉 vscode 是每个文件 都会有一个对应路径 需要按照每个文件的路径做参考系 来包含所有的文件,导致我加载cef的include文件夹时, 里面需要大量改动文件头路径 太麻烦所以放弃了。
2、所以采用第二种方法QtCreater,直接下载QtCreater
自行搜索 Qt教程 有详细的Qt下载和linux安装教程
新建一个Qt 控制台应用,我这里工程取名cef_code
我们要把之前 cef 工程文件下的Debug 文件夹 Release文件夹 include文件夹 Resources 文件夹复制到qt当前项目路径中
复制到
然后将cef文件夹 里的 libcef_dll_wrapper文件夹里的libcef_dll_wrapper.a 文件复制到qt文件夹里的Debug 和Release 文件夹内,此时资源文件和cef头文件和 动态库全部配置完毕。
打开工程, 在main.cpp中代码如下
#include <QCoreApplication>
#include "include/cef_app.h" //! cef 头文件
#include "include/cef_browser.h"
#include "include/cef_client.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
// ! 保存成bmp 文件 需要的bmp 文件头信息
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef struct tagBITMAPFILEHEADER{
WORD bfType; //Linux此值为固定值,0x4d42
DWORD bfSize; //BMP文件的大小,包含三部分
WORD bfReserved; //置0
WORD bfReserved2; //保留,0
DWORD bfOffBits; //文件起始位置到图像像素数据的字节偏移量
}__attribute__((packed)) BITMAPFILEHEADER;
//__attribute__((packed))
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //文件信息头的大小,40
DWORD biWidth; //图像宽度
DWORD biHeight; //图像高度
WORD biPlanes; //BMP存储RGB数据,总为1
WORD biBitCount; //图像像素位数,笔者RGB位数使用24
DWORD biCompression; //压缩 0:不压缩 1:RLE8 2:RLE4
DWORD biSizeImage; //4字节对齐的图像数据大小
DWORD biXPelsPerMeter; //水平分辨率 像素/米
DWORD biYPelsPerMeter; //垂直分辨率 像素/米
DWORD biClrUsed; //实际使用的调色板索引数,0:使用所有的调色板索引
DWORD biClrImportant;
}__attribute__((packed)) BITMAPINFOHEADER;
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
//! 保存 bmp 函数
static void SaveImage(int width, int height, const void* buffer, const char* path)
{
//Set BITMAPINFOHEADER
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = -height;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = 0;
bi.biSizeImage = ((width*height) <<2);
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
//Set BITMAPFILEHEADER
BITMAPFILEHEADER bf;
bf.bfType = 0x4d42;
bf.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + bi.biSizeImage;
bf.bfReserved = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
FILE* fp;
if((fp = fopen(path, "wb")) == NULL)
return;
fwrite(&bf,sizeof(BITMAPFILEHEADER),1,fp); //写入文件头
fwrite(&bi,sizeof(BITMAPINFOHEADER),1,fp); //写入信息头
fwrite(buffer,bi.biSizeImage,1,fp); //写入图像数据
fclose(fp);
}
//提供一个CefClinet子类处理某个浏览进程的回调, 要实现离屏渲染 必须 继承 CefRenderHandler类 实现GetViewRect 和 OnPaint 方法
class MyClient :public CefClient, public CefLifeSpanHandler, public CefRenderHandler
{
public:
MyClient():count(0){};
virtual ~MyClient()override {};
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler()override {
return this;
}
virtual CefRefPtr<CefRenderHandler> GetRenderHandler()override {
return this;
}
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser)override {
CefQuitMessageLoop();
}
virtual void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect)override
{
// 设置渲染图片的宽高
rect.x = rect.y = 0;
rect.width = 1920;
rect.height = 1080;
}
virtual void OnPaint(CefRefPtr<CefBrowser> browser,
PaintElementType type,
const RectList& dirtyRects,
const void* buffer,
int width,
int height)override
{
// width 图片宽 height 图片高 buffer 图片数据
count++;
char num[100];
snprintf(num, 100, "/home/leizhang/Documents/picture/%d.bmp", count);
SaveImage(width, height, buffer, num);
}
private:
int count;
IMPLEMENT_REFCOUNTING(MyClient);
};
//提供一个CefApp子类处理某个进程的回调
class MyApp :public CefApp, public CefBrowserProcessHandler
{
public:
virtual ~MyApp()override{};
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()override {
return this;
}
virtual void OnContextInitialized() override {
CEF_REQUIRE_UI_THREAD();
CefWindowInfo window_info;
CefRefPtr<MyClient> client = new MyClient();
// 设置 无窗口渲染模式
window_info.SetAsWindowless(0);
//window_info.SetAsPopup(NULL, "SIMPLE");
//window_info.SetAsChild(m_hwnd, m_rect);
CefBrowserSettings settings;
// 设置离屏渲染帧率
settings.windowless_frame_rate = 60;
// 设置网址url
CefString url = "http://www.baidu.com";
// 调用CefBrowserHost::CreateBrowser()函数创建浏览进程实例并使用CefLifeSpanHandler来管理浏览生命周期
CefBrowserHost::CreateBrowser(window_info, client, url, settings, nullptr, nullptr);
}
private:
IMPLEMENT_REFCOUNTING(MyApp);
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//每个CEF3子进程运行时使用运行行来指定配置信息并通过CefMainArgs结构传递给CefExecuteProcess函数,CefMainArgs结构是跨平台的。
CefMainArgs main_args(argc, argv);
//当单可执行体时行时,入口函数CefExecuteProcess在不同进程类型之间需要区分。单可执行体结构只支持window和linux,不支持macos
int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
if (exit_code >= 0)
{
return exit_code;
}
CefSettings settings;
settings.no_sandbox = true;
//settings.multi_threaded_message_loop = true; window有效 linux无效,linux 不能打开这个 否则可能会无法运行
// 离屏渲染模式开启
settings.windowless_rendering_enabled = true;
settings.log_severity = LOGSEVERITY_DISABLE;//日志
auto myApp = CefRefPtr<MyApp>(new MyApp());
// 提供一个入口函数以初始化CEF和运行每个子进程逻辑和CEF消息处理
CefInitialize(main_args, settings, myApp.get(), nullptr);
// 开启cef 消息循环
CefRunMessageLoop();
//a.exec();
CefShutdown();
return 0;
}
想要实现离屏渲染 提供一个CefClient 子类 要继承CefRenderHandler类主要提供方法有:
1.CefRenderHandler::GetViewRect()函数获取想要获取视图的矩阵。// 必须实现
2.CefRenderHandler::OnPaint()被调用,以提供一个无效区域和更新的像素buffer。CefClient应用程序使用OpenGL绘制缓冲。// 必须实现
3.调用CefBrowserHost::WasResized()重置浏览器大小。这将导致调用GetViewRect()来检索新尺寸随后调用OnPaint()。
4.CefBrowserHost::SendXXX()函数通知浏览进程的鼠标、键盘和焦点事件
5.CefBrowserHost::CloseBrowser()销毁浏览器
4. 编译代码
完成简单的代码编写后,需要加入动态链接库
打开cef_code.pro 文件添加
LIBS += $$PWD/Debug/libcef.so LIBS += $$PWD/Debug/libcef_dll_wrapper.a
必须添加否则会报错, 编译完成后, 调试发现会提示libcef.so 文件找不到
这里也是比较头疼,于是我把qt项目当前目录下的Debug里的所有文件 都包含的有libcef.so 文件放到 可执行文件目录下 也提示libcef.so无法找到, 于是我将libcef.so 文件放到/usr/lib/ 中 终于不提示报错了,然后我想 通过vim ~/.bashrc 在里面加入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Debug
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Release
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Resources
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Debug/locales
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Debug/swiftshader
把各种加载资源文件和动态库目录都导入带bashrc 文件中, 并把/usr/lib/ 得libcef.so 删除 再次运行 还是报libcef.so 文件找不到,到这里我已经不知道怎么办了,于是我将libcef.so 还是拷贝到/usr/lib/ 系统目录下,能找到文件了,但是调试到CefInitialize 这一步又崩溃了,应该是资源文件没有添加,于是我把qt项目目录下的Resource文件夹里所有资源全部拷贝到可执行文件当前目录下,调试还是崩溃,应该还是调试的时候资源文件没有加载到导致的,但是我又不知道资源文件应该加载到哪里。。 网上也基本没有这种解决办法,暂时无解。
然后我把/usr/lib/ 里的libcef.so 文件删除,
准备直接在生成的可执行文件的目录下 用命令行执行 试试看
报这个什么libva error 于是又在网上找 好像没装libva 库 然后安装后还是不行,但是我发现当我点退出的时候说我还有进程未退出,代表着可能还在运行, 我进入生成图片的文件夹内部发现竟然生成了 网页图片,我猜测 直接执行的话 它能找到当前目录的可执行文件、资源文件和动态链接库,于是我再次把bashrc 里 之前导入的文件夹路径删除,再次实验无法执行报错,发现动态链接库还是需要在bashrc文件里指定路径
总结:1. linux Qt调试时 将libcef.so 放到 /usr/lib 下才能运行,网上说在bashrc 文件指定动态库目录路径也可以,但是我这不行不知道为啥
指定路径为 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库文件夹路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Debug
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/leizhang/Documents/qt_code/cef_code/Release
2. linux Qt 可以调试之后 在CefInitialize 初始化失败 可能是调试时 Resource文件.pak等文件 没有加载进来, 暂时也不太清楚这个文件在哪里加载
3. linux Qt 编译成功后 可以直接在可执行文件下 用终端进行 执行, 但是要将资源的所有文件和动态链接库也就是Qt工程目录下Debug 或者Release 文件夹里libcef.so libcef_dll_wrapper.a等所有内容放到可执行文件目录下,在bashrc文件添加动态库文件夹路径,才能执行!
参考文献:
https://blog.csdn.net/cqclark/article/details/49121053
https://blog.csdn.net/CAir2/article/details/84969813
https://blog.csdn.net/hp_cpp/article/details/109864269
https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding.md