FFmpeg es la herramienta de referencia para convertir, recortar y empaquetar vídeo y audio desde la línea de comandos. En este tutorial convertirás un servidor Ubuntu 24.04 en un nodo de transcodificación: instalarás FFmpeg, elegirás los parámetros de calidad adecuados para H.264 y HEVC, generarás un paquete HLS con varias calidades y, si el servidor tiene GPU, usarás aceleración por hardware con VAAPI (Intel/AMD) o NVENC (NVIDIA). Al final dejarás un proceso por lotes que convierte automáticamente los vídeos que aparecen en una carpeta.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS o un servidor dedicado de CubePath.
- Un usuario no root con privilegios
sudo. - CPU con 4 núcleos o más: la codificación por software con
libx264ylibx265escala casi linealmente con los núcleos. - Espacio en disco rápido (SSD o NVMe) para los originales y los ficheros generados.
- Opcional: una GPU Intel, AMD o NVIDIA en un servidor dedicado para los pasos de aceleración por hardware. Un VPS normal no tiene GPU, así que en ese caso puedes saltarte los pasos 4 y 5.
Paso 1: Instalar FFmpeg
Ubuntu 24.04 incluye FFmpeg 6.1 en sus repositorios, compilado con libx264, libx265, libvpx, libopus, libmp3lame y soporte para VAAPI y NVENC. Para un servidor de transcodificación es suficiente y se actualiza con el resto del sistema:
sudo apt update
sudo apt install ffmpeg
Comprueba la versión instalada:
ffmpeg -hide_banner -version | head -n 1
ffmpeg version 6.1.1-3ubuntu5 Copyright (c) 2000-2023 the FFmpeg developers
Verifica también qué codificadores de vídeo tienes disponibles:
ffmpeg -hide_banner -encoders | grep -E "libx264|libx265|libvpx-vp9|nvenc|vaapi"
Deberías ver al menos libx264, libx265 y libvpx-vp9, además de los codificadores h264_nvenc, hevc_nvenc, h264_vaapi y hevc_vaapi. Que aparezcan en la lista solo significa que FFmpeg sabe usarlos; para que funcionen hace falta el hardware y el controlador correspondientes.
Crea una estructura de carpetas para trabajar:
sudo mkdir -p /srv/transcode/{entrada,salida,hls}
sudo chown -R $USER:$USER /srv/transcode
Copia en /srv/transcode/entrada un vídeo de prueba con el nombre muestra.mp4 para seguir los ejemplos.
Paso 2: Transcodificar a H.264 y HEVC con calidad constante
Para archivos que se van a almacenar o descargar, el modo recomendado es CRF (Constant Rate Factor): fijas un nivel de calidad y el codificador usa los bits que necesite en cada escena. En libx264 el rango útil es de 18 (casi sin pérdida visual) a 28, con 23 como valor por defecto. El preset decide cuánto tiempo de CPU se invierte: slow comprime mejor que medium, veryfast es mucho más rápido pero genera ficheros más grandes.
ffmpeg -hide_banner -i /srv/transcode/entrada/muestra.mp4 \
-c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p \
-c:a aac -b:a 160k \
-movflags +faststart \
/srv/transcode/salida/muestra-h264.mp4
-pix_fmt yuv420p garantiza compatibilidad con navegadores y móviles, y -movflags +faststart mueve el índice al principio del fichero para que la reproducción web empiece antes de descargarlo entero.
HEVC (H.265) consigue una calidad similar con un 30 a 50 % menos de bitrate, a cambio de mucha más CPU. Su escala CRF es distinta: 28 en libx265 equivale aproximadamente a 23 en libx264:
ffmpeg -hide_banner -i /srv/transcode/entrada/muestra.mp4 \
-c:v libx265 -crf 28 -preset medium -tag:v hvc1 \
-c:a aac -b:a 160k \
-movflags +faststart \
/srv/transcode/salida/muestra-hevc.mp4
La etiqueta -tag:v hvc1 es necesaria para que Safari y los dispositivos Apple reproduzcan el fichero.
Comprueba el resultado con ffprobe, que se instala junto con FFmpeg:
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,bit_rate \
-of default=noprint_wrappers=1 /srv/transcode/salida/muestra-h264.mp4
codec_name=h264
width=1920
height=1080
bit_rate=4213760
Si necesitas un bitrate fijo (por ejemplo, para un destino con ancho de banda limitado), usa -b:v 3000k -maxrate 3000k -bufsize 6000k en lugar de -crf.
Paso 3: Generar HLS con varias calidades
HLS (HTTP Live Streaming) divide el vídeo en segmentos cortos y ofrece varias calidades para que el reproductor cambie de una a otra según la conexión. Un único comando de FFmpeg puede escalar el vídeo a tres resoluciones, codificar cada una y escribir la lista maestra:
cd /srv/transcode/hls
ffmpeg -hide_banner -i /srv/transcode/entrada/muestra.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3];[v1]scale=-2:1080[v1out];[v2]scale=-2:720[v2out];[v3]scale=-2:480[v3out]" \
-map "[v1out]" -c:v:0 libx264 -b:v:0 5000k -maxrate:v:0 5350k -bufsize:v:0 7500k \
-map "[v2out]" -c:v:1 libx264 -b:v:1 2800k -maxrate:v:1 2996k -bufsize:v:1 4200k \
-map "[v3out]" -c:v:2 libx264 -b:v:2 1400k -maxrate:v:2 1498k -bufsize:v:2 2100k \
-map a:0 -map a:0 -map a:0 -c:a aac -b:a 128k -ac 2 \
-preset veryfast -g 48 -keyint_min 48 -sc_threshold 0 \
-f hls -hls_time 6 -hls_playlist_type vod -hls_flags independent_segments \
-hls_segment_filename "stream_%v/segmento_%03d.ts" \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
"stream_%v/playlist.m3u8"
Los puntos importantes son:
scale=-2:720ajusta la altura y calcula un ancho par que mantiene la proporción.-g 48 -keyint_min 48 -sc_threshold 0fuerza fotogramas clave a intervalos fijos para que los segmentos de todas las calidades estén alineados.-var_stream_mapagrupa cada pista de vídeo con su pista de audio;%vse sustituye por 0, 1 y 2.
Comprueba que se han creado la lista maestra y las tres variantes:
cat /srv/transcode/hls/master.m3u8
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=5640800,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
stream_0/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3220800,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
stream_1/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1680800,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2"
stream_2/playlist.m3u8
El directorio /srv/transcode/hls se puede servir tal cual con cualquier servidor web (Nginx, Caddy) o subir a un almacenamiento de objetos detrás de una CDN.
Paso 4: Aceleración por hardware con VAAPI (Intel y AMD)
VAAPI permite usar el bloque de codificación de las GPU Intel (integradas o Arc) y AMD. Instala la utilidad de diagnóstico y el controlador correspondiente. En Intel, el paquete intel-media-va-driver-non-free está en el repositorio multiverse e incluye todas las funciones de codificación:
sudo apt install vainfo intel-media-va-driver-non-free
En AMD, el controlador está en Mesa:
sudo apt install vainfo mesa-va-drivers
El dispositivo de la GPU aparece en /dev/dri/renderD128 y solo pueden usarlo los miembros del grupo render. Añade tu usuario al grupo y vuelve a iniciar sesión para que se aplique:
sudo usermod -aG render $USER
Comprueba que el controlador carga y qué perfiles admite codificar (las líneas con VAEntrypointEncSlice):
vainfo --display drm --device /dev/dri/renderD128 | grep -E "Driver version|EncSlice"
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 24.1.0 ()
VAProfileH264Main : VAEntrypointEncSlice
VAProfileH264High : VAEntrypointEncSlice
VAProfileHEVCMain : VAEntrypointEncSlice
Con el dispositivo operativo, este comando decodifica y codifica en la GPU sin pasar los fotogramas por la memoria del sistema, y reescala a 720p con el filtro scale_vaapi:
ffmpeg -hide_banner \
-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi \
-i /srv/transcode/entrada/muestra.mp4 \
-vf "scale_vaapi=w=1280:h=720" \
-c:v h264_vaapi -qp 23 \
-c:a aac -b:a 160k -movflags +faststart \
/srv/transcode/salida/muestra-vaapi.mp4
Para HEVC, cambia h264_vaapi por hevc_vaapi. Si el códec de origen no se puede decodificar en la GPU, FFmpeg mostrará un error de formato; en ese caso decodifica por software y sube los fotogramas a la GPU:
ffmpeg -hide_banner -vaapi_device /dev/dri/renderD128 \
-i /srv/transcode/entrada/muestra.mp4 \
-vf "format=nv12,hwupload" \
-c:v h264_vaapi -qp 23 \
-c:a aac -b:a 160k \
/srv/transcode/salida/muestra-vaapi-sw.mp4
Paso 5: Aceleración por hardware con NVENC (NVIDIA)
NVENC es el codificador dedicado de las GPU NVIDIA. FFmpeg lo carga en tiempo de ejecución a través del controlador propietario, así que lo único que necesitas es instalar el controlador recomendado por Ubuntu:
sudo apt install ubuntu-drivers-common
sudo ubuntu-drivers install
sudo reboot
Tras reiniciar, comprueba que el controlador reconoce la GPU:
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
NVIDIA L4, 550.120
Este comando decodifica con la GPU (-hwaccel cuda), reescala en la GPU con scale_cuda y codifica con h264_nvenc usando control de bitrate variable. Los presets de NVENC van de p1 (más rápido) a p7 (mejor calidad):
ffmpeg -hide_banner \
-hwaccel cuda -hwaccel_output_format cuda \
-i /srv/transcode/entrada/muestra.mp4 \
-vf "scale_cuda=1280:720" \
-c:v h264_nvenc -preset p5 -rc vbr -cq 23 -b:v 0 \
-c:a aac -b:a 160k -movflags +faststart \
/srv/transcode/salida/muestra-nvenc.mp4
Para HEVC, usa hevc_nvenc y añade -tag:v hvc1. Mientras el comando se ejecuta, en otra terminal puedes confirmar que la GPU está trabajando:
nvidia-smi dmon -s u -c 5
Las columnas enc y dec deberían mostrar porcentajes de uso mayores que cero.
Paso 6: Automatizar la transcodificación por lotes
Para procesar todo lo que llegue a /srv/transcode/entrada, crea un script que convierta cada vídeo nuevo a H.264, se salte los que ya estén convertidos y no se ejecute dos veces a la vez:
sudo nano /usr/local/bin/transcode-batch.sh
#!/usr/bin/env bash
set -euo pipefail
ENTRADA="/srv/transcode/entrada"
SALIDA="/srv/transcode/salida"
# Evita dos ejecuciones simultáneas
exec 9>/run/lock/transcode-batch.lock
flock -n 9 || { echo "Ya hay una ejecución en curso"; exit 0; }
find "$ENTRADA" -maxdepth 1 -type f \( -iname '*.mp4' -o -iname '*.mkv' -o -iname '*.mov' -o -iname '*.avi' \) -print0 |
while IFS= read -r -d '' origen; do
nombre="$(basename "${origen%.*}")"
destino="$SALIDA/${nombre}.mp4"
temporal="$SALIDA/.${nombre}.part.mp4"
if [[ -f "$destino" ]]; then
continue
fi
echo "Transcodificando: $origen"
if nice -n 10 ffmpeg -hide_banner -nostdin -loglevel error -y -i "$origen" \
-c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p \
-c:a aac -b:a 160k -movflags +faststart \
"$temporal"; then
mv "$temporal" "$destino"
echo "Completado: $destino"
else
rm -f "$temporal"
echo "ERROR al transcodificar: $origen" >&2
fi
done
El fichero se escribe primero con un nombre temporal y solo se renombra cuando FFmpeg termina bien, así nunca queda un vídeo a medias con el nombre final. -nostdin evita que FFmpeg consuma la entrada del bucle. Si tienes GPU, sustituye las opciones de vídeo por las de los pasos 4 o 5.
Hazlo ejecutable y pruébalo a mano:
sudo chmod 755 /usr/local/bin/transcode-batch.sh
/usr/local/bin/transcode-batch.sh
Transcodificando: /srv/transcode/entrada/muestra.mp4
Completado: /srv/transcode/salida/muestra.mp4
Ahora crea un servicio de systemd que ejecute el script con tu usuario. Sustituye tu_usuario por el nombre de tu usuario:
sudo nano /etc/systemd/system/transcode-batch.service
[Unit]
Description=Transcodificación por lotes con FFmpeg
[Service]
Type=oneshot
User=tu_usuario
ExecStart=/usr/local/bin/transcode-batch.sh
IOSchedulingClass=idle
Y un timer que lo lance cada 10 minutos:
sudo nano /etc/systemd/system/transcode-batch.timer
[Unit]
Description=Ejecuta la transcodificación por lotes cada 10 minutos
[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
[Install]
WantedBy=timers.target
Activa el timer y comprueba cuándo se ejecutará:
sudo systemctl daemon-reload
sudo systemctl enable --now transcode-batch.timer
systemctl list-timers transcode-batch.timer
Si quieres usar VAAPI desde el servicio, añade SupplementaryGroups=render en la sección [Service]. La salida de cada ejecución queda en el journal:
journalctl -u transcode-batch.service -n 20
Solución de problemas
Unknown encoder 'libfdk_aac' u otro codificador que no existe. El FFmpeg de Ubuntu no incluye códecs con licencia no libre como libfdk_aac. Usa el codificador AAC nativo (-c:a aac), cuya calidad es buena a partir de 128 kbps.
Failed to initialise VAAPI connection o No VA display found. Comprueba que existe /dev/dri/renderD128, que el paquete del controlador está instalado y que tu usuario pertenece al grupo render (id debe mostrarlo; si no, cierra la sesión y vuelve a entrar).
Cannot load libcuda.so.1 o No capable devices found con NVENC. El controlador de NVIDIA no está cargado. Revisa nvidia-smi; si falla, reinstala con sudo ubuntu-drivers install y reinicia. Las GPU de consumo tienen además un límite de sesiones NVENC simultáneas.
La transcodificación es muy lenta. Revisa con htop si la CPU está al 100 %. Un preset más rápido (-preset veryfast) reduce mucho el tiempo a costa de ficheros algo mayores. Si esperabas usar la GPU y la CPU está saturada, comprueba en la salida de FFmpeg que el codificador es h264_vaapi o h264_nvenc y no libx264.
Conclusión
Ya tienes un servidor que transcodifica a H.264 y HEVC con calidad controlada, genera paquetes HLS multicalidad y procesa automáticamente los vídeos nuevos con un timer de systemd, con aceleración por GPU cuando el hardware lo permite. Como siguientes pasos puedes servir el directorio HLS con Nginx y HTTPS, subir la salida a un almacenamiento de objetos compatible con S3 o añadir la generación de miniaturas con el filtro thumbnail de FFmpeg al script de lotes.
