Package: dxcam
DXcamFastest Python Screenshot for Windowsimportdxcamcamera=dxcam.create()camera.grab()IntroductionDXcam is a Python high-performance screenshot library for Windows using Desktop Duplication API. Capable of 240Hz+ capturing. It was originally built as a part of deep learning pipeline for FPS games to perform better than existed python solutions (python-mss,D3DShot).Compared to these existed solutions, DXcam provides:Way faster screen capturing speed (> 240Hz)Capturing of Direct3D exclusive full-screen application without interrupting, even when alt+tab.Automatic handling of scaled / stretched resolution.Accurate FPS targeting when in capturing mode, makes it suitable for Video output.Seamless integration with NumPy, OpenCV, PyTorch, etc.Contributions are welcome!InstallationFrom TestPyPI:pipinstalldxcamFrom source:pipinstall--editable.UsageIn DXCam, each output (monitor) is asscociated to aDXCamerainstance.
To create a DXCamera instance:importdxcamcamera=dxcam.create()# returns a DXCamera instance on primary monitorScreenshotFor screenshot, simply use.grab:frame=camera.grab()The returnedframewill be anumpy.ndarrayin the shape of(Height,  Width, 3[RGB]). This is the default and the only supported format (for now). It is worth noting that.grabwill returnNoneif there is no new frame since the last time you called.grab. Usually it means there's nothing new to render since last time (E.g. You are idling).To view the captured screenshot:fromPILimportImageImage.fromarray(frame).show()To screenshot a specific region, use theregionparameter: it takestuple[int, int, int, int]as the left, top, right, bottom coordinates of the bounding box. Similar toPIL.ImageGrab.grab.left,top=(1920-640)//2,(1080-640)//2right,bottom=left+640,top+640region=(left,top,right,bottom)frame=camera.grab(region=region)# numpy.ndarray of size (640x640x3) -> (HXWXC)The above code will take a screenshot of the center640x640portion of a1920x1080monitor.Screen CaptureTo start a screen capture, simply use.start: the capture will be started in a separated thread, default at 60Hz. Use.stopto stop the capture.camera.start(region=(left,top,right,bottom))# Optional argument to capture a regioncamera.is_capturing# True# ... Do Somethingcamera.stop()camera.is_capturing# FalseConsume the Screen Capture DataWhile theDXCamerainstance is in capture mode, you can use.get_latest_frameto get the latest frame in the frame buffer:camera.start()foriinrange(1000):image=camera.get_latest_frame()# Will block until new frame availablecamera.stop()Notice that.get_latest_frameby default will block until there is a new frame available since the last call to.get_latest_frame. To change this behavior, usevideo_mode=True.Advanced Usage and RemarksMultiple monitors / GPUscam1=dxcam.create(device_idx=0,output_idx=0)cam2=dxcam.create(device_idx=0,output_idx=1)cam3=dxcam.create(device_idx=1,output_idx=1)img1=cam1.grab()img2=cam2.grab()img2=cam3.grab()The above code creates threeDXCamerainstances for:[monitor0, GPU0], [monitor1, GPU0], [monitor1, GPU1], and subsequently takes three full-screen screenshots. (cross GPU untested, but I hope it works.) To get a complete list of devices and outputs:>>>importdxcam>>>dxcam.device_info()'Device[0]:<Device Name:NVIDIA GeForce RTX 3090 Dedicated VRAM:24348Mb VendorId:4318>\n'>>>dxcam.output_info()'Device[0] Output[0]: Res:(1920, 1080) Rot:0 Primary:True\nDevice[0] Output[1]: Res:(1920, 1080) Rot:0 Primary:False\n'Output FormatYou can specify the output color mode upon creation of the DXCamera instance:dxcam.create(output_idx=0,output_color="BGRA")We currently support "RGB", "RGBA", "BGR", "BGRA", "GRAY", with "GRAY being the gray scale. As for the data format,DXCameraonly supportsnumpy.ndarrayin shape of(Height, Width, Channels)right now.We will soon add support for other output formats.Video BufferThe captured frames will be insert into a fixed-size ring buffer, and when the buffer is full the newest frame will replace the oldest frame. You can specify the max buffer length (defualt to 64) using the argumentmax_buffer_lenupon creation of theDXCamerainstance.camera=dxcam.create(max_buffer_len=512)Note:  Right now to consume frames during capturing there is onlyget_latest_frameavailable which assume the user to process frames in a LIFO pattern. This is a read-only action and won't pop the processed frame from the buffer. we will make changes to support various of consuming pattern soon.Target FPSTo makeDXCameracapture close to the user specifiedtarget_fps, we used the undocumentedCREATE_WAITABLE_TIMER_HIGH_RESOLUTIONflag to create a WindowsWaitable Timer Object. This is far more accurate (+/- 1ms) than Python (<3.11)time.sleep(min resolution 16ms). The implementation is done throughctypescreating a perodic timer. Python 3.11 used a similar approach[^2].camera.start(target_fps=120)# Should not be made greater than 160.However, due to Windows itself is a preemptive OS[^1] and the overhead of Python calls, the target FPS can not be guarenteed accurate when greater than 160. (See Benchmarks)Video ModeThe default behavior of.get_latest_frameonly put newly rendered frame in the buffer, which suits the usage scenario of a object detection/machine learning pipeline. However, when recording a video that is not ideal since we aim to get the frames at a constant framerate: When thevideo_mode=Trueis specified when calling.startmethod of aDXCamerainstance, the frame buffer will be feeded at the target fps, using the last frame if there is no new frame available. For example, the following code output a 5-second, 120Hz screen capture:target_fps=120camera=dxcam.create(output_idx=0,output_color="BGR")camera.start(target_fps=target_fps,video_mode=True)writer=cv2.VideoWriter("video.mp4",cv2.VideoWriter_fourcc(*"mp4v"),target_fps,(1920,1080))foriinrange(600):writer.write(camera.get_latest_frame())camera.stop()writer.release()You can do interesting stuff with libraries likepyavandpynput: see examples/instant_replay.py for a ghetto implementation of instant replay using hot-keysSafely Releasing of ResourceUpon calling.releaseon a DXCamera instance, it will stop any active capturing, free the buffer and release the duplicator and staging resource. Upon calling.stop(), DXCamera will stop the active capture and free the frame buffer. If you want to manually recreate aDXCamerainstance on the same output with different parameters, you can also manully delete it:camera1=dxcam.create(output_idx=0,output_color="BGR")camera2=dxcam.create(output_idx=0)# Not allowed, camera1 will be returnedcamera1iscamera2# Truedelcamera1delcamera2camera2=dxcam.create(output_idx=0)# AllowedBenchmarksFor Max FPS Capability:start_time,fps=time.perf_counter(),0cam=dxcam.create()start=time.perf_counter()whilefps<1000:frame=cam.grab()ifframeisnotNone:# New framefps+=1end_time=time.perf_counter()-start_timeprint(f"{title}:{fps/end_time}")When using a similar logistic (only captured new frame counts),DXCam, python-mss, D3DShotbenchmarked as follow:DXcampython-mssD3DShotAverage FPS238.79 :checkered_flag:75.87118.36Std Dev1.250.54470.3224The benchmark is across 5 runs, with a light-moderate usage on my PC (5900X + 3090; Chrome ~30tabs, VS Code opened, etc.), I used theBlur Buster UFO testto constantly render 240 fps on my monitor (Zowie 2546K). DXcam captured almost every frame rendered.For Targeting FPS:camera=dxcam.create(output_idx=0)camera.start(target_fps=60)foriinrange(1000):image=camera.get_latest_frame()camera.stop()(Target)\(mean,std)DXcampython-mssD3DShot60fps61.71, 0.26 :checkered_flag:N/A47.11, 1.3330fps30.08, 0.02 :checkered_flag:N/A21.24, 0.17Work ReferencedD3DShot: DXcam borrows the ctypes header directly from the no-longer maintained D3DShot.OBS Studio: Learned a lot from it.[^1]:https://en.wikipedia.org/wiki/Preemption_(computing)Preemption (computing)[^2]:https://github.com/python/cpython/issues/65501bpo-21302: time.sleep() uses waitable timer on Windows