V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cool1205
V2EX  ›  程序员

Windows 截图原理,高难度问题,请慎入

  •  
  •   cool1205 · 2020-12-31 09:53:44 +08:00 · 10469 次点击
    这是一个创建于 1428 天前的主题,其中的信息可能已经有所发展或是发生改变。
    windows 截图可以使用 windows 自带的,也可以使用 QQ 等辅助工具截图,效果还是可以的。我想请教各位大神一个问题,windows 通过 API 截图是拿的哪里的数据,这可能与 windows 刷新屏幕有关系了,windows 是直接从屏幕直接截取,还是从内存中直接获取。这么说有些抽象,比如屏幕刷新率是 1HZ (例子而已),那就是一秒屏幕更新一下,此时有个程序每隔 0.1 秒向控制台输出当前毫秒时间戳,那此时使用截图工具,截到图片的时间戳会不会一秒内截图的十张图片一样,还是十张不一样。我尝试使用软件去验证,但不存在可操作性,希望有大哥指教一二
    53 条回复    2021-01-01 11:09:52 +08:00
    murmur
        1
    murmur  
       2020-12-31 09:56:53 +08:00
    虽然原理不知道么,但是你没用过截图工具么,你按下快捷键的一刻,已经先生成屏幕的截图了,此时屏幕是被截图盖住的,你可以继续框选

    另外 qq 的截图应该不是一般的 api,现在的截图工具都得支持 dx,太老的 api 截不到 dx 的游戏
    zocome
        2
    zocome  
       2020-12-31 09:58:15 +08:00
    同关注,我也一直有这个疑问
    IGJacklove
        3
    IGJacklove  
       2020-12-31 10:05:43 +08:00 via Android
    十张一样的吧,你都没刷新,只有在一秒之后才能拿到不一样的值吧。
    Tumblr
        4
    Tumblr  
       2020-12-31 10:07:19 +08:00
    @levie 来个高难度的回答。。。
    opengps
        5
    opengps  
       2020-12-31 10:11:15 +08:00 via Android
    虽然高难度不敢发言,不过软件验证方案很简单,那就是扩展屏下跑毫秒表,这个做法被人用云
    murmur
        6
    murmur  
       2020-12-31 10:11:32 +08:00
    @IGJacklove 但是 ufo 测试屏幕刷新率是说不能用截图验证刷新率,必须用高速相机,楼主这个 1fps 还真难试,60fps 、144 这些倒是能试出来
    enenaaa
        7
    enenaaa  
       2020-12-31 10:11:36 +08:00   ❤️ 1
    取得桌面窗口的 DC 句柄,BitBlt 拷贝位图出来就是了。
    跟屏幕刷新没关系。
    opengps
        8
    opengps  
       2020-12-31 10:13:22 +08:00 via Android
    @opengps 没这玩,回复按钮飘了一下。这个做法被人用来做屏幕延迟测试,在两块屏幕同时显示一样内容下拍照,可以抓到两块屏幕上不同的时间,从而得出延迟差异的毫秒级别时间差
    anuding
        9
    anuding  
       2020-12-31 10:18:31 +08:00 via Android
    你把显示器拔了试试看能不能截图不就知道了。
    答案应该是不一样,录屏之类的和你显示适配器能产生的图像速度有关吧(大概。
    wangkuanglin
        10
    wangkuanglin  
       2020-12-31 10:24:31 +08:00   ❤️ 1
    没了解过 DX 或者显卡驱动吗?截图就是从显存拿数据,各种工具只是层层封装后的 API 而已,和显示器没关系
    h82258652
        11
    h82258652  
       2020-12-31 10:25:05 +08:00
    前几个版本的 win10 有新 API,可以抓窗口或者屏幕
    https://docs.microsoft.com/en-us/uwp/api/windows.graphics.capture?view=winrt-19041
    老一点版本的 windows,抓窗口完美的做法只能 hook DX,这个我也没弄过,.net 做不了,只能上 C++的。简单的做法就是向目标窗口发送 WM_PAINT,但像 QQ 这种自绘一般抓出来的就是黑色的。
    抓屏是从显存取的。后面那个情况应该是 10 张不一样的,具体我也没验证过。
    stoneabc
        12
    stoneabc  
       2020-12-31 10:34:56 +08:00
    从显存 buffer 取,acuqireNextFrame 我记得是这个 API,阻塞调用直到数据有更新。
    ksc010
        13
    ksc010  
       2020-12-31 10:42:20 +08:00
    跟屏幕刷新帧率没关系应该
    不过我想楼主的意思应该是显卡的输出帧率吧?
    截图操作应该是直接从显存读取的
    PopRain
        14
    PopRain  
       2020-12-31 10:50:48 +08:00
    1.屏幕显示是冲显示缓冲去读取数据刷新
    2.刷新很快的程序,譬如游戏,用的是双缓冲方式,每次是写一个缓冲区,然后整个切换整个缓冲区,不会存在写一半的问题。
    3.缓冲区切换应该有锁机制,不会出现读一半然后缓冲区切换的问题。
    4.没有缓冲区切换的普通程序,你抓屏那一刻,位图更新到哪里就截取到哪里,没有影响
    NeezerGu
        15
    NeezerGu  
       2020-12-31 10:52:36 +08:00
    “比如屏幕刷新率是 1HZ (例子而已),那就是一秒屏幕更新一下,此时有个程序每隔 0.1 秒向控制台输出当前毫秒时间戳”

    我觉得 lz 可以做个测试,把显示器关了, 然后盲操作截图?
    w1573007
        16
    w1573007  
       2020-12-31 10:54:29 +08:00 via Android
    我想的是,你看到的显示器是具有刷新的,但数据在显卡中不是刷新的,只是通过显卡输出而已。截图可以理解从显存中读取当前状态,区域截图则是读取一部分的显存
    cool1205
        17
    cool1205  
    OP
       2020-12-31 10:55:15 +08:00
    我最开始的打算是直接买一个 240hz 的显示器+RTX3070+AMD R7 5800X,这样不管是从哪里截的图片,对于我软件的影响我都能接受。我对硬件与 Windows 系统不是很熟悉。我这样问会更好些,我现在每隔 5ms 需要截取一次屏幕截图,软件控制台输出是 1ms 更新一次,实时抓取,要满足这个条件,60hz 显示器是否足够用?
    aloxaf
        18
    aloxaf  
       2020-12-31 11:00:12 +08:00   ❤️ 3
    我总觉得你在提一个 X-Y 问题……
    你的目的是抓取某个程序每隔 1ms 输出在控制台上的时间戳?
    murmur
        19
    murmur  
       2020-12-31 11:03:40 +08:00
    @cool1205 240hz 的截图就不要想了,这个实时性可能跟不上,你大概需要一个高速的采集卡
    anuding
        20
    anuding  
       2020-12-31 11:06:46 +08:00 via Android
    这个问题和显示器没关系,和你的显卡有关系,首先你的显卡要能够每秒输出 1000 帧以上,即 1000fps 以上。其次,你的控制台应用,这个软件,这个窗口程序,要能支持 1000fps 的刷新率,这一点,要么你自己开个窗口写,要么你就去查 windows 默认的 cmd 支持不支持 1000 帧。
    cool1205
        21
    cool1205  
    OP
       2020-12-31 11:08:21 +08:00
    @aloxaf 没隔 5ms 就够了
    murmur
        22
    murmur  
       2020-12-31 11:11:01 +08:00
    @cool1205 你怎么保证 5ms 的精确时钟 编程时钟都是受系统负载干扰的 你的 timer 能精确到这个速度么

    不如这个东西 Elgato 4K60 Pro MK.2 这是真的 1080p 240hz 采集卡 全自动按你要求录视频 然后逐帧你可以满满分析
    sampeng
        23
    sampeng  
       2020-12-31 11:12:01 +08:00
    这。。哪高难度了。。。随便怎么想都不可能是截图显示器啊。。显示器的刷新率和我软件有什么关系?你用接口去操作显示器?我为什么不直接操作显卡的输出不更简单?
    cool1205
        24
    cool1205  
    OP
       2020-12-31 11:12:11 +08:00
    描述存在歧义,不是 1ms 截一次,5ms 截取就够了
    sujin190
        25
    sujin190  
       2020-12-31 11:12:31 +08:00
    截图从显存复制就行吧,窗口内容不变,显存内容又不会变更,和显示器刷新率页没啥关系,如果窗口渲染也要像显示器那么时时刻刻在刷新,那你这也太资源了吧,再说就算串口内容变更了,现在也完全是哪部分区域变更了重新渲染哪部分区域变更显存内容就行了吧,并不需要整个刷新
    aloxaf
        26
    aloxaf  
       2020-12-31 11:16:39 +08:00
    @cool1205
    你这个需求我感觉是在记录日志,请问「将程序的输出重定向到文件」这种方案是否考虑过,还是说考虑过但被否决了?
    shenjinpeng
        27
    shenjinpeng  
       2020-12-31 11:21:15 +08:00
    你猜不要显示器能不能截图 ?
    jones2000
        28
    jones2000  
       2020-12-31 11:46:39 +08:00
    下个开源的截图软件, 看下源码不就知道了。
    iriyave
        29
    iriyave  
       2020-12-31 12:06:54 +08:00   ❤️ 1
    正好搞过截图的,我试过两种方法:
    1 是 GDI,GetDesktopWindow,CreateCompatibleBitmap,GetDIBits ;
    2 是 DX,CreateOffscreenPlainSurface,GetFrontBufferData ;
    cool1205
        30
    cool1205  
    OP
       2020-12-31 12:08:13 +08:00
    刚才是试了一下,当我把 HDMI 线拔了后,进行截图,可以截图,截图内容与为拔截图内容一样,只有时间不一样。证明 windows 会继续上一屏幕进行截图,此时显卡有输出。其实这并没有解答我的疑问,windows 截图的内容来源是哪里? 1. Windows 通过自身 API 调用显卡内存,再输出我们看得到的图片; 2. Windows 根据自身 UI 句柄或其他方式,再不调用显存的情况下直接截图。如果是按照显存来的话,按照大家所说相当于显存是实时更新的,更新的频率就根据显卡性能来定,Windows 前往显存去抓取,至于显示器是多少 hz 则由显卡根据设定的 hz 从显存中获取数据后输出到显示器。不知道我这个理解对不对?
    cool1205
        31
    cool1205  
    OP
       2020-12-31 12:12:02 +08:00
    不用任何第三方截图软件,我认为 windows 自身的 API 必定是最快的。若真有这种代码存在,请大家推荐完全不调用 windows 自身 API 的方式,我试过几种,都是在 windows 自身 API 上做了一些封装而已,这种方式只会增加程序截图的时间,若对实时性要求不高的场景可以使用,但我对实时性要求很高。
    iriyave
        32
    iriyave  
       2020-12-31 12:12:28 +08:00
    补充一下,虽然我搞过截图功能,但也只是调用 win 提供的 API,也不懂 win 底层是怎么实现的,
    我的猜测是 dwm 也是双缓冲,各种程序的数据都是汇总到 BackBuffer,FrontBuffer 是随桌面刷新率更新,而截图是获取的 FrontBuffer 的数据,当然只是推测没验证过。
    GuuJiang
        33
    GuuJiang  
       2020-12-31 12:56:41 +08:00
    显示器的刷新率和程序进行重绘的速率两者之间没有任何关联,并且两者相差了好几个数量级,你完全可以写个程序显示一个静态图片并且不再进行任何重绘,此时显示器的刷新率仍然是固定的值
    如同楼上各位指出,这大概率是个 XY 问题,还是直接说出你的真正需求吧
    crystom
        34
    crystom  
       2020-12-31 13:04:09 +08:00
    截图和屏幕没关系吧,有刘海或者圆角屏幕的手机,截屏出来也是完整的长方形,不会像物理屏幕一样的形状
    janus77
        35
    janus77  
       2020-12-31 13:10:24 +08:00 via iPhone
    友情提示人眼所看到的东西只要是 60 帧就不卡了,也就是 16ms 一帧。你要 5ms 一次那你不如直接录视频,然后从视频里慢慢分析,还能暂停呢。
    3dwelcome
        36
    3dwelcome  
       2020-12-31 13:13:20 +08:00 via Android
    NVIDIA 有 60fps 输出流视频的 API,就和现在的游戏直播一样,在 H265 视频里截图,可以精确到 16ms 。
    正常 windows api 也可以截图,我写过这种软件,就是没办法连续抓取。
    phpfpm
        37
    phpfpm  
       2020-12-31 13:23:38 +08:00
    不太懂 windows 世界的事儿,但是应该是和 windows 窗口渲染有关

    DWM,窗口管理器什么的应该有好多个渲染层,有一些视频(比如 hdr )渲染的和窗口什么的都不一样

    所以这个问题还是挺复杂的

    https://www.zhihu.com/question/21747929/answer/498345137

    之前看过这篇文章,供参考
    phpfpm
        38
    phpfpm  
       2020-12-31 13:25:45 +08:00
    @cool1205 hdmi 实际上是显示适配器,决定了 DWM 绘制的时候要显示多大的视窗范围。

    如果只有显卡没有显示适配器,系统不会绘制图形什么的。
    SlipStupig
        39
    SlipStupig  
       2020-12-31 14:46:40 +08:00
    这块我刚好做过远程会议类软件,使用 GDI 系列 API 就可以得到,也可以通过 DX 得到,至于图像每一帧是不是一样的话,从 hash 比较上比较可能会不一样,稳妥的方法是监听鼠标和键盘事件
    Lemeng
        40
    Lemeng  
       2020-12-31 15:11:14 +08:00
    除非特殊需求。不然真没研究过。貌似也没必要
    YouLMAO
        41
    YouLMAO  
       2020-12-31 16:55:33 +08:00
    截图跟显示屏有个乱关系, 真是知乎派来挖 keng
    tomychen
        42
    tomychen  
       2020-12-31 17:02:13 +08:00
    你的显示器是真彩,我可以用调色板给你截成黑白的

    这和显示器刷新率有什么关系???
    across
        43
    across  
       2020-12-31 17:17:04 +08:00
    显示器这个硬件物理刷新率先放一边。 软件系统设定刷新率 1Hz,那意思就是程序每秒只触发 1 次画面更新。
    [此时有个程序每隔 0.1 秒向控制台输出当前毫秒时间戳] ,答案就是十张一样的。

    Windows 底层图形绘制 api 是 DirectX 。DirectX 和显卡沟通,在显存上申请一块 Buffer 代表画面帧( Frame ),类似于 web 中的 canvas (可能不太贴切), 画布上具体要画什么东西,由上层软件给他发命令:这里画个圈圈,白色的,那里一个方块啥的,方块黑色的······
    因为画面上命令绘制必然有先后顺序,如果只有一块 Buffer,人会看到画面是一步一步画出来的,这对于画面连贯性影响很大。现代系统对此的优化方法就是双缓冲三缓冲这种,前台始终显示的绘制完的画面帧,绘制中帧的放后台,画完后交换。
    这个交换刷新后画面的时间间隔,就是的刷新率 。Frame 不刷,输出画面不会变。

    一般情况下,要更新画面帧,应用软件是把自己要绘制的数据在内存中加载好,然后通过 DirectX 发到显卡上。截图是反过来,把 Frame 从显存读回内存,内存中你想干怎么就你自己干了,反正和硬件架构无关了。
    across
        44
    across  
       2020-12-31 17:19:24 +08:00
    @cool1205
    显示器显示的画面和显存中的画面是两回事。
    可以把显存中的画面看成“源头”,显卡把显存的数据转换成数字信号,通过 HDMI 之类的接口发给显示器显示。
    而电脑中的数据,是显存通过 PCI-E 接口(显卡插槽)和内存交流的。
    blindie
        45
    blindie  
       2020-12-31 17:29:52 +08:00 via Android
    计算机里面有一块 buffer 放置任意一个时刻应该渲染的数据。显示器每隔 internal 去取一遍。而 buffer 会在任何时候更新。截图是取 buffer 。跟显示器无关。因为写速度和读速度的天然不统一,所以游戏里才有个选项叫做垂直同步。写的比读的快所以造成图像撕裂
    laqow
        46
    laqow  
       2020-12-31 19:50:09 +08:00 via Android
    屏幕刷新率和显存刷新没关系的,你插 1hz 屏幕打 60 帧的游戏电脑照样给你 60 帧渲染,反过来你插 240hz 屏幕电脑也是 60 帧渲染。另外这个 60 帧是人为卡的,不卡它给你 2400 帧渲染都可以。
    snw
        47
    snw  
       2020-12-31 21:33:16 +08:00 via Android
    借楼,你们有没有用过 Azure Information Protection (AIP)?如果 Office 文档或邮件被保护,那么就截不到图(截出来全黑),不知道怎样实现的。
    cool1205
        48
    cool1205  
    OP
       2020-12-31 22:29:17 +08:00
    @across 感谢你的回复,我懂了你的意思,但我今天买了一个 240hz 的显示器,出现一个让我有些疑惑的问题,之前截张图片大概需要十几毫秒甚至二十毫秒,偶尔个位数毫秒。我现在使用 240hz 显示器,同样截图程序输出基本上都是 3-4ms 。这个情况是什么原因造成的呢?
    cool1205
        49
    cool1205  
    OP
       2020-12-31 22:33:21 +08:00
    @across 240hz 的显示器是 1080p 的,60hz 的显示器是 2k 的
    across
        50
    across  
       2020-12-31 22:47:25 +08:00
    @cool1205 和刷新率没关系。
    截图速度包括: 显存数据拷贝到内存,再在内存中编码成 jpg 格式,最后写入到硬盘。
    拷贝的数据量就是画面帧,帧大小 = 帧宽 * 帧高 * 每个像素点的色彩位数。 一般 32 位真彩就是 4byte,2560x1440 就是 14mb,1920:1080 才 8mb 。
    systemcall
        51
    systemcall  
       2020-12-31 22:49:22 +08:00   ❤️ 1
    @cool1205 dwm 在更新画面的时候,一些区域的更新确实是和显示器目前的刷新率一致的,这点可以使用 Fraps 之类的软件来证明
    分辨率高确实会降低绘图的速度,同时还对显存和内存有更大的负担,带宽上的需求也高一些。2k 的像素点是 1080p 的 2 倍,如果像素格式一样的话,2k 的负担应该是高一些的
    GPU 的话,AMD 和 NVIDIA 都没有什么文档,Intel 倒是有。感兴趣的话可以去 Intel Ark 找个稍微新一点的带核显的处理器,看一下有没有 datasheet,一些产品是有的,里面可能会有 GPU 方面的介绍,2D 部分是有几个图层和精灵的
    @snw 显卡、显示器是有 HDCP 之类的 DRM 的,受保护的内容会在专门的内存区域中处理,需要 1.处理器和 GPU 支持 2.BIOS 中启用相关功能 3.系统和驱动程序支持
    @anuding 没有显示器的情况下没试过。但是如果是远程桌面之类的是通过 WARP 来使用 CPU 进行渲染。Windows Defend 应用程序高级防护下启用了 GPU 加速的浏览器,感觉是用名为“Display Controller"这个东西来渲染的, 似乎拆成了 2 个部分而且很吃 CPU
    没有接显示器的情况下启动 OS 、自动登录,之后无论是远程桌面还是直接接显示器连接到电脑,都是可以正常显示画面的。但是如果是登录了之后断开显示器再连接上去,感觉还是和之前连接过的显示器有关系,桌面大小、缩放之类的
    ysc3839
        52
    ysc3839  
       2020-12-31 23:47:11 +08:00 via Android   ❤️ 1
    codehz
        53
    codehz  
       2021-01-01 11:09:52 +08:00
    @cool1205 #48 多半是截图程序的故意设计。。。因为截图目标是游戏 /视频一类的话,它不会强行多画几帧的(换句话说,类似于 push 模式,画好了推到显存上)
    但是如果是普通的窗口,则是你啥时候截图就可以即时给你画一个出来(当然内容更新与否还要看程序自己的意思),这就类似于 pull 模式( gdi 截图就可以做到)
    但是同一个截图方式不可能同时兼容于两种模式,所以为了能给游戏窗口截图,就只能用于刷新率绑定的 dx 截图方法了*
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1072 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 42ms · UTC 19:56 · PVG 03:56 · LAX 11:56 · JFK 14:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.