Blog

Capturando la pantalla con encoding GPU y FFmpeg

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