“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.

OSDeviceRequired build flagNotes
Windowsgdigrabincluded by defaultGDI based, max compatibility
WindowsddagrabDXGI support (in official Windows builds)FFmpeg 5.0+
Windowsdshowincluded by defaultAudio / camera capture
macOSavfoundationincluded by default (brew install ffmpeg)macOS 10.7+
Linuxx11grab--enable-libxcbRequires X11 session
Linuxpulse--enable-libpulseSystem 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
OptionPurpose
-framerate 30Input frame rate (must be set before recording)
-preset ultrafastAvoid dropped frames during real-time capture
-crf 23Quality (lower = better; 18–28 recommended)
-pix_fmt yuv420pRequired 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
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=60 -c:v h264_nvenc -cq 20 -preset p5 output.mp4
OptionPurpose
-f lavfi -i ddagrab=...Invoked as a filter source, not a device
output_idx=0Primary display (1, 2, … for additional monitors)
framerate=6060 fps capture
h264_nvencNVIDIA 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

ItemgdigrabddagrabOBS Studio
Supported OSWindows XP+Windows 8+Windows / macOS / Linux
CPU load (4K60p)High (30–50%)Low (5–10%)Low (with GPU)
HDR captureNoYesYes
Scene switchingNoNoYes
Live streamingManual RTMP setupManual RTMP setupBuilt-in
Learning curveLowMediumMedium–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.

  1. Run the command from your terminal (or iTerm) for the first time.
  2. Click “OK” on “Terminal would like to record this computer’s screen.”
  3. Open System Settings → Privacy & Security → Screen Recording and enable your terminal.
  4. Fully quit and reopen the terminal.

avfoundation vs QuickTime Player

ItemFFmpeg avfoundationQuickTime Player
Encoder choiceFree (libx264, hevc_videotoolbox, …)Fixed H.264 / HEVC
Mic + system audioNeeds BlackHole or similarMic only
Batch automationYesNo
GUINoneYes
Quick to startCommandOne 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
OptionPurpose
-video_size 1920x1080Capture region size (required)
-i :0.0Value 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:

  1. PipeWire route (recommended) — record with wf-recorder (wlroots) or gpu-screen-recorder, then post-process with FFmpeg.
  2. kmsgrab — possible but needs root or CAP_SYS_ADMIN.
  3. 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

ToolSessionPrivilegesDifficultyNotes
x11grabX11UserEasyStable, well documented
kmsgrabX11/Waylandroot or CAP_SYS_ADMINHardLow-level DRM
wf-recorderwlroots WaylandUserMediumSway, Hyprland, …
gpu-screen-recorderX11/WaylandUserEasyNVENC / 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

  1. Install VB-CABLE.
  2. In Windows Sound settings, change the playback device to “CABLE Input”.
  3. 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

  1. brew install blackhole-2ch
  2. In Audio MIDI Setup, create a Multi-Output Device combining Speakers + BlackHole 2ch.
  3. Set system output to that Multi-Output Device.
  4. 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.

Use casepresetCRFEstimated 30-min 1080p30 size
Real-time captureultrafast23~800 MB
Capture-then-processsuperfast20~1.2 GB
Re-encode after capturerecord ultrafast → re-encode medium CRF 22800 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:

  1. Switch to -preset ultrafast
  2. Raise -crf (23 → 28)
  3. Lower the resolution (4K → 1080p)
  4. 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.



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