X11服务提供一种共享内存的方式来高效的捕获屏幕。
直接上代码:
头文件
#ifndef SRC_VIDEOCAPTURE_X11DESKTOPCAPTURE_H_
#define SRC_VIDEOCAPTURE_X11DESKTOPCAPTURE_H_
#include "VideoCapture/VideoCapture.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <sys/shm.h>
#include <string>
#include <vector>
namespace RemoteServer {
class X11DesktopCapture: public VideoCapture {
public:
X11DesktopCapture(std::shared_ptr<BufferQueue<unsigned char>>& buffer_queue);
virtual ~X11DesktopCapture();
void UpdateVideoCapture() final;
// bool StopVideoCapture() final;
bool DestroyVideoCapture() final;
private:
bool InitVideoCapture();
bool InitShmImages();
Display* m_display = nullptr;
Window m_desktop_window = 0;
XShmSegmentInfo* m_shm_segment_info = nullptr;
XImage* m_x_shm_image = nullptr;
int m_screen_size = 0;
};
} /* namespace RemoteServer */
#endif /* SRC_VIDEOCAPTURE_X11DESKTOPCAPTURE_H_ */
实现.cpp
/*
* X11DesktopCapture.cpp
*
* Created on: Apr 3, 2020
* Author: zou
*/
#include "X11DesktopCapture.h"
#include <cstring>
namespace RemoteServer {
X11DesktopCapture::X11DesktopCapture(std::shared_ptr<BufferQueue<unsigned char>>& buffer_queue)
{
// TODO Auto-generated constructor stub
m_buffer_queue = buffer_queue;
InitVideoCapture();
}
X11DesktopCapture::~X11DesktopCapture() {
// TODO Auto-generated destructor stub
DestroyVideoCapture();
}
bool X11DesktopCapture::InitVideoCapture(){
m_display = XOpenDisplay(NULL);
if(!m_display){
SIMLOG(SimLogger::Error, "can not connect a display");
return false;
}
m_desktop_window = RootWindow(m_display, 0);
if(!m_desktop_window){
SIMLOG(SimLogger::Error, "can not get the root window");
return false;
}
m_video_format->pixel_type = RGBA8888;
m_video_format->width = DisplayWidth(m_display, 0);
m_video_format->height = DisplayHeight(m_display, 0);
XImage* window_image = XGetImage(m_display, m_desktop_window, 0, 0, m_video_format->width, m_video_format->height, AllPlanes, ZPixmap);
m_video_format->bits_per_pixel = window_image->bits_per_pixel;
m_video_format->depth = window_image->depth;
m_screen_size = m_video_format->width * m_video_format->height * m_video_format->bits_per_pixel / 8;
SIMLOG(SimLogger::Info, "w/h/d/bpp/size: " << m_video_format->width <<"/" << m_video_format->height << "/"
<< m_video_format->depth << "/" << m_video_format->bits_per_pixel << "/" << m_screen_size);
XDestroyImage(window_image);
//create share memory image
return InitShmImages();
}
bool X11DesktopCapture::InitShmImages(){
m_shm_segment_info = new XShmSegmentInfo();
m_shm_segment_info->shmid = -1;
m_x_shm_image = XShmCreateImage(m_display,
DefaultVisual(m_display,0), // Use a correct visual. Omitted for brevity
m_video_format->depth, // Determine correct depth from the visual. Omitted for brevity
ZPixmap, NULL, m_shm_segment_info, m_video_format->width, m_video_format->height);
if(!m_x_shm_image){
SIMLOG(SimLogger::Error, "can not create XshmImage");
return false;
}
m_shm_segment_info->shmid = shmget(IPC_PRIVATE,
m_x_shm_image->bytes_per_line * m_x_shm_image->height,
IPC_CREAT|0777);
if(m_shm_segment_info->shmid == -1){
SIMLOG(SimLogger::Error, "shmid failed");
return false;
}
m_shm_segment_info->shmaddr = m_x_shm_image->data = (char* )shmat(m_shm_segment_info->shmid, 0, 0);
// m_shm_segment_info->readOnly = False;
if(!XShmAttach(m_display, m_shm_segment_info)){
SIMLOG(SimLogger::Error, "XShmAttach failed");
return false;
}
SIMLOG(SimLogger::Debug, "initShmImages success");
return true;
}
void X11DesktopCapture::UpdateVideoCapture(){
while(m_start_capture){
if(!XShmGetImage(m_display, m_desktop_window, m_x_shm_image, 0, 0, AllPlanes)){
SIMLOG(SimLogger::Error, "update shmImage failed");
return ;
}
std::vector<unsigned char> buffer(m_x_shm_image->data, m_x_shm_image->data + m_screen_size);
m_buffer_queue->WaitePushBuffer(buffer);
}
// memcpy(video_data, m_x_shm_image->data, m_screen_size );
}
bool X11DesktopCapture::DestroyVideoCapture(){
StopVideoCapture();
if(m_shm_segment_info && m_shm_segment_info->shmid != -1 && m_display){
if(!XShmDetach(m_display, m_shm_segment_info)){
SIMLOG(SimLogger::Error, "XShmDetach failed");
return false;
}
}
if(m_x_shm_image){
XDestroyImage(m_x_shm_image);
m_x_shm_image = nullptr;
}
if(m_shm_segment_info && m_shm_segment_info->shmaddr != (char*)-1 && m_display){
shmdt(m_shm_segment_info->shmaddr);
}
if(m_shm_segment_info && m_shm_segment_info->shmid != -1 && m_display){
shmctl(m_shm_segment_info->shmid, IPC_RMID, 0);
m_display = nullptr;
}
if(m_shm_segment_info){
m_shm_segment_info->shmaddr = (char*)-1;
m_shm_segment_info->shmid = -1;
delete m_shm_segment_info;
m_shm_segment_info = nullptr;
}
return true;
}
} /* namespace RemoteServer */
首先是初始化InitVideoCapture
。
在初始化过程中,首先通过XOpenDisplay(NULL)
获取X11的默认DISPLAY。参数可以直接设置为DISPLAY号,如图我的DISPLAY是:1。就可以写成XOpenDisplay(:1)
然后获取根窗口,也就是你的桌面窗口。
m_desktop_window = RootWindow(m_display, 0);
然后获取屏幕宽高
m_video_format->width = DisplayWidth(m_display, 0);
m_video_format->height = DisplayHeight(m_display, 0);
通过XGetImage
获取到当前屏幕信息,包括宽、高、位深等信息。
XImage* window_image = XGetImage(m_display, m_desktop_window, 0, 0, m_video_format->width, m_video_format->height, AllPlanes, ZPixmap);
如果只是想要截屏,把window_image->data
数据保存下来就是原始的RGB图像信息了。
使用后记得销毁,不然就内存泄露了
XDestroyImage(window_image);
如果想要录屏的话,用XGetImage
的方式效率极低。要使用X共享内存的方式进行捕获。首先对X共享内存初始化。
InitShmImages()
在初始化过程中,对将X共享内存与当前DISPLAY进行绑定,这样以后可以通过读取共享内存数据直接捕获当前DISPLAY的屏幕
XShmAttach(m_display, m_shm_segment_info)
初始化以后,便可以通过XShmGetImage
函数直接高效的捕获当前屏幕的数据了
XShmGetImage(m_display, m_desktop_window, m_x_shm_image, 0, 0, AllPlanes)
最后记得要释放掉共享内存
DestroyVideoCapture()
通过测试程序捕获屏幕
#include "VideoCapture/VideoCapture.h"
#include "Logger/Logger.h"
#include <fstream>
#include <memory>
#include <unistd.h>
#include "../../src/Base/BufferQueue/BufferQueue.hpp"
using namespace RemoteServer;
int main(){
SimLogger::Logger::CreateLoggerInstance();
SimLogger::Logger::InitLogger(SimLogger::ConsoleType, "LoggerTest", SimLogger::Info);
std::ofstream file_stream("x11capture.rgb", std::ios::out | std::ios::binary);
auto buffer_queue = std::make_shared< BufferQueue<unsigned char> >(5);
auto video_capture = VideoCapture::CreateVideoCapture(X11Desktop, buffer_queue);
auto format = video_capture->GetVideoFormat();
int screen_size = format->width * format->height * format->bits_per_pixel / 8;
// unsigned char* data = new unsigned char[screen_size];
video_capture->StartVideoCapture();
int count = 0;
while(count++ < 500){
std::vector<unsigned char> buffer;
buffer_queue->WaitePopBuffer(buffer);
file_stream.write(reinterpret_cast<char*>(buffer.data()), screen_size);
usleep(20000);
}
video_capture->StopVideoCapture();
file_stream.close();
// delete[] data;
video_capture->DestroyVideoCapture();
return 0;
}
捕获了500帧RGB原始数据存放在x11capture.rgb
中。