“I don’t want to install OBS just to make a quick screen recording.” “Every OS has a different command and the answers online are scattered across forums.” This guide solves that — every platform’s screen-capture flow, including the painful “how do I record system audio” part, in a single page.
Tested with: FFmpeg 8.1 on Windows 11, macOS 14 Sonoma, and Ubuntu 24.04.
What You’ll Learn
- Which capture device to use per OS (gdigrab / ddagrab / avfoundation / x11grab)
- Full screen, region selection, and mouse-cursor handling
- How to record system audio simultaneously (VB-Cable / BlackHole / PulseAudio loopback)
- Why your recording file is huge and the right CRF / preset for screencasts
- Post-processing commands to trim, crop, and re-compress
- Troubleshooting common errors (Permission denied, Could not find video device, dropped frames)
FFmpeg Build Requirements
Different inputs need different compile-time features. Run ffmpeg -devices to confirm what’s available.
| OS | Device | Required build flag | Notes |
|---|---|---|---|
| Windows | gdigrab | included by default | GDI based, max compatibility |
| Windows | ddagrab | DXGI support (in official Windows builds) | FFmpeg 5.0+ |
| Windows | dshow | included by default | Audio / camera capture |
| macOS | avfoundation | included by default (brew install ffmpeg) | macOS 10.7+ |
| Linux | x11grab | --enable-libxcb | Requires X11 session |
| Linux | pulse | --enable-libpulse | System audio / mic capture |
Confirm available devices:
ffmpeg -devices
If you see gdigrab, avfoundation, or x11grab in the list, you’re ready.
Windows: Record with gdigrab
Full screen
ffmpeg -f gdigrab -framerate 30 -i desktop output.mp4
-i desktop represents the entire primary display. Without an explicit codec, FFmpeg picks libx264 based on the .mp4 extension.
With explicit quality settings
ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p output.mp4
| Option | Purpose |
|---|---|
-framerate 30 | Input frame rate (must be set before recording) |
-preset ultrafast | Avoid dropped frames during real-time capture |
-crf 23 | Quality (lower = better; 18–28 recommended) |
-pix_fmt yuv420p | Required for player compatibility |
Region capture
ffmpeg -f gdigrab -framerate 30 -offset_x 100 -offset_y 50 -video_size 1280x720 -i desktop output.mp4
-offset_x / -offset_y use a top-left origin; -video_size is width×height. -video_size must precede -i.
Cursor handling
gdigrab includes the cursor by default. Disable with -draw_mouse 0.
ffmpeg -f gdigrab -framerate 30 -draw_mouse 0 -i desktop output.mp4
Capture a single window
ffmpeg -f gdigrab -framerate 30 -i title="Notepad" output.mp4
The window title must match exactly. Quote it if it contains spaces.
Windows: Faster ddagrab (DXGI)
ddagrab uses the Desktop Duplication API introduced in Windows 8 and runs on the GPU. Compared to gdigrab:
- 4K@60fps with only 5–10% CPU usage
- HDR display capture
- Almost no tearing
Basic command (hardware encoder recommended)
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=60 -c:v h264_nvenc -cq 20 -preset p5 output.mp4
| Option | Purpose |
|---|---|
-f lavfi -i ddagrab=... | Invoked as a filter source, not a device |
output_idx=0 | Primary display (1, 2, … for additional monitors) |
framerate=60 | 60 fps capture |
h264_nvenc | NVIDIA encoder (use h264_amf for AMD, h264_qsv for Intel) |
Falling back to CPU encoding
Without an NVENC/AMF/QSV GPU, copy the frames back to CPU memory before libx264:
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=60 -vf "hwdownload,format=bgra,format=yuv420p" -c:v libx264 -preset ultrafast -crf 20 output.mp4
ddagrab outputs GPU textures; without hwdownload, software encoders reject the frames.
gdigrab vs ddagrab vs OBS
| Item | gdigrab | ddagrab | OBS Studio |
|---|---|---|---|
| Supported OS | Windows XP+ | Windows 8+ | Windows / macOS / Linux |
| CPU load (4K60p) | High (30–50%) | Low (5–10%) | Low (with GPU) |
| HDR capture | No | Yes | Yes |
| Scene switching | No | No | Yes |
| Live streaming | Manual RTMP setup | Manual RTMP setup | Built-in |
| Learning curve | Low | Medium | Medium–High |
Use OBS for live streaming and complex compositing; ddagrab for the fastest one-shot recording.
macOS: Record with avfoundation
List devices first
ffmpeg -f avfoundation -list_devices true -i ""
Sample output:
[AVFoundation indev] AVFoundation video devices:
[AVFoundation indev] [0] FaceTime HD Camera
[AVFoundation indev] [1] Capture screen 0
[AVFoundation indev] [2] Capture screen 1
[AVFoundation indev] AVFoundation audio devices:
[AVFoundation indev] [0] MacBook Pro Microphone
[AVFoundation indev] [1] BlackHole 2ch
Capture screen 0 is the primary display.
Screen only
ffmpeg -f avfoundation -framerate 30 -i "1" -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p output.mp4
The "1" is the index assigned to Capture screen 0 in your environment (it may differ).
Screen + built-in microphone
ffmpeg -f avfoundation -framerate 30 -i "1:0" -c:v libx264 -preset ultrafast -crf 23 -c:a aac -b:a 128k output.mp4
The colon-separated form is "video_index:audio_index".
Screen + webcam (picture-in-picture)
ffmpeg -f avfoundation -framerate 30 -i "1" -f avfoundation -framerate 30 -i "0" -filter_complex "[1:v]scale=320:-1[pip];[0:v][pip]overlay=W-w-20:H-h-20" -c:v libx264 -preset ultrafast output.mp4
Overlays the webcam in the bottom-right corner of the screen capture.
Granting permissions (important)
Since macOS 10.15 Catalina, the first run triggers a Screen Recording permission dialog. Without permission, the output is a black video.
- Run the command from your terminal (or iTerm) for the first time.
- Click “OK” on “Terminal would like to record this computer’s screen.”
- Open System Settings → Privacy & Security → Screen Recording and enable your terminal.
- Fully quit and reopen the terminal.
avfoundation vs QuickTime Player
| Item | FFmpeg avfoundation | QuickTime Player |
|---|---|---|
| Encoder choice | Free (libx264, hevc_videotoolbox, …) | Fixed H.264 / HEVC |
| Mic + system audio | Needs BlackHole or similar | Mic only |
| Batch automation | Yes | No |
| GUI | None | Yes |
| Quick to start | Command | One click |
Linux: Record with x11grab
Assumes an X11 session. Wayland needs a different approach (see below).
Confirm the display
echo $DISPLAY
Usually :0 or :0.0.
Full screen
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p output.mp4
| Option | Purpose |
|---|---|
-video_size 1920x1080 | Capture region size (required) |
-i :0.0 | Value of the DISPLAY environment variable |
Use xdpyinfo | grep dimensions to read the exact resolution.
Region capture
ffmpeg -f x11grab -framerate 30 -video_size 1280x720 -i :0.0+100,50 -c:v libx264 -preset ultrafast output.mp4
The :0.0+x,y form sets the capture offset.
Hide the cursor
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -draw_mouse 0 -i :0.0 -c:v libx264 -preset ultrafast output.mp4
-draw_mouse 0 must be before -i.
Wayland alternatives
x11grab does not work on Wayland sessions (GNOME 40+, recent KDE). FFmpeg has no native Wayland capture, so pick one of:
- PipeWire route (recommended) — record with
wf-recorder(wlroots) orgpu-screen-recorder, then post-process with FFmpeg. - kmsgrab — possible but needs root or
CAP_SYS_ADMIN. - Switch to Xwayland session — choose X11 at the login screen.
Minimal kmsgrab example:
ffmpeg -device /dev/dri/card0 -f kmsgrab -i - -vf "hwdownload,format=bgr0" -c:v libx264 -preset ultrafast output.mp4
Linux capture comparison
| Tool | Session | Privileges | Difficulty | Notes |
|---|---|---|---|---|
| x11grab | X11 | User | Easy | Stable, well documented |
| kmsgrab | X11/Wayland | root or CAP_SYS_ADMIN | Hard | Low-level DRM |
| wf-recorder | wlroots Wayland | User | Medium | Sway, Hyprland, … |
| gpu-screen-recorder | X11/Wayland | User | Easy | NVENC / VAAPI support |
Capturing System Audio Simultaneously
This is the single most-asked question about screen recording. No OS lets you record “the sound coming out of your speakers” directly without a virtual audio device in between.
Windows: VB-CABLE
- Install VB-CABLE.
- In Windows Sound settings, change the playback device to “CABLE Input”.
- In FFmpeg, capture from “CABLE Output”.
ffmpeg -f gdigrab -framerate 30 -i desktop -f dshow -i audio="CABLE Output (VB-Audio Virtual Cable)" -c:v libx264 -preset ultrafast -crf 23 -c:a aac -b:a 192k output.mp4
List dshow audio devices:
ffmpeg -list_devices true -f dshow -i dummy
macOS: BlackHole
brew install blackhole-2ch- In Audio MIDI Setup, create a Multi-Output Device combining Speakers + BlackHole 2ch.
- Set system output to that Multi-Output Device.
- In FFmpeg, capture audio from BlackHole.
ffmpeg -f avfoundation -framerate 30 -i "1:1" -c:v libx264 -preset ultrafast -crf 23 -c:a aac -b:a 192k output.mp4
The second 1 in "1:1" is the audio index assigned to BlackHole in -list_devices output.
Linux: PulseAudio loopback
PulseAudio / PipeWire ships with monitor sources for every output sink.
pactl list short sources
Look for a name like alsa_output.pci-XXXX.analog-stereo.monitor.
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 -f pulse -i alsa_output.pci-0000_00_1f.3.analog-stereo.monitor -c:v libx264 -preset ultrafast -crf 23 -c:a aac -b:a 192k output.mp4
Mixing system audio + microphone
Combine three inputs with amix (PulseAudio example):
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 -f pulse -i alsa_output.pci-0000_00_1f.3.analog-stereo.monitor -f pulse -i alsa_input.pci-0000_00_1f.3.analog-stereo -filter_complex "[1:a][2:a]amix=inputs=2:duration=longest[a]" -map 0:v -map "[a]" -c:v libx264 -preset ultrafast -c:a aac -b:a 192k output.mp4
The same amix pattern applies on Windows and macOS — just swap the audio inputs. To lower the mic relative to system audio, insert [2:a]volume=0.5[mic] before the mix.
Balancing File Size and Quality
Screencasts compress better than typical video because most pixels stay still between frames.
Recommended presets
| Use case | preset | CRF | Estimated 30-min 1080p30 size |
|---|---|---|---|
| Real-time capture | ultrafast | 23 | ~800 MB |
| Capture-then-process | superfast | 20 | ~1.2 GB |
| Re-encode after capture | record ultrafast → re-encode medium CRF 22 | — | 800 MB → 200 MB final |
The recording rule
While capturing, always use -preset ultrafast or superfast. Anything medium or slower causes the encoder to fall behind real time, producing dropped frames (frame= 120 fps= 18).
Re-encode after capture (1/4 the size)
Below this point we work with regular video files, so the commands verify cleanly on Linux/macOS CI.
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 24 -c:a aac -b:a 128k output.mp4
H.265 for screencasts
H.265 (HEVC) compresses text and scrolling content even better.
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 26 -c:a aac -b:a 128k output.mp4
Add -tag:v hvc1 if the file needs to play in Apple QuickTime / iOS.
Post-processing the Recording
Trim unused intro/outro
ffmpeg -ss 00:00:05 -to 00:01:30 -i input.mp4 -c copy output.mp4
Putting -ss before -i makes the trim almost instantaneous.
Crop to a region
ffmpeg -i input.mp4 -vf "crop=in_w-200:in_h-100:100:50" output.mp4
Format is crop=width:height:x:y. Using in_w / in_h lets you crop relative to the input size, which avoids errors when the recording resolution differs. For a fixed region, use crop=1280:720:100:50.
Halve the resolution
ffmpeg -i input.mp4 -vf "scale=iw/2:ih/2" -c:v libx264 -preset medium -crf 23 -c:a copy output.mp4
Extract audio only
ffmpeg -i input.mp4 -vn -c:a copy output.m4a
Troubleshooting
Could not find video device with name [desktop] (Windows)
You forgot -f gdigrab. It must precede -i desktop.
# Wrong
ffmpeg -i desktop output.mp4
# Correct
ffmpeg -f gdigrab -framerate 30 -i desktop output.mp4
Permission denied / black screen (macOS)
Screen Recording permission isn’t granted. Open System Settings → Privacy & Security → Screen Recording, enable your terminal, then fully restart it.
Cannot open display :0 / BadAccess (Linux)
You’re either on Wayland or running over SSH without forwarding.
echo $XDG_SESSION_TYPE
If it returns wayland, re-login with an X11 session or use wf-recorder instead.
Dropped frames (frame= XXX fps= 5)
The encoder can’t keep up with the capture rate. Try in order:
- Switch to
-preset ultrafast - Raise
-crf(23 → 28) - Lower the resolution (4K → 1080p)
- Switch to a hardware encoder (
h264_nvenc/hevc_videotoolbox/h264_qsv/h264_vaapi)
Audio and video drift apart
Mismatched input clocks. Add -async 1 or -vsync cfr:
ffmpeg -f gdigrab -framerate 30 -i desktop -f dshow -i audio="Microphone" -vsync cfr -c:v libx264 -preset ultrafast -c:a aac output.mp4
For long recordings, add -use_wallclock_as_timestamps 1 to each input for additional stability.
FAQ
Why is my recording huge?
You probably forgot the encoder flag, or used -c:v copy. Always specify -c:v libx264 -preset ultrafast -crf 23 for capture, then re-encode with -preset medium -crf 24 afterwards. See the video compression guide for the full menu.
How do I include the cursor?
gdigrab and x11grab include the cursor by default; add -draw_mouse 0 before -i to hide it. avfoundation needs -capture_cursor 1 to enable it.
I can’t record at 60fps
Set -framerate 60 on the input and lighten the encoder load. If ultrafast still falls behind, switching to a GPU encoder (NVENC / VideoToolbox / QSV / VAAPI) is the single most effective fix. ddagrab + h264_nvenc handles 4K60p comfortably.
Mix system audio + microphone?
Capture system audio through a virtual device (VB-CABLE / BlackHole / PulseAudio monitor) and combine it with the microphone via -filter_complex amix=inputs=2. The command in the “Capturing System Audio” section is the complete recipe.
Compared to OBS Studio?
OBS is a GUI tool that handles scene switching, streaming, and recording in one place. FFmpeg is stronger when you need scriptable, headless, server-side capture — generating videos in CI, deploying identical recording configs across machines, or piping straight to a streaming service. If all you need is “record and push to RTMP,” FFmpeg covers that too (see RTMP streaming with FFmpeg).
How do I get it under Discord’s upload limit?
Discord’s free tier caps uploads at 25 MB. Re-encode after capture — the full recipe is in Compress video for Discord with FFmpeg.
Related Articles
- How to Compress MP4 with FFmpeg
- RTMP Live Streaming with FFmpeg
- Compress Video for Discord with FFmpeg
Tested with ffmpeg 8.1 / Windows 11 23H2 / macOS 14 Sonoma / Ubuntu 24.04 Primary sources: ffmpeg.org/ffmpeg-devices.html#gdigrab / ffmpeg.org/ffmpeg-devices.html#avfoundation / ffmpeg.org/ffmpeg-devices.html#x11grab