Capturando la pantalla con encoding GPU y FFmpeg
Quería hacer un screencast en Linux para grabar una demo. Estuve probando con SimpleScreenRecorder y ffmpeg -f x11grab
, pero mi CPU es antigua y no podía grabar con soltura mientras hacía la demo, a pesar de utilizar encoding GPU (h264_vaapi
)
En la documentación de FFmpeg encontré la opción de capturar empleando kmsgrab en lugar de x11grab. La ventaja es un uso muy reducido de CPU:
Capture the screen from the first active KMS plane:
ffmpeg -device /dev/dri/card0 -f kmsgrab -i - -vf 'hwmap=derive_device=vaapi,scale_vaapi=w=1920:h=1080:format=nv12' -c:v h264_vaapi -qp 24 output.mp4
Compared to capturing through X as in the previous examples, this should use much less CPU (all surfaces stay on the GPU side) and can work outside X (on VTs or in Wayland), but can only capture whole planes and requires DRM master or CAP_SYS_ADMIN to run.
En mi caso, acabé con este comando:
sudo ffmpeg -framerate 60 -f kmsgrab -i - -vsync 0 -init_hw_device vaapi=v:/dev/dri/renderD128 -vf 'hwmap=derive_device=vaapi,scale_vaapi=1920:1200:nv12' -c:v h264_vaapi -profile:v main -bf 0 out.mp4
Aspectos a tener en cuenta:
- No pude utilizar
-profile:v high
porque duplicaba frames y se perdían casi todos, yendo a tirones. Imagino que el causante del problema es el driver de la tarjeta gráfica (amdgpu).
- sudo es necesario por lo que comenta en «requires DRM master or CAP_SYS_ADMIN to run.»
- El puntero del ratón no se graba.
- Tengo 3 pantallas de 1920×1200 y no pude emplear el filtro crop para grabar sólo una, como con x11grab. Me daba error de filtro «Impossible to convert between the formats supported by the filter ‘Parsed_crop_1’ and the filter ‘auto_scaler_0′». Simplemente apagué dos pantallas mientras hacía la demo.
- La opción
-init_hw_device
era necesaria, de lo contrario me devolvía «No handle set on framebuffer: maybe you need some additional capabilities?»
El mayor inconveniente es que el puntero de ratón no se graba, pero no me importó hacer ese sacrificio porque la calidad del vídeo era alta (bitrate bueno y 60fps).
Actualización: he conseguido grabar con varias pantallas encendidas:
sudo env PULSE_COOKIE=$HOME/.config/pulse/cookie PULSE_SERVER=$XDG_RUNTIME_DIR/pulse/native \
ffmpeg -y \
-f pulse -thread_queue_size 1024 -i alsa_input.usb-046d_Logitech_BRIO_69515057-02.analog-stereo \
-crtc_id 47 -framerate 60 -f kmsgrab -thread_queue_size 1024 -i - -vsync 0 -init_hw_device vaapi=v:/dev/dri/renderD128 \
-vaapi_device /dev/dri/renderD128 \
-vf 'hwmap=derive_device=vaapi,crop=1920:1200:0:327,scale_vaapi=1920:1200:nv12,hwupload' \
-c:v h264_vaapi -profile:v main -bf 0 \
out.mp4
Podemos obtener los crtcs haciendo sudo cat /sys/kernel/debug/dri/0/state
. El 0:327
es porque tengo una de las pantallas en vertical y las horizontales están «debajo», en un offset de 327 px.
Aquí estoy capturando audio también. Como estoy ejecutando desde sudo, root no tiene la cookie de pulseaudio y proporciono la mía con las variables de entorno. Ver este enlace.
Un problemilla que he tenido con el audio es que no está sincronizado con el vídeo. Lo he apañado así:
ffmpeg -i out.mp4 -itsoffset -1.2 -i out.mp4 -vcodec copy -acodec copy -map 0:0 -map 1:1 out_delay.mp4
Podemos capturar la salida de audio (aplicaciones) del ordenador listando y buscando la línea «Name»:
j ~ Videos pactl list sources
Source #1
State: RUNNING
Name: alsa_output.pci-0000_00_1b.0.iec958-stereo.monitor
Description: Monitor of Built-in Audio Digital Stereo (IEC958)
Driver: module-alsa-card.c
Sample Specification: s16le 2ch 44100Hz
Channel Map: front-left,front-right
Owner Module: 7
Mute: no
Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
balance 0.00
Base Volume: 65536 / 100% / 0.00 dB
Monitor of Sink: alsa_output.pci-0000_00_1b.0.iec958-stereo
Latency: 0 usec, configured 46000 usec
Flags: DECIBEL_VOLUME LATENCY
Properties:
device.description = "Monitor of Built-in Audio Digital Stereo (IEC958)"
device.class = "monitor"
alsa.card = "0"
alsa.card_name = "HDA Intel"
alsa.long_card_name = "HDA Intel at 0xffafc000 irq 31"
alsa.driver_name = "snd_hda_intel"
device.bus_path = "pci-0000:00:1b.0"
sysfs.path = "/devices/pci0000:00/0000:00:1b.0/sound/card0"
device.bus = "pci"
device.vendor.id = "8086"
device.vendor.name = "Intel Corporation"
device.product.id = "27d8"
device.product.name = "NM10/ICH7 Family High Definition Audio Controller"
device.form_factor = "internal"
device.string = "0"
module-udev-detect.discovered = "1"
device.icon_name = "audio-card-pci"
Formats:
pcm
Source #2
State: RUNNING
Name: alsa_input.usb-046d_Logitech_BRIO_69515057-02.analog-stereo
Description: Logitech BRIO Analog Stereo
Driver: module-alsa-card.c
Sample Specification: s16le 2ch 48000Hz
Channel Map: front-left,front-right
Owner Module: 8
Mute: no
Volume: front-left: 60138 / 92% / -2.24 dB, front-right: 60138 / 92% / -2.24 dB
balance 0.00
Base Volume: 8250 / 13% / -54.00 dB
Monitor of Sink: n/a
Latency: 1782 usec, configured 40000 usec
Flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY
Properties:
alsa.resolution_bits = "16"
device.api = "alsa"
device.class = "sound"
alsa.class = "generic"
alsa.subclass = "generic-mix"
alsa.name = "USB Audio"
alsa.id = "USB Audio"
alsa.subdevice = "0"
alsa.subdevice_name = "subdevice #0"
alsa.device = "0"
alsa.card = "2"
alsa.card_name = "Logitech BRIO"
alsa.long_card_name = "Logitech BRIO at usb-0000:00:1d.7-7.1, high speed"
alsa.driver_name = "snd_usb_audio"
device.bus_path = "pci-0000:00:1d.7-usb-0:7.1:1.2"
sysfs.path = "/devices/pci0000:00/0000:00:1d.7/usb1/1-7/1-7.1/1-7.1:1.2/sound/card2"
udev.id = "usb-046d_Logitech_BRIO_69515057-02"
device.bus = "usb"
device.vendor.id = "046d"
device.vendor.name = "Logitech, Inc."
device.product.id = "085e"
device.product.name = "Logitech BRIO"
device.serial = "046d_Logitech_BRIO_69515057"
device.form_factor = "webcam"
device.string = "front:2"
device.buffering.buffer_size = "384000"
device.buffering.fragment_size = "192000"
device.access_mode = "mmap+timer"
device.profile.name = "analog-stereo"
device.profile.description = "Analog Stereo"
device.description = "Logitech BRIO Analog Stereo"
alsa.mixer_name = "USB Mixer"
alsa.components = "USB046d:085e"
module-udev-detect.discovered = "1"
device.icon_name = "camera-web-usb"
Ports:
analog-input-mic: Microphone (priority: 8700)
Active Port: analog-input-mic
Formats:
pcm
Source #14
State: RUNNING
Name: alsa_output.pci-0000_06_00.1.hdmi-stereo-extra2.monitor
Description: Monitor of Ellesmere HDMI Audio [Radeon RX 470/480 / 570/580/590] Digital Stereo (HDMI 3)
Driver: module-alsa-card.c
Sample Specification: s16le 2ch 44100Hz
Channel Map: front-left,front-right
Owner Module: 6
Mute: no
Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
balance 0.00
Base Volume: 65536 / 100% / 0.00 dB
Monitor of Sink: alsa_output.pci-0000_06_00.1.hdmi-stereo-extra2
Latency: 0 usec, configured 40000 usec
Flags: DECIBEL_VOLUME LATENCY
Properties:
device.description = "Monitor of Ellesmere HDMI Audio [Radeon RX 470/480 / 570/580/590] Digital Stereo (HDMI 3)"
device.class = "monitor"
alsa.card = "1"
alsa.card_name = "HDA ATI HDMI"
alsa.long_card_name = "HDA ATI HDMI at 0xff99c000 irq 32"
alsa.driver_name = "snd_hda_intel"
device.bus_path = "pci-0000:06:00.1"
sysfs.path = "/devices/pci0000:00/0000:00:01.0/0000:06:00.1/sound/card1"
device.bus = "pci"
device.vendor.id = "1002"
device.vendor.name = "Advanced Micro Devices, Inc. [AMD/ATI]"
device.product.id = "aaf0"
device.product.name = "Ellesmere HDMI Audio [Radeon RX 470/480 / 570/580/590]"
device.string = "1"
module-udev-detect.discovered = "1"
device.icon_name = "audio-card-pci"
Formats:
pcm