You can record your screen with FFmpeg alone, without installing OBS. Each OS has its own capture device: gdigrab or ddagrab on Windows, avfoundation on macOS and x11grab on Linux. Recording the sound your computer plays (system audio) also needs a virtual audio device on Windows and macOS.
FFmpeg Build Requirements
Each capture device has to be included when FFmpeg is built.
| OS | Device | Required build flag | Notes |
|---|---|---|---|
| Windows | gdigrab |
included by default | GDI based, max compatibility |
| Windows | ddagrab |
DXGI support (included in Windows builds such as gyan.dev) | FFmpeg 6.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 |
To list the devices in your build:
ffmpeg -devices
If gdigrab, avfoundation or x11grab is in the list, you can record. ddagrab is a filter, not a device, so it never appears here. Look for it in ffmpeg -filters instead.
Windows: Record with gdigrab
Full screen
ffmpeg -f gdigrab -framerate 30 -i desktop -pix_fmt yuv420p output.mp4
-i desktop records the whole desktop. With several monitors, it records all of them in one picture. Because no codec is given, FFmpeg picks libx264 from the .mp4 extension. Keep -pix_fmt yuv420p: without it, libx264 encodes the RGB screen capture as yuv444p, which many players cannot play.
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 |
Capture frame rate (must be set before recording) |
-preset ultrafast |
Fastest encoding, so fewer frames are dropped while recording |
-crf 23 |
Quality (lower = better; 18–28 recommended) |
-pix_fmt yuv420p |
Needed for most players to play the file |
Region capture
ffmpeg -f gdigrab -framerate 30 -offset_x 100 -offset_y 50 -video_size 1280x720 -i desktop -pix_fmt yuv420p output.mp4
-offset_x and -offset_y are measured from the top-left corner of the screen. -video_size is width×height and must come before -i.
Cursor handling
gdigrab records the mouse cursor by default. To hide it, add -draw_mouse 0:
ffmpeg -f gdigrab -framerate 30 -draw_mouse 0 -i desktop -pix_fmt yuv420p output.mp4
Capture a single window
ffmpeg -f gdigrab -framerate 30 -i title="Untitled - Notepad" -pix_fmt yuv420p output.mp4
The title must match the window’s title bar exactly. Notepad’s includes the file name, as in “Untitled - Notepad”. Put the title in quotes if it contains spaces.
Windows: Faster ddagrab (DXGI)
ddagrab uses the Desktop Duplication API (added in Windows 8) and runs on the GPU. Compared with gdigrab, it:
- uses less CPU
- can capture an HDR display
- shows 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=... |
Opens ddagrab 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 cannot take ddagrab’s frames directly) |
Falling back to CPU encoding
Without an NVENC or AMF encoder, copy the frames from the GPU back to system memory for 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 produces frames in GPU memory. Without hwdownload, software encoders cannot take them.
gdigrab vs ddagrab vs OBS
| Item | gdigrab | ddagrab | OBS Studio |
|---|---|---|---|
| Supported OS | Windows Vista+ | Windows 8+ | Windows / macOS / Linux |
| CPU load (4K60p) | High | Low | 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 for scenes built from several sources. For a fast one-off recording, use ddagrab.
macOS: Record with avfoundation
List devices first
ffmpeg -f avfoundation -list_devices true -i ""
Example 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
"1" is the index of Capture screen 0 in the example list. Use the index from your own list.
Screen + built-in microphone
ffmpeg -f avfoundation -framerate 30 -i "1:0" -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p -c:a aac -b:a 128k output.mp4
The form is "video_index:audio_index", so "1:0" means video device 1 and audio device 0.
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
This puts the webcam image in the bottom-right corner of the screen recording.
Granting permissions (important)
Since macOS 10.15 Catalina, the first recording opens a Screen Recording permission dialog. Without permission, the video is black.
- Run the command once in Terminal (or iTerm) so that macOS asks for permission.
- Open System Settings → Privacy & Security → Screen & System Audio Recording (on macOS 13 it is called Screen Recording) and turn on your terminal app.
- Quit the terminal completely and open it again.
avfoundation vs QuickTime Player
| Item | FFmpeg avfoundation | QuickTime Player |
|---|---|---|
| Encoder choice | Your choice (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
x11grab needs an X11 session. Wayland needs a different approach (see below).
Confirm the display
echo $DISPLAY
It is 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 size (the whole screen if omitted) |
-i :0.0 |
Value of the DISPLAY environment variable |
xdpyinfo | grep dimensions shows your exact screen resolution.
Region capture
ffmpeg -f x11grab -framerate 30 -video_size 1280x720 -i :0.0+100,50 -c:v libx264 -preset ultrafast -pix_fmt yuv420p output.mp4
With :0.0+x,y, the capture area starts x pixels from the left and y pixels from the top of the screen.
Hide the cursor
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -draw_mouse 0 -i :0.0 -c:v libx264 -preset ultrafast -pix_fmt yuv420p output.mp4
-draw_mouse 0 must come before -i.
Wayland alternatives
x11grab does not work in Wayland sessions (GNOME 40+, recent KDE). FFmpeg has no native Wayland capture, so use one of these:
- A recorder that supports Wayland (recommended): record with
wf-recorder(wlroots) orgpu-screen-recorder, then process the file with FFmpeg. - kmsgrab: works, but needs root or
CAP_SYS_ADMIN. - An X11 session: choose X11 on the login screen.
A basic kmsgrab command:
ffmpeg -device /dev/dri/card0 -f kmsgrab -i - -vf "hwdownload,format=bgr0" -c:v libx264 -preset ultrafast -pix_fmt yuv420p 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
On Windows and macOS, FFmpeg usually cannot record the sound coming out of your speakers directly. You send that sound through a virtual audio device and record from there. On Linux, use the monitor source described below.
Windows: VB-CABLE
- Install VB-CABLE.
- In Windows Sound settings, set 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 -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4
To list the 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 that includes your speakers and BlackHole 2ch.
- Set the system sound output to that Multi-Output Device.
- In FFmpeg, record the audio from BlackHole.
ffmpeg -f avfoundation -framerate 30 -i "1:1" -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4
The second 1 in "1:1" is BlackHole’s audio index in the -list_devices output.
Linux: PulseAudio loopback
PulseAudio and PipeWire provide a monitor source for every output. A monitor source carries whatever that output plays.
pactl list short sources
Look for a name ending in .monitor, such as 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 -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4
Mixing system audio + microphone
amix mixes the two audio inputs, system audio and microphone, into one track. A 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 -pix_fmt yuv420p -c:a aac -b:a 192k output.mp4
The same amix setup works on Windows and macOS with their own audio inputs. To make the microphone quieter than the system audio, add [2:a]volume=0.5[mic] before the mix and feed [mic] into amix.
Balancing File Size and Quality
Screen recordings compress better than camera video because most of the screen stays the same from one frame to the next.
Recommended presets
| Use case | preset | CRF |
|---|---|---|
| Real-time capture | ultrafast |
23 |
| Capture-then-process | superfast |
20 |
| Re-encode after capture | record with ultrafast, then medium |
24 (command below) |
The recording rule
While recording, the encoder must keep up in real time. Make a short test recording first and check the speed, any dropped frames and the saved file. If it falls behind, use ultrafast or superfast, or lower the resolution or frame rate. Whether medium keeps up depends on your machine and what is on the screen.
Re-encode after capture to make it smaller
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 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
With -ss before -i, the cut is almost instant.
Crop to a region
ffmpeg -i input.mp4 -vf "crop=in_w-200:in_h-100:100:50" output.mp4
The format is crop=width:height:x:y. in_w and in_h are the input width and height, so the crop still works when the recording has a different resolution. For a fixed area, use crop=1280:720:100:50.
Halve the resolution
ffmpeg -i input.mp4 -vf "scale=trunc(iw/4)*2:trunc(ih/4)*2" -c:v libx264 -preset medium -crf 23 -c:a copy output.mp4
trunc(iw/4)*2 and trunc(ih/4)*2 round half the width and height down to even numbers. On a 1366×768 screen, half the width is 683, an odd number, and with scale=iw/2:ih/2 libx264 stops with width not divisible by 2.
Extract audio only
ffmpeg -i input.mp4 -vn -c:a copy output.m4a
Troubleshooting
Error opening input file desktop. (Windows)
-f gdigrab is missing. Without it, FFmpeg looks for a file named desktop and stops with No such file or directory. Put -f gdigrab before -i desktop.
# Wrong
ffmpeg -i desktop output.mp4
# Correct
ffmpeg -f gdigrab -framerate 30 -i desktop -pix_fmt yuv420p output.mp4
Permission denied / black screen (macOS)
The terminal does not have permission to record the screen. Open System Settings → Privacy & Security → Screen & System Audio Recording (Screen Recording on macOS 13), turn on your terminal app, then quit it completely and open it again.
Cannot open display :0 / BadAccess (Linux)
You are on Wayland, or you are connected over SSH without X forwarding.
echo $XDG_SESSION_TYPE
If it prints wayland, log in again with an X11 session or use wf-recorder instead.
Dropped frames (frame= XXX fps= 5)
The encoder cannot keep up with the capture. Try these 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_vaapineeds extra options; see the VAAPI guide.
Audio and video drift apart
The audio and video inputs run on different clocks. Add -af aresample=async=1000. It stretches or squeezes the audio by up to 1000 samples per second so that it matches its timestamps:
ffmpeg -f gdigrab -framerate 30 -i desktop -f dshow -i audio="Microphone" -af aresample=async=1000 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac output.mp4
For long recordings, also add -use_wallclock_as_timestamps 1 to each input to keep the timing steadier.
FAQ
Why is my recording huge?
You probably used -c:v copy, or kept the -preset ultrafast recording as it is. libx264 already uses CRF 23 when -crf is left out, and ultrafast makes larger files in exchange for speed. Re-encode afterwards with -preset medium -crf 24. The video compression guide has more options.
How do I include the cursor?
gdigrab and x11grab record the cursor by default; add -draw_mouse 0 before -i to hide it. avfoundation needs -capture_cursor 1 to show it.
I can’t record at 60fps
Set -framerate 60 on the input and make the encoder’s work lighter. If ultrafast still falls behind, switch to a GPU encoder (NVENC / VideoToolbox / QSV / VAAPI); that helps more than anything else. With ddagrab and h264_nvenc, both capture and encoding run on the GPU, so the CPU load drops.
Mix system audio + microphone?
Capture system audio through a virtual device (VB-CABLE, BlackHole or a PulseAudio monitor) and mix it with the microphone using -filter_complex amix=inputs=2. The full command is in the “Capturing System Audio” section.
Compared to OBS Studio?
OBS is a GUI app that does scene switching, streaming and recording in one place. FFmpeg is better when the recording has to run from a script or without a GUI, for example on a server or in CI. It also makes it easy to use the same recording settings on many machines, or to send the video straight to a streaming service. If all you need is to record and push to RTMP, FFmpeg does that too (see RTMP streaming with FFmpeg).
How do I get it under Discord’s upload limit?
Discord’s limit depends on your plan and can change, so check the limit shown on your upload screen. Then re-encode the recording as described in Compress video for Discord with FFmpeg.