Converter vídeos para HLS e MPEG-DASH com Rust
O streaming adaptativo é essencial para entregar conteúdo de vídeo de alta qualidade a diversos dispositivos em diferentes condições de rede. Este DevTip mostra como converter vídeos para os formatos HLS (HTTP Live Streaming) e MPEG-DASH (Dynamic Adaptive Streaming over HTTP) usando Rust e FFmpeg.
Introdução ao HLS e ao MPEG-DASH
HLS e MPEG-DASH são protocolos populares de streaming adaptativo que dividem arquivos de vídeo em pequenos segmentos e oferecem várias opções de taxa de bits. Essa segmentação permite que os clientes alternem sem interrupções para a qualidade ideal, de acordo com as condições atuais da rede e os recursos do dispositivo.
Entendendo o streaming adaptativo
O streaming adaptativo normalmente envolve:
- Codificar o vídeo em várias taxas de bits e resoluções.
- Dividir cada versão codificada em segmentos curtos.
- Gerar arquivos de manifesto que descrevem os streams e segmentos disponíveis.
- Permitir que os clientes selecionem automaticamente a qualidade mais adequada durante a reprodução.
Configurando um projeto Rust
Comece instalando as dependências de sistema necessárias:
sudo apt-get update
sudo apt-get install ffmpeg
Crie um novo projeto Rust:
cargo new video_converter
cd video_converter
Adicione estas dependências ao seu arquivo Cargo.toml:
[dependencies]
anyhow = "1.0.75"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
warp = "0.3.7"
Integrando o FFmpeg com Rust
Estes exemplos executam o binário do FFmpeg, então você não precisa dos headers de desenvolvimento do
FFmpeg nem de bindings para Rust. Comece o src/main.rs com uma verificação de que o FFmpeg está
disponível:
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(())
}
Implementando a conversão de vídeo para HLS e MPEG-DASH
Para converter vídeos para HLS e MPEG-DASH, chamamos as ferramentas de linha de comando do FFmpeg
por meio do módulo Command do Rust. Essa abordagem aproveita a poderosa CLI do FFmpeg e mantém
o código Rust simples. Use uma entrada com um stream de vídeo e um stream de áudio. O exemplo de DASH
pressupõe um vídeo 16:9 de pelo menos 1280×720. Cada conversão exige um novo diretório de saída para
não sobrescrever uma conversão anterior, e o diretório pai dele já precisa existir.
Convertendo para HLS
Use a função a seguir para converter um arquivo de vídeo para o formato HLS. Ela cria o diretório de
saída necessário e chama o FFmpeg com parâmetros ajustados para a geração de HLS. Adicione esta
função abaixo dos imports em src/main.rs. Este exemplo de HLS cria uma única rendition, e a playlist
mestre dele aponta para uma playlist de mídia separada:
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(())
}
Convertendo para MPEG-DASH
Da mesma forma, a função a seguir converte um arquivo de vídeo para o formato MPEG-DASH. Ela configura a nomeação dos segmentos e a geração do manifesto de forma adequada ao streaming DASH. Adicione-a ao mesmo arquivo. Mapear duas vezes o primeiro stream de vídeo cria duas representações de vídeo, com quadros-chave alinhados a cada quatro 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(())
}
Uma conversão com falha pode deixar arquivos parciais no novo diretório de saída. Tente novamente com um diretório novo e só sirva os arquivos depois que as duas conversões terminarem com sucesso.
Criando um servidor de streaming simples em Rust
Depois de converter seus vídeos, você pode servir o conteúdo HLS ou MPEG-DASH com um servidor HTTP
básico criado com o crate warp. Salve este executável separado como src/bin/server.rs (crie
src/bin antes). O servidor atribui content types padrão de streaming às playlists e aos
segmentos MPEG-TS e mantém o content type inferido pelo Warp para os demais arquivos:
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(())
}
Testando a configuração de streaming
Para testar sua solução de streaming:
-
Substitua
mainemsrc/main.rspelo código a seguir, mantendo as funções acima: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(()) } -
Execute o conversor uma vez e, em seguida, inicie o servidor de streaming a partir do diretório do projeto:
cargo run --bin video_converter && cargo run --bin server -
Use um player compatível, como o VLC, ou bibliotecas web como hls.js ou dash.js, para reproduzir o stream a partir de:
Conclusão
Este DevTip mostrou como converter vídeos para os formatos HLS e MPEG-DASH usando Rust e FFmpeg. Abordamos a geração de playlists e arquivos de manifesto e a configuração de um servidor de streaming básico para streaming adaptativo. Para uma solução mais robusta, considere estas melhorias:
- Estenda o exemplo de HLS com uma única rendition para várias renditions alinhadas e obtenha reprodução adaptativa.
- Experimente configurações avançadas do FFmpeg para otimizar a qualidade do vídeo.
- Adicione suporte a outros recursos, como várias faixas de áudio e legendas.
- Proteja sua configuração de streaming com HTTPS e autenticação baseada em token.
Se você procura uma solução gerenciada para codificação de vídeo e streaming adaptativo, a Transloadit oferece recursos poderosos de processamento de vídeo que escalam para atender às suas necessidades.
