Cómo cambiar la codificación de un audio

Compartir esta publicación

Hoy me he encontrado en una situación un tanto molesta. Tras obtener de forma totalmente legal y lícita una gran colección de videos mp4, los cargué en mi disco duro externo, enchufé el disco duro en mi televisor, le di al play al primer video… y el audio no se reconocía. Drama y desesperación, no hay posibilidad de encontrar estos videos en otro formato, solo existen en este, así que me puse manos a la obra.

¿Qué es un encoding de audio?

Si tú, como yo, eres un tanto ignorante en estos aspectos del multimedia, te voy a explicar brevemente por qué mi televisor no sabía reproducir el audio de estos archivos mp4.

Un archivo mp4 se puede explicar como un paquete que comprime por separado el audio y el video. Tanto el video como el audio tienen su propio encoding, que resumidamente es la forma en que se almacena y se transmite sus datos.  Por lo tanto, si el reproductor (mi televisor) no tiene los codecs apropiados, no sabrá leer ese formato de encoding. Los codecs son los paquetes de algoritmos que permiten codificar, en este caso decodificar, los datos de video/audio.

Y si mi televisor no tiene los codecs para leer ese encoding de audio concreto, qué hago? Pues cambiar el encoding a los videos. Y aquí entra FFmpeg.

FFmpeg es un proyecto open source para manejar archivos multimedia, te permite ver el detalle de estos con FFprobe, te permite reproducirlos con FFplay, y te permite modificarlos con FFmpeg.

rruiz@rruiz:~/Videos$ ffprobe video1.mp4 
ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers
  built with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
  configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'video1.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    title           : video 1 (1080p)
    artist          : WinX HD Video Converter Deluxe
    encoder         : Lavf58.29.100
  Duration: 00:24:39.56, start: 0.000000, bitrate: 3737 kb/s
    Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3455 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(cat): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 276 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

Aquí podemos ver que el stream #0:0(und) contiene la pista de video en encoding h264, que una rápida búsqueda en internet nos dirá que es un standard de video encoding también conocido como AVC (advanced video encoding) o MPEG-4 Part 10. Aunque esta no es la parte que nos interesa.

  Aprendiendo Angular como desarrollador de React

En el stream #0:1(cat) encontramos la pista de audio, con encoding aac(advanced audio encoding), este es el encoding que, por algún motivo, mi televisor no sabe reproducir.

No es la primera vez que esto me ocurre, generalmente me ocurre con vídeos mkv, formato de video que no tiene un standard de encodings tan definido como el mp4 y que provoca con mayor facilidad que el reproductor no tenga el codec apropiado. Sin embargo por algún motivo, ya sea la versión del aac de estos videos o cualquier otro, el audio de estos videos mp4 no es compatible con mi televisor.

¿Cómo cambio el encoding del video?

Si conFFprobe puedo sacar la información del video, conFFmpeg puedo cambiarlo.

ffmpeg -i video1.mp4 -acodec mp3 -vcodec copy encoded_video1.mp4

La documentación de FFmpeges larga y completa, pero en nuestro caso solo necesitamos conocer un par de opciones:

i: input, el vídeo que queremos modificar, en este caso video1.mp4.acodec: el codec de audio que queremos que tenga el archivo de salida, en nuestro caso mp3. –vcodec: el codec de video que queremos que tenga el archivo de salida, como no queremos cambiarlo, ponemos copy. Finalmente ponemos el nombre del archivo de salida, en este caso encoded_video1.mp4.

Automatizarlo

Genial, ya se cambiar el encoding de un archivo, pero tengo muchos videos, no puedo estar escribiendo el comando uno a uno. ¿Qué opciones tenemos?

for loop

for filename in *.mp4; do 
    ffmpeg -i $filename -acodec mp3 -vcodec copy encoded_$filename;
done

Un for loop sería una primera opción, pero tiene unos cuantos inconvenientes. Procesará los archivos uno a uno y mi PC cuenta con 16 cores y FFmpeg, por defecto usa un solo thread, es poco eficiente y lento para muchos archivos medianamente grandes.

  GitTip: Skip WorkTree

FFmpeg -threads

FFmpeg cuenta con la posibilidad de definir un threadpool para usar en el procesado:

ffmpeg -threads 4 -i video1.mp4 -acodec mp3 -vcodec copy encoded_video1.mp4

-threads 0 implies that FFmpeg will choose the optimal amount of threads, with -threads 1 it will be monothread, and so on, as long as it can parallelize.

Sin embargo la cantidad de threads que vaya a usar realmente depende de la versión de FFmpeg en cuestión, del codec que estés usando y de los cores de tu PC. No es algo muy consistente, y en mi caso aunque le pusiera 4 threads, nunca ha pasado de usar 1.

¿Forks? ¡Parallel!

Si FFmpeg no me permite explotar los 16 cores de mi PC, tendré que crear los threads yo mismo, y justo cuando estaba refrescando el cómo manejar una threadpool en bash para procesar archivos en batches, encuentro parallel.

Paralell es un comando gnu/bash que permite pasarle un listado de N argumentos y un comando. Ejecutará el comando N veces, cada vez con uno de los argumentos, por defecto en paralelo con tantos jobs como cores tenga tu PC.

De esta manera, si tu PC cuenta con 4 cores y tu le pasas 40 argumentos, paralelizará 4 de ellos, procesando el siguiente cuando tenga un core libre, genial, justo lo que necesitaba.

Sin embargo te permite indicar el número máximo de jobs que quieres que use, para no saturar el PC, con –jobs. También recomiendan usar –ungroup para más eficiencia, esto permite pintar los logs asap, resultando en logs de diferentes comandos mezclados.

ls *.mp4 | parallel --ungroup --jobs 6 echo

En este ejemplo los argumentos que le pasamos a parallel son el listado de archivos mp4 de la carpeta actual, y el comando que va a ejecutar en paralelo es… echo. No es útil, pero nos sirve para ver la sintaxis.

Es hora de montar un pequeño script para cambiar el encoding de los videos de una carpeta:

  Mantener información delicada en secreto en un repositorio git

#!/bin/bash

encode() { ffmpeg -i $1 -acodec mp3 -vcodec copy encoded_$1; }

export -f encode

ls *.mp4 | parallel --ungroup --jobs 14 encode

En este sencillo script definimos una función encodeque contiene el uso del comando FFmpeg tal y como lo hemos explicado antes. A continuación exportamos la función para que pueda ser usada porparallel. Finalmente llamamos a parallel con todos los mp4 de la carpeta actual, usando un máximo de 14 jobs (cores) simultáneos.

Genial, ya tenemos un prototipo operativo. Ahora toca mejorarlo para poder usarlo en diferentes carpetas:

#!/bin/bash

DIRECTORY=$1
JOBS=14

encode() { ffmpeg -i $1 -acodec mp3 -vcodec copy encoded_$1; }

export -f encode

cd "$DIRECTORY"
ls *.mp4 | parallel --ungroup --jobs $JOBS encode

Extraemos el directorio origen de los archivos del argumento $1 del script. Sacamos la cantidad de jobs a una variable para que sea más visible y fácil de cambiar.

Un cd al directorio en cuestión, y listos. El motivo por que el cd usa “” es para que funcione también para nombres de directorios que contengan espacios.

Para terminar, mejoramos el logging y el output de videos:

#!/bin/bash

DIRECTORY=$1
JOBS=14
ENCODED_DIRECTORY=encoded

echo "changing audio encode to aac for mp4 files in directory $DIRECTORY, $JOBS files at a time, storing the newly encoded files in $DIRECTORY/$ENCODED_DIRECTORY"

encode() { echo "processing $2..."; ffmpeg -loglevel 0 -nostats -i $2 -acodec mp3 -vcodec copy $1/$2; }

export -f encode

cd "$DIRECTORY"

mkdir -p $ENCODED_DIRECTORY

ls *.mp4 | parallel --ungroup --jobs $JOBS encode $ENCODED_DIRECTORY

Las opciones -loglevel o -nostats de FFmpeg permiten su ejecución con loglevel panic, en caso que algo falle no lo veremos salvo que sea un fatal error, pero la ejecución normal será más rápida y limpia. Siempre podemos quitar estas opciones más adelante para ver qué está fallando, o cambiar el loglevel a fatal(8), error(16), warning(24), etc.

Crearemos un subdirectorio “encoded”, especificado en la variableENCODED_DIRECTORY . Le pasaremos esta variable al comando encode dentro del parallel de manera que la función lo reciba como primer argumento, y lo usaremos en la función para guardar el output de FFmpeg en un subdirectorio, pero conservando el nombre.

Para entenderlo mejor, podemos usar la opción de parallel:

rruiz@rruiz:~/Videos$ ls *.mp4 | parallel --dryrun --jobs 14 encode "subdirectory"
encode subdirectory video1.mp4
encode subdirectory video2.mp4
encode subdirectory video3.mp4
encode subdirectory video4.mp4

Y listos, ahora podemos cambiar el encoding de los archivos mp4 de directorios enteros disparando:

rruiz@rruiz:~/Videos$ ./encode_mp4.sh directorio_1

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

apiumhub software development projects barcelona
Secured By miniOrange