How to change audio encoding
Tags: tools
Published: 2022-09-28 13:15:42

---
title: How to change audio encoding
published: true
date: 2022-09-20 06:00:43 UTC
tags: Tools
canonical_url: https://apiumhub.com/tech-blog-barcelona/how-to-change-audio-encoding/
---

Today I found myself in a somewhat annoying situation. After obtaining in a totally legal and lawful way a large collection of mp4 videos, I loaded them on my external hard drive, plugged the hard drive into my TV, hit play on the first video… and the audio was not recognized. Drama and despair, there is no possibility of finding these videos in another format, they only exist in this one, so I got down to work.

## What is audio encoding?

If you, like me, are somewhat ignorant in these aspects of multimedia, I will briefly explain why my TV could not play the audio of these mp4 files.

An mp4 file can be explained as a package that compresses audio and video separately. Both video and audio have their own encoding, which in short is the way their data is stored and transmitted.  
Therefore, if the player (my TV) does not have the appropriate codecs, it will not know how to read that encoding format. Codecs are the packages of algorithms that allow encoding, in this case, decoding, the video/audio data.

And if my TV does not have the codecs to read that particular audio encoding, what do I do? Well, change the encoding of the videos. And this is where **FFmpeg** comes in.

FFmpeg is an open-source project to manage multimedia files that allows you to see the detail of these with FFprobe, play them with FFplay, and also allows you to modify them with 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

```

Here we can see that stream #0:0(und) contains the video track in **h264** encoding, which a quick search on the internet will tell us is a video encoding standard also known as AVC (advanced video encoding) or **MPEG-4 Part 10.** But this is not the part that interests us.

In the stream #0:1(cat) we find the audio track, with aac encoding (advanced audio encoding), this is the encoding that, for some reason, my TV does not know how to play.

It is not the first time that this happens to me, generally, it happens with mkv videos, a video format that does not have a standard of encodings as defined as mp4 and that causes more easily that the player does not have the appropriate codec. However, for some reason, either the aac version of these videos or any other, the audio of these mp4 videos is not compatible with my TV.

## How can I change the video encoding?

If with **FFprobe** I can get the information from the video, with **FFmpeg** I can change it.

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

```

The documentation of **FFmpeg** is long and complete, but in our case, we only need to know a couple of options:

– **i:** input, the video we want to modify, in this case, _video1.mp4._  
– **acodec:** the audio codec we want the output file to have, _in our case mp3._  
– **vcodec:** the video codec we want the output file to have, as we don’t want to change it, we put copy.  
Finally, we put the name of the output file, in this case, _encoded\_video1.mp4._

## Automate it

Great, I already know how to change the encoding of a file, but I have many videos. I can’t be typing the command one by one, what options do I have?

### for loop

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

```

A for loop would be the first choice, but it has a few drawbacks, it will process the files one by one. My PC has 16 cores and FFmpeg by default uses only one thread. It is inefficient and slow for many medium-large files.

### FFmpeg -threads

FFmpeg has the possibility to define a threadpool to be used in the processing:

```
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.

However, the amount of threads it will use really depends on the version of **FFmpeg** in question, the codec you are using, and the cores of your PC. It is not very consistent, and in my case even if I put 4 threads on it, it has never gone beyond 1.

### Forks? Parallel!

If **FFmpeg** does not allow me to exploit the 16 cores of my PC, I will have to create the threads myself, and just when I was refreshing how to handle a thread pool in bash to process files in batches, I find **parallel.**

**Paralell** is a gnu/bash command that allows you to pass it a list of N arguments and a command. It will execute the command N times each time with one of the arguments by default in parallel with as many jobs as your PC has cores.

This way if your PC has 4 cores and you pass it 40 arguments, it will parallelize 4 of them, processing the next one when it has a free core. Great, just what I needed.

However, it allows you to indicate the maximum number of jobs you want it to use, so as not to saturate the PC, with -jobs. They also recommend using -ungroup for more efficiency, this allows you to paint the asap logs, resulting in logs from different commands mixed together.

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

```

In this example, the arguments that we pass to parallel are the list of mp4 files in the current folder, and the command that it will execute in parallel is… echo. It is not useful, but it is useful to see the syntax.

It is time to assemble a small script to change the encoding of the videos in a folder:

```
#!/bin/bash

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

export -f encode

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

```

In this simple script, we define an **encode** function that contains the use of the **FFmpeg** command as explained above.  
Then we export the function so that it can be used by **parallel.**  
Finally, we call **parallel** with all the mp4s in the current folder, using a maximum of 14 simultaneous jobs (cores).

Now that we already have a working prototype, we have to improve it to be able to use it in different folders:

```
#!/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

```

We extract the source directory of the files from the **$1** argument of the script and take the number of jobs out of a variable to make it more visible and easier to change.

A cd to the directory in question, and that’s it. The reason why the cd uses “” is so that it also works for directory names containing spaces.

Finally, we improve logging and video output:

```
#!/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
```

The _-loglevel_ 0 _-nostats_ options of **FFmpeg** allow its execution with loglevel panic, in case something fails we will not see it unless it is a fatal error, but the normal execution will be faster and cleaner. We can always remove these options later to see what is failing or change the loglevel to _fatal(8), error(16), warning(24),_ etc.

We will create an “encoded” subdirectory, specified in the _ENCODED\_DIRECTORY_ variable. We will pass this variable to the encode command inside the parallel so that the function receives it as the first argument, and we will use it in the function to save the output of **FFmpeg** in a subdirectory while keeping the name.

To understand it better, we can use the _-dryrun_ option of 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
```

And that’s it, now we can change the encoding of mp4 files in whole directories by shooting:

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

```