Sincronización de sonido cuando la tasa de refresco de vídeo es irregular

Los motivos por los que el audio y el vídeo se pueden desincronizar son múltiples y variados. Hay que estudiar cada caso en particular y aplicar las medidas correctivas apropiadas. Un caso muy simple, descrito en Determinar automáticamente el desfase de audio en un fichero MKV, se da cuando las diferentes pistas están bien, pero no empiezan en el mismo instante. Esto puede ocurrir con facilidad si, por ejemplo, estamos añadiendo la pista de sonido en castellano a una película que solo tenía audio en inglés. Como se describe en ese artículo, el proceso es trivial.

Tenemos un caso más complicado cuando estamos separando las diferentes pistas de un fichero multimedia, las procesamos por separado y luego volvemos a unirlas. Normalmente no tendremos problemas, pero cuando surjan -y surgirán- hay que saber cómo diagnosticarlos y solucionarlos.

En este artículo estudiaremos un ejemplo real.

Síntomas

La película tiene un canal de audio y un canal de vídeo. Tras recodificarla para que mida la mitad, nos encontramos con que el audio está desincronizado.

La corrección típica de desplazar el audio adelante y atrás no funciona bien, ya que el desajuste es variable a lo largo de la película. Tampoco se trata de un desajuste lineal (va creciendo linealmente a lo largo de la película), sino que es irregular.

Hipótesis: La película tiene una tasa de refresco variable

Usando ffprobe veo que el vídeo de la película original está declarado como de 25 fotogramas por segundo, que dura en total 1:49:09.918 y que tiene 163566 fotogramas. Pero si dividimos el número de fotogramas entre la duración de la película, nos salen 24.972221 fotogramas por segundo de media. La diferencia parece pequeña, pero es la responsable del desajuste de sonido.

De hecho, reproduciendo la película en mi pantalla veo que se añaden 36 fotogramas "artificiales" en los primeros minutos de la película, acumulando más de un segundo de desfase. También veo que estos fotogramas "artificiales" que añade mi reproductor para mantener una tasa de refresco de 25 fotogramas por segundo aparecen esporádicamente y de forma irregular a lo largo de toda la película.

Confirmación de la hipótesis

Hay varias formas de confirmar que estamos en lo cierto. La más evidente es que al reproducir el fichero con MPlayer o FFmpeg aparece un campo dup= con un número que va subiendo. Esos fotogramas duplicados nos están dando una pista clara, pero solo es real si la película que estamos viendo declara una tasa de refresco igual al de nuestro monitor, lo que no es el caso en muchas ocasiones.

El vídeo recodificado tiene 163564 fotogramas (se han perdido dos en el proceso), pero solo dura 01:49:02.640. Se han perdido algo más de siete segundos. Si ahora hacemos la división de nuevo, vemos que la tasa de refresco es de 25 fotogramas por segundo exactamente, no los 24.972221 fotogramas por segundo de media de la película original.

La verificación definitiva que yo recomiendo consistiría en generar un fichero de marcas temporales y echarle un vistazo:

$ ffmpeg -vsync 0 -i FUENTE.mkv -f mkvtimestamp_v2 DATOS.ts

Este comando generará un fichero de texto DATOS.ts con la marca temporal de cada fotograma de la película. Añadimos el parámetro -vsync 0 porque no queremos que FFmpeg toque las marcas temporales originales, que es precisamente lo que nos interesa estudiar. Según el manual:

0, passthrough

Each frame is passed with its timestamp from the demuxer to the muxer.

Si tenemos una tasa de refresco de 25 fotogramas por segundo, a cada uno le corresponden 40 milisegundos. Echando un vistazo al fichero que hemos generado con las marcas temporales, vemos ya problemas muy al principio:

# timecode format v2
0
      <- Falta un fotograma
80
      <- falta un fotograma
160
      <- Falta un fotograma
240
...
880
920
      <- Falta un fotograma
1000

Los fotogramas están perfectamente alineados en múltiplos de 40 milisegundos, pero faltan algunos fotogramas.

Al recodificar esta película perdemos esa información, por eso la película es más corta a pesar de tener el mismo número de fotogramas y por eso se nos desincroniza el sonido.

¿Y ahora qué?

Solución 1: Arrastrar las marcas temporales

La herramienta FFmpeg nos permite extraer la información de marcas temporales, como ya hemos visto, y la herramienta mkvmerge nos permite utilizarla mediante el parámetro --timecodes:

$ ffmpeg -vsync 0 -i FUENTE.mkv -f mkvtimestamp_v2 DATOS.ts
$ mkvmerge -o SALIDA2.mkv --timecodes 0:DATOS.ts SALIDA.mkv

El comando ffmpeg nos extrae las marcas temporales del vídeo de la película original. Usamos el comando mkvmerge para generar una nueva versión a partir de la primera versión transcodificada (la que tiene al audio desincronizado), ajustando las marcas temporales de cada fotograma del canal de vídeo a los valores originales.

La película así reconstruida se reproduce correctamente. Hemos solucionado el problema.

La principal ventaja de este método es que no necesitamos transcodificar la película de nuevo. Tan solo debemos reprocesar el original para extraer la información que necesitamos. En mi sistema y para esta película, ese proceso se completa en unos quince minutos.

Otra ventaja interesante de este sistema es que podemos hacer frente a fuentes de vídeo con una tasa de refresco completamente irregular. En algunos casos esta característica será impagable.

El principal inconveniente -muy importante en mi opinión- es que, aunque el fichero generado sea perfectamente legal y compatible con todo, si alguna vez necesitamos transcodificarlo por algún motivo, volveremos a tener el mismo problema otra vez.

Solución 2: Recodificar usando una tasa de refresco homogénea

Mi solución favorita, aunque requiere transcodificar la película de nuevo, consiste en generar una versión en la que no falten fotogramas. Para ello se puede usar el parámetro de salida -r de ffmpeg. Según su manual:

-r[:stream_specifier] fps (input/output,per-stream)

Set frame rate (Hz value, fraction or abbreviation).

As an input option, [...]

As an output option, duplicate or drop input frames to achieve constant output frame rate fps.

Es decir, añadiendo -r 25 o -r:0 25 como parámetro para el fichero de salida transcodificado generado por ffmpeg, habremos solucionado el problema de forma definitiva.

Nota

En nuestro caso la tasa de refresco es perfectamente regular. Sencillamente faltan algunos fotogramas. Regenerarlos es trivial simplemente repitiendo su fotograma previo.

Nota

Tener más fotogramas supone un fichero de mayor tamaño, aunque lo más probable es que el cambio sea inapreciable por que a) la compresión de vídeo hace milagros y b) el número de fotogramas añadidos es muy pequeño.

Por ejemplo, en este caso concreto pasamos de tener 163566 fotogramas a tener 163748. Eso es apenas un 0.1113% más, incluso aunque la compresión no sea efectiva (que lo va a ser, porque los fotogramas nuevos son idénticos a su fotograma anterior).

Nota

El procedimiento exacto a seguir cuando usamos MPlayer para hacer la transcodificación de vídeo queda como ejercicio para el lector :-).