Convertir videos a HLS y MPEG-DASH con Rust
El streaming adaptativo es fundamental para entregar contenido de video de alta calidad a distintos dispositivos bajo diferentes condiciones de red. Este DevTip muestra cómo convertir videos a los formatos HLS (HTTP Live Streaming) y MPEG-DASH (Dynamic Adaptive Streaming over HTTP) con Rust y FFmpeg.
Introducción a HLS y MPEG-DASH
HLS y MPEG-DASH son protocolos populares de streaming adaptativo que dividen los archivos de video en segmentos pequeños y ofrecen varias opciones de bitrate. Esta segmentación permite que los clientes cambien sin interrupciones a la calidad óptima según las condiciones de red actuales y las capacidades del dispositivo.
Entender el streaming adaptativo
El streaming adaptativo normalmente implica:
- Codificar el video con varios bitrates y resoluciones.
- Dividir cada versión codificada en segmentos cortos.
- Generar archivos de manifiesto que detallan los streams y segmentos disponibles.
- Permitir que los clientes seleccionen automáticamente la calidad más adecuada durante la reproducción.
Configurar un proyecto de Rust
Comienza instalando las dependencias del sistema necesarias:
sudo apt-get update
sudo apt-get install ffmpeg
Crea un nuevo proyecto de Rust:
cargo new video_converter
cd video_converter
Agrega estas dependencias a tu archivo Cargo.toml:
[dependencies]
anyhow = "1.0.75"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
warp = "0.3.7"
Integrar FFmpeg con Rust
Estos ejemplos ejecutan el ejecutable de FFmpeg, por lo que no se necesitan encabezados de
desarrollo de FFmpeg ni bindings de Rust. Empieza src/main.rs con una comprobación de que FFmpeg
esté disponible:
use anyhow::Result;
use std::process::Command;
fn check_ffmpeg() -> Result<()> {
let status = Command::new("ffmpeg").arg("-version").status()?;
anyhow::ensure!(status.success(), "FFmpeg version check failed");
Ok(())
}
fn main() -> Result<()> {
check_ffmpeg()?;
Ok(())
}
Implementar la conversión de video a HLS y MPEG-DASH
Para convertir videos a HLS y MPEG-DASH, invocamos las herramientas de línea de comandos de FFmpeg a
través del módulo Command de Rust. Este enfoque aprovecha la potente CLI de FFmpeg y mantiene
sencillo el código de Rust. Usa una entrada con un stream de video y un stream de audio. El ejemplo
de DASH asume un video 16:9 de al menos 1280×720. Cada conversión requiere un nuevo directorio de
salida para evitar sobrescribir una conversión anterior; su directorio padre ya debe existir.
Convertir a HLS
Usa la siguiente función para convertir un archivo de video al formato HLS. Crea el directorio de
salida necesario e invoca a FFmpeg con parámetros adaptados a la generación de HLS. Agrega esta
función debajo de las importaciones en src/main.rs. Este ejemplo de HLS crea una sola variante;
su lista de reproducción maestra apunta a una lista de reproducción de medios independiente:
fn convert_to_hls(input: &str, output_dir: &str) -> Result<()> {
std::fs::create_dir(output_dir)?;
let output_path = format!("{}/index.m3u8", output_dir);
let status = Command::new("ffmpeg")
.args(&[
"-nostdin", "-n",
"-i", input,
"-map", "0:v:0", "-map", "0:a:0",
"-c:v", "libx264",
"-c:a", "aac",
"-profile:v", "main",
"-preset", "medium",
"-crf", "23",
"-sc_threshold", "0",
"-force_key_frames", "expr:gte(t,n_forced*4)",
"-hls_time", "4",
"-hls_playlist_type", "vod",
"-hls_segment_filename", &format!("{}/segment_%03d.ts", output_dir),
"-master_pl_name", "master.m3u8",
"-var_stream_map", "v:0,a:0",
"-f", "hls",
&output_path,
])
.status()?;
if !status.success() {
return Err(anyhow::anyhow!("FFmpeg process failed"));
}
Ok(())
}
Convertir a MPEG-DASH
De forma similar, la siguiente función convierte un archivo de video al formato MPEG-DASH. Configura el nombrado de segmentos y la generación del manifiesto de manera adecuada para el streaming DASH. Agrégala al mismo archivo. Mapear dos veces el primer stream de video crea dos variantes de video, con fotogramas clave alineados de cuatro segundos:
fn convert_to_mpeg_dash(input: &str, output_dir: &str) -> Result<()> {
std::fs::create_dir(output_dir)?;
let output_path = format!("{}/manifest.mpd", output_dir);
let status = Command::new("ffmpeg")
.args(&[
"-nostdin", "-n",
"-i", input,
"-map", "0:v:0",
"-map", "0:v:0",
"-map", "0:a:0",
"-c:v", "libx264",
"-c:a", "aac",
"-b:v:0", "2M",
"-b:v:1", "1M",
"-s:v:1", "1280x720",
"-profile:v", "main",
"-sc_threshold", "0",
"-force_key_frames", "expr:gte(t,n_forced*4)",
"-seg_duration", "4",
"-use_template", "1",
"-use_timeline", "1",
"-init_seg_name", "init-$RepresentationID$.m4s",
"-media_seg_name", "chunk-$RepresentationID$-$Number%05d$.m4s",
"-adaptation_sets", "id=0,streams=v id=1,streams=a",
"-f", "dash",
&output_path,
])
.status()?;
if !status.success() {
return Err(anyhow::anyhow!("FFmpeg process failed"));
}
Ok(())
}
Una conversión fallida puede dejar archivos parciales en su nuevo directorio de salida. Vuelve a intentarlo con un directorio nuevo y sirve los archivos solo después de que ambas conversiones terminen correctamente.
Crear un servidor de streaming simple en Rust
Después de convertir tus videos, puedes servir el contenido HLS o MPEG-DASH con un servidor HTTP
básico creado con el crate warp. Guarda este ejecutable independiente como src/bin/server.rs
(crea antes src/bin). El servidor asigna tipos de contenido de streaming estándar a las
listas de reproducción y a los segmentos MPEG-TS, y conserva el tipo de contenido inferido por Warp
para los demás archivos:
use warp::{Filter, Reply};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cors = warp::cors()
.allow_any_origin()
.allow_methods(vec!["GET", "HEAD", "OPTIONS"]);
let routes = warp::fs::dir("./output")
.map(|file: warp::fs::File| {
let content_type = match file.path().extension().and_then(|ext| ext.to_str()) {
Some("m3u8") => "application/vnd.apple.mpegurl",
Some("mpd") => "application/dash+xml",
Some("ts") => "video/mp2t",
_ => return file.into_response(),
};
let mut response = file.into_response();
response.headers_mut().insert(
warp::http::header::CONTENT_TYPE,
warp::http::HeaderValue::from_static(content_type),
);
response
})
.with(cors);
println!("Starting server at http://127.0.0.1:3030/");
warp::serve(routes)
.run(([127, 0, 0, 1], 3030))
.await;
Ok(())
}
Probar la configuración de streaming
Para probar tu solución de streaming:
-
Reemplaza
mainensrc/main.rspor lo siguiente, conservando las funciones anteriores:fn main() -> Result<()> { check_ffmpeg()?; std::fs::create_dir_all("./output")?; convert_to_hls("input.mp4", "./output/hls")?; convert_to_mpeg_dash("input.mp4", "./output/dash")?; println!("Conversion complete."); Ok(()) } -
Ejecuta el convertidor una vez y luego inicia el servidor de streaming desde el directorio del proyecto:
cargo run --bin video_converter && cargo run --bin server -
Usa un reproductor compatible, como VLC, o bibliotecas web como hls.js o dash.js, para reproducir el stream desde:
Conclusión
Este DevTip mostró cómo convertir videos a los formatos HLS y MPEG-DASH con Rust y FFmpeg. Vimos cómo generar listas de reproducción y archivos de manifiesto, y cómo configurar un servidor de streaming básico para el streaming adaptativo. Para una solución más robusta, considera las siguientes mejoras:
- Amplía el ejemplo de HLS de una sola variante con varias variantes alineadas para la reproducción adaptativa.
- Experimenta con ajustes avanzados de FFmpeg para optimizar la calidad del video.
- Agrega compatibilidad con funciones adicionales, como varias pistas de audio y subtítulos.
- Protege tu configuración de streaming con HTTPS y autenticación basada en tokens.
Si buscas una solución gestionada para el encoding de video y el streaming adaptativo, Transloadit ofrece potentes capacidades de procesamiento de video que escalan para satisfacer tus necesidades.
