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

  1. Run the command once in Terminal (or iTerm) so that macOS asks for permission.
  2. Open System Settings → Privacy & Security → Screen & System Audio Recording (on macOS 13 it is called Screen Recording) and turn on your terminal app.
  3. 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:

  1. A recorder that supports Wayland (recommended): record with wf-recorder (wlroots) or gpu-screen-recorder, then process the file with FFmpeg.
  2. kmsgrab: works, but needs root or CAP_SYS_ADMIN.
  3. 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

  1. Install VB-CABLE.
  2. In Windows Sound settings, set 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 -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

  1. brew install blackhole-2ch
  2. In Audio MIDI Setup, create a Multi-Output Device that includes your speakers and BlackHole 2ch.
  3. Set the system sound output to that Multi-Output Device.
  4. 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.

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:

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