主要还是下面这个问题。最近一直在思考这个问题到底是什么原因导致的。思来想去决定还是得看一看ffmpeg的源码。
ffmpeg主要还是用C写的,读起来问题不大。看了源码之后发现之前的思路有些地方不太对,不过问题也不大。
当然问题来源还是没错的,确实是gdigrab的报错信息。具体的报错信息根据实际场景的不同,试了下有两种
- 只报Failed to capture image
- 先报Couldn’t get cursor info,再报Failed to capture image
一般都是第一种情况,第二种情况前几天还能复现,今天试了下又没法复现了。但既然会出现这个问题,那还是顺便分析分析。
Couldn’t get cursor info
从报错信息入手
根据报错信息的先后顺序,先看看cursor info。首先还是得从报错信息入手。录制被中断的时候命令行会输出Couldn’t get cursor info这条报错信息。既然ffmpeg开源,那肯定是去源码里找这条报错信息是在什么情况下出现的。
把ffmpeg的仓库git clone到本地之后,直接开搜。最后在gdigrab.h里面找到了这条代码

这里是else,那肯定得往上翻翻看看if条件是什么。

Cursor那就是鼠标光标,paint_mouse_pointer函数也告诉你了这函数和鼠标光标有关。
再看看GetCursorInfo,当它返回False的时候就会进入CURSOR_ERROR。那看看GetCursorInfo什么时候返回False。但是….

点进去之后发现是WINUSERAPI,直接调用的是本地C编译器的头文件winuser.h。再仔细一看,WINUSERAPI,不用想都知道肯定是Windows自身提供的API。那只能去微软文档看看。

那接下来看看GetCursorInfo什么时候返回false。

拿光标信息的时候报错就返回false呗。而cursorinfo是个结构体。

- cbSize:结构体大小
- flags:光标状态。
- hCursor:光标句柄,
- ptScreenPos:光标在屏幕上的位置。
简单来说,当ffmpeg拿不到屏幕上光标的信息的时候就会报错。联系到远程桌面的话可能是最小化/关闭远程桌面后,远程主机的界面上是没有光标的,自然而然拿不到cursorinfo。
当然这是推测,一方面是cursorinfo和getcursorinfo的源码都没开源;另一方面是前几天还能复现这个问题,今天怎么试都复现不了,还是挺搞心态的。
Failed to capture image
还是gdigrab.c
还是在gdigrab.c里面,能找到Failed to capture image的来源。

看到WIN32,那肯定还是和windows本身的API有关系,毕竟头文件都叫gdigrab(GDI:全球防…Graphics Device Interface)了。
主要是BitBlt,BitBlt返回false的时候才会报错。那看看bitblt是什么。

看着挺模糊的,什么叫将颜色数据从原设备传输到目标设备?目前唯一知道的是当该函数执行失败时返回false。不过好在官方给了用例:BitBlt可用于屏幕截图


这里的“DC”指的是“device context”,即“设备上下文”。官方也给出了相应的解释。DC就像设备的“绘画指导书”:该画啥,用啥颜色,画完该放哪,这本“书”里面都有。

回到正文,当我们执行下面这条指令的时候,就会调用到BitBlt。简单来说就是ffmpeg调用BitBlt捕获桌面图像,再将图像复制到内存的DC中的位图。ffmpeg再从这个位图读取数据将其转换为视频帧。
ffmpeg -f gdigrab -i desktop output.mp4
而BitBlt执行失败的时候就会返回false。那么内存肯定是没问题的,不会说远程桌面关了之后内存就不可用了。ffmpeg也没问题,那问题只能在”获取屏幕“这上面。
写者注
有一个比较抽象的问题。我在用sanfor的HCI平台的时候,也是用控制台然后远程连接到执行机,但是关闭标签页之后对ffmpeg的录屏没有任何影响。
但如果是控制台直接去看执行机,关闭标签页之后虽然不会中断录制,但录制出来的视频会卡在某一帧。看录制出来的视频的时候发现系统时间都不带动的,就像截了某个时间点的图然后把这张图转换成了视频一样。
更底层的东西就和HCI控制台的实现原理有关了,接触不到,所以这里只是顺带提一嘴。