Linux屏幕捕获最简单的方式就是使用FFmpeg,一些Linux上的开源录屏软件也是基于FFmpeg开发的,如vokoscreen。如果需要在Linux上实现录屏功能(单一屏幕捕获方式、单一编码格式),只需要使用FFmpeg中的一小部分功能,然而又不想把FFmpeg引到自己的项目中,那么可以参考FFmpeg实现自己的功能。第一、可以熟悉Linux屏幕的捕获方法;第二、如果出现bug(虽然FFmpeg出bug的可能性比较小),也便于自己调试和修复。本文主要介绍使用xcb实现Linux上的屏幕捕获功能。
The X protocol C-language Binding (XCB) is a replacement for Xlib featuring a small footprint, latency hiding, direct access to the protocol, improved threading support, and extensibility.[1]
在Linux上可以使用Ctrl+Alt+Fx键在字符终端与窗口界面之间进行切换。如果要捕获窗口界面又想跳过root权限,那么选用X协议窗口系统提供的接口是个不错的选择(什么是X protocol可以参看Wikipedia);如果要捕获字符终端的屏幕,X窗口系统就爱莫能助了,这个时候可以通过读取fb0来获取屏幕数据,但是需要root权限。为什么选用xcb呢?嗯嗯…… 除了Xlib,好像没得选了。以下是通过xcb捕获屏幕的具体步骤。
建立连接
X协议的窗口系统是基于网络的,存在一个服务端X-server,而我们就是客户端。
客户端在进行一系列操作之前需要先与服务端建立连接1
2/* Open the connection to the X server */
xcb_connection_t *connection = xcb_connect (NULL, NULL);
获取屏幕参数
可以理解窗口系统是基于树的,那么要找到根窗口。1
2
3
4/* Get the first screen */
const xcb_setup_t *setup = xcb_get_setup (connection);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator (setup);
xcb_screen_t *screen = iter.data;
这个时候就知道屏幕的宽度和高度了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/* Get bpp */
const xcb_format_t* fmt = xcb_setup_pixmap_formats(setup);
int length = xcb_setup_pixmap_formats_length(setup);
int bpp = 0;
while (length--) {
if (screen->root_depth == fmt->depth) {
bpp = fmt->bits_per_pixel;
break;
}
fmt++;
}
uint32_t width = screen->width_in_pixels;
uint32_t height = screen->height_in_pixels;
uint32_t stride = screen->width_in_pixels * (bpp >> 3);
屏幕的数据格式中一个depth只会对应一个bpp,但一个bpp可以对应多个depth。
分配内存
知道屏幕参数了,那么在获取屏幕数据时要让X-server知道有那么一块地方可以存储数据。1
2
3
4
5
6
7
8
9
10
11
12
13xcb_shm_segment_info_t shminfo;
shminfo.shmid = shmget(IPC_PRIVATE, stride * height, (IPC_CREAT | 0666));
if (shminfo.shmid == (uint32_t)-1) {
// error stuff
}
shminfo.shmaddr = (uint8_t*)shmat(shminfo.shmid, 0, 0);
if (shminfo.shmaddr == (uint8_t*)-1) {
// error stuff
}
shminfo.shmseg = xcb_generate_id(connection_);
xcb_shm_attach(connection_, shminfo.shmseg, shminfo.shmid, 0);
这就是Linux进程间通信的一种方式:共享内存,shminfo.shmaddr
就是共享内存的首地址。
捕获屏幕像素
既然存储屏幕数据的内存有了,那么这个时候就可以让X-server把屏幕数据拷贝到这块内存了。1
2
3
4
5
6
7
8
9
10
11
12
13xcb_generic_error_t* e = nullptr;
xcb_shm_get_image_cookie_t cookie = xcb_shm_get_image(connection, screen->root, 0, 0, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, shminfo.shmseg, 0);
xcb_shm_get_image_reply_t* reply = xcb_shm_get_image_reply(connection, cookie, &e);
if (e) {
// error stuff
}
if (reply) {
free(reply);
}
xcb_flush(connection);
现在shminfo.shmaddr
里面就是当前屏幕的像素数据了,这段代码一般放在while
循环里。
释放内存和断开连接
最后如果结束了要记得断开连接和释放内存。1
2
3
4
5
6xcb_shm_detach(connection, shminfo.shmseg);
shmdt(shminfo.shmaddr);
shmctl(shminfo.shmid, IPC_RMID, 0);
xcb_disconnect(connection);
是不是很简单,别高兴的太早,这个时候好像还缺点什么?没有光标、没有编码、没有存储、没有音频。
参考资料
[1]. xcb
[2]. https://stackoverflow.com/questions/32972539/gdk-x11-screen-capture
[3]. https://github.com/bawNg/krad_radio
[4]. http://ffmpeg.org/doxygen/trunk/xcbgrab_8c_source.html