|
| 1 | +Audio data is often represented by a waveform image. This guide explains how to |
| 2 | +easily create such an image or video. |
| 3 | + |
| 4 | += Waveform image = |
| 5 | + |
| 6 | +The [https://ffmpeg.org/ffmpeg-filters.html#showwavespic showwavespic] filter is the easiest method to create a waveform image. |
| 7 | + |
| 8 | +== All channels == |
| 9 | + |
| 10 | +[[Image(showwavespic.png)]] |
| 11 | + |
| 12 | +{{{ |
| 13 | +ffmpeg -i input -filter_complex "showwavespic=s=640x120" -frames:v 1 output.png |
| 14 | +}}} |
| 15 | + |
| 16 | +All channels will be represented by various shades in the waveform. |
| 17 | + |
| 18 | +== Downmixed == |
| 19 | + |
| 20 | +[[Image(showwavespic_mono.png)]] |
| 21 | + |
| 22 | +If you want a simpler representation where all channels are represented by one waveform you can downmix your audio to mono first with [https://ffmpeg.org/ffmpeg-filters.html#aformat aformat]: |
| 23 | + |
| 24 | +{{{ |
| 25 | +ffmpeg -i input -filter_complex "aformat=channel_layouts=mono,showwavespic=s=640x120" -frames:v 1 output.png |
| 26 | +}}} |
| 27 | + |
| 28 | +== Separate channels == |
| 29 | + |
| 30 | +[[Image(showwavespic_split.png)]] |
| 31 | + |
| 32 | +If you want to split them into separate channels: |
| 33 | + |
| 34 | +{{{ |
| 35 | +ffmpeg -i input -filter_complex "showwavespic=s=640x240:split_channels=1" -frames:v 1 output.png |
| 36 | +}}} |
| 37 | + |
| 38 | +== Changing range == |
| 39 | + |
| 40 | +[[Image(showwavespic_compand.png)]] |
| 41 | + |
| 42 | +If the waveform looks a little flat you can use the [https://ffmpeg.org/ffmpeg-filters.html#compand compand] filter to expand or compress the dynamic range: |
| 43 | + |
| 44 | +{{{ |
| 45 | +ffmpeg -i input -filter_complex "compand,showwavespic=s=640x120" -frames:v 1 output.png |
| 46 | +}}} |
| 47 | + |
| 48 | +Note that this won't be as accurate of a representation as it would without compand, but for aesthetics it may be preferred. |
| 49 | + |
| 50 | +== Adding a background == |
| 51 | + |
| 52 | +[[Image(showwavespic_bg.png)]] |
| 53 | + |
| 54 | +Using [https://ffmpeg.org/ffmpeg-filters.html#overlay overlay]: |
| 55 | + |
| 56 | +{{{ |
| 57 | +ffmpeg -i input -i background.png -filter_complex "[0:a]showwavespic=s=640x240[fg];[1:v][fg]overlay=format=auto" -frames:v 1 output.png |
| 58 | +}}} |
| 59 | + |
| 60 | +* This example assumes the background is the same width and height as the waveform. If it is not, you can [https://ffmpeg.org/ffmpeg-filters.html#scale scale], [https://ffmpeg.org/ffmpeg-filters.html#crop crop], or [https://ffmpeg.org/ffmpeg-filters.html#pad pad] the background first. |
| 61 | + |
| 62 | +* The waveform color can be modified with the `colors` option in showwavespic. |
| 63 | + |
| 64 | +=== Add a solid background color === |
| 65 | + |
| 66 | +[[Image(showwavespic_bg_solid.png)]] |
| 67 | + |
| 68 | +{{{ |
| 69 | +ffmpeg -i in.flac -f lavfi -i color=c=black:s=640x320 -filter_complex \ |
| 70 | + "[0:a]showwavespic=s=640x320:colors=white[fg];[1:v][fg]overlay=format=auto" \ |
| 71 | + -frames:v 1 out.png |
| 72 | +}}} |
| 73 | + |
| 74 | +== Audacity-like look == |
| 75 | + |
| 76 | +[[Image(showwavespic_like_audacity.png)]] |
| 77 | + |
| 78 | +{{{ |
| 79 | +size="640x240" |
| 80 | +bg_col="#c0c0c0" |
| 81 | +peak_col="#3232c8" |
| 82 | +rms_col="#6464dc" |
| 83 | + |
| 84 | +ffmpeg -i input.wav -f lavfi -i "color=c=${bg_col}:s=${size}" -filter_complex " \ |
| 85 | + [0:a] showwavespic=s=${size}:split_channels=1:colors=${peak_col}:filter=peak [pk]; \ |
| 86 | + [0:a] showwavespic=s=${size}:split_channels=1:colors=${rms_col} [rms], \ |
| 87 | + [pk] [rms] overlay=format=auto [nobg], [1:v] [nobg] overlay=format=auto" \ |
| 88 | + -frames:v 1 -update true output.png |
| 89 | +}}} |
| 90 | + |
| 91 | += Waveform video = |
| 92 | + |
| 93 | +[[Image(showwaves.png)]] |
| 94 | + |
| 95 | +The [https://ffmpeg.org/ffmpeg-filters.html#showwaves showwaves] filter makes a video waveform. |
| 96 | + |
| 97 | +{{{ |
| 98 | +ffmpeg -i input -filter_complex "[0:a]showwaves=s=1280x720:mode=line,format=yuv420p[v]" -map "[v]" -map 0:a -c:v libx264 -c:a copy output.mkv |
| 99 | +}}} |
| 100 | + |
| 101 | +---- |
| 102 | + |
| 103 | += Using Gnuplot = |
| 104 | + |
| 105 | +== Single Channel == |
| 106 | + |
| 107 | +Plotting a single channel waveform is easier as the data can be passed to |
| 108 | +Gnuplot directly. The basic idea here is to generate a specific binary format |
| 109 | +for the audio data which then can be read and interpreted automatically with |
| 110 | +Gnuplot. |
| 111 | + |
| 112 | +The following command will generate a stream of raw binary data with two bytes |
| 113 | +representing each sample. The input format does not matter as long as there is |
| 114 | +an audio stream. If it is a multi-channel audio stream, the channels will be |
| 115 | +mixed into one. |
| 116 | + |
| 117 | +{{{ |
| 118 | + ffmpeg -i in.mkv -ac 1 -map 0:a -c:a pcm_s16le -f data - |
| 119 | +}}} |
| 120 | + |
| 121 | +Since audio usually comes with a lot of samples per second (e.g. 44100 samples |
| 122 | +per second for CD audio) it is usually a good idea to reduce the amount of data |
| 123 | +to increase speed and decrease memory consumption. Note that reducing the |
| 124 | +samplerate too much might distort the generated waveform. A good value to use |
| 125 | +is usually 8000 samples per second. The modified command line to do that would |
| 126 | +look like this: |
| 127 | + |
| 128 | +{{{ |
| 129 | + ffmpeg -i "$1" -ac 1 -filter:a aresample=8000 -map 0:a -c:a pcm_s16le -f data - |
| 130 | +}}} |
| 131 | + |
| 132 | +Now what is left is the letting Gnuplot create the waveform image from these |
| 133 | +data. For that we need a plot command that deals with the output from FFmpeg. |
| 134 | + |
| 135 | +{{{ |
| 136 | + plot '<cat' binary filetype=bin format='%int16' endian=little array=1:0 with lines; |
| 137 | +}}} |
| 138 | + |
| 139 | +This plot command reads from stdin where it expects a one dimensional array of |
| 140 | +two byte, little endian, signed integer representing the pcm vales. These are |
| 141 | +then plotted with lines. |
| 142 | + |
| 143 | +We can already try this out by combining those commands. Note that the live |
| 144 | +rendering using the graphical user interface of Gnuplot is rather slow so you |
| 145 | +don't want to throw too many data at it. Use a short audio file or limit the |
| 146 | +duration using the '-t' option to plot only a part of the file. |
| 147 | + |
| 148 | +{{{ |
| 149 | + ffmpeg -i in.wav -ac 1 -filter:a aresample=8000 -map 0:a -c:a pcm_s16le -f data - | \ |
| 150 | + gnuplot -p -e "plot '<cat' binary filetype=bin format='%int16' endian=little array=1:0 with lines;" |
| 151 | +}}} |
| 152 | + |
| 153 | +This should result in something like this: |
| 154 | + |
| 155 | +[[Image(gnuplot_window.png)]] |
| 156 | + |
| 157 | +Here we already got the waveform, but usually we don't need axis, labels, |
| 158 | +scales etc. Also the aspect ratio is not optimal. It should rather be something |
| 159 | +like '1:10'. Hence we need to extend the plot command. |
| 160 | + |
| 161 | +{{{ |
| 162 | + set terminal png size 5000,500; |
| 163 | + set output 'waveform.png'; |
| 164 | + |
| 165 | + unset key; |
| 166 | + unset tics; |
| 167 | + unset border; |
| 168 | + set lmargin 0; |
| 169 | + set rmargin 0; |
| 170 | + set tmargin 0; |
| 171 | + set bmargin 0; |
| 172 | + |
| 173 | + plot '<cat' binary filetype=bin format='%int16' endian=little array=1:0 with lines; |
| 174 | +}}} |
| 175 | + |
| 176 | +This will make Gnuplot generate a PNG image with the dimension of 5000x500 |
| 177 | +pixel as output and store it in a file named 'waveform.png'. It also removes all |
| 178 | +labels, axis and other non-data from the image and set all margins to zero. |
| 179 | + |
| 180 | +All this can still be specified using the command line, but it is much more |
| 181 | +convenient to put all the plot commands in a separate file and pass that one to |
| 182 | +Gnuplot. Assuming the plot commands are stored in 'waveform.gnuplot', a valid |
| 183 | +command line to generate a waveform image would then be: |
| 184 | + |
| 185 | +{{{ |
| 186 | + ffmpeg -i in.mp3 -ac 1 -filter:a aresample=8000 -map 0:a -c:a pcm_s16le -f data - | \ |
| 187 | + gnuplot waveform.gnuplot |
| 188 | +}}} |
| 189 | + |
| 190 | +The result should then look somewhat like this. |
| 191 | + |
| 192 | +[[Image(ac1.png)]] |
| 193 | + |
| 194 | + |
| 195 | +== Multiple Channels == |
| 196 | + |
| 197 | +While the basic idea and the command for plotting multiple channels remain the |
| 198 | +same, we cannot simply pipe the data into Gnuplot since the channels have to be |
| 199 | +plotted separately. Thus we first use FFmpeg to extract the data for all |
| 200 | +channels and then plot the data in a second step. |
| 201 | + |
| 202 | +An FFmpeg command line to extract the audio channel data into separate files, |
| 203 | +prepared for Gnuplot could look like this: |
| 204 | + |
| 205 | +{{{ |
| 206 | + ffmpeg -i in.mp4 -ac 2 -filter_complex:a '[0:a]aresample=8000,asplit[l][r]' \ |
| 207 | + -map '[l]' -c:a pcm_s16le -f data /tmp/plot-waveform-ac1 \ |
| 208 | + -map '[r]' -c:a pcm_s16le -f data /tmp/plot-waveform-ac2 |
| 209 | +}}} |
| 210 | + |
| 211 | +This would again downsample the data, but then split the audio channels into |
| 212 | +separate streams and store them in two files. One for each channel. |
| 213 | + |
| 214 | +A plot command for this would then look like this: |
| 215 | + |
| 216 | +{{{ |
| 217 | + set terminal png size 5000,1000; |
| 218 | + set output 'waveform.png'; |
| 219 | + |
| 220 | + unset key; |
| 221 | + unset tics; |
| 222 | + unset border; |
| 223 | + set lmargin 0; |
| 224 | + set rmargin 0; |
| 225 | + set tmargin 0; |
| 226 | + set bmargin 0; |
| 227 | + |
| 228 | + set multiplot layout 2,1; |
| 229 | + plot '/tmp/plot-waveform-ac1' binary filetype=bin format='%int16' endian=little array=1:0 with lines; |
| 230 | + plot '/tmp/plot-waveform-ac2' binary filetype=bin format='%int16' endian=little array=1:0 with lines; |
| 231 | + unset multiplot; |
| 232 | +}}} |
| 233 | + |
| 234 | +The resulting image would then look like this: |
| 235 | + |
| 236 | +[[Image(ac2.png)]] |
| 237 | + |
| 238 | + |
| 239 | +== Additional Hints == |
| 240 | + |
| 241 | +Sometimes there are a few loud pitches while the rest of the data is relatively |
| 242 | +quiet. These pitches would cause the rest of the data to scale down which might |
| 243 | +be unwanted. To make the y-axis centered and cut off peaks, add the following |
| 244 | +line (adjust the values) to the plot command: |
| 245 | + |
| 246 | +{{{ |
| 247 | + set yrange [-600:600]; |
| 248 | +}}} |
0 commit comments