WavPack's roundtrip advantage over FLAC

This page is about a non-obvious advantage of the audio codec WavPack. I discovered it some years ago when archiving music I’d made with samples as a teenager. (Don’t ask to hear it. It isn’t good.) WavPack has other cool features, like “hybrid mode”, but I won’t cover them here.

Current lossless audio codecs all have a compression ratio of around 50%. Most people seem to use FLAC for lossless audio compression. FLAC is widely supported, including in browsers, and compresses as well as the rest. So why choose WavPack, a less popular alternative? WavPack has an unusual property of always (as far as I can tell) letting you get back a bit-for-bit identical PCM WAV file. This means a WAV file roundtripped through WavPack will have an identical checksums.

This got me interested because in the 2010s I wanted to preserve my old files exactly as they were while saving disk space. I’ll note that now with more abundant disk space this is a less of a concern than it used to be. If you need to get the same exact bits back, you can use a good general-purpose compressor like Zstandard and eat the cost in storage and bandwisth because it’s small. Still, I find the feature interesting, and it might be useful in special cases like archiving a computer game that will be downloaded an installed by many people, which multiplies the bandwidth and the distribution size by the number of users.

The following test demonstrates the difference between FLAC and WavPack. We’ll try them on a file where FLAC gets the exact file back and one where it doesn’t.

For this demo, I will be using Debian 12 and issuing commands that work in both the POSIX shell and fish.

First, let’s create the files we’ll use for the test. One we will download from Wikimedia Commons as-is, and another we’ll download in MP3 and convert to PCM WAV.

$ sudo apt install -y b3sum curl file flac mpg123 wavpack xxd
[...]
$ curl -Lfs -o test.mp3 https://upload.wikimedia.org/wikipedia/commons/transcoded/2/2e/Mysterioso_Pizzicato.mid/Mysterioso_Pizzicato.mid.mp3
$ curl -Lfs -o test2.wav 'https://upload.wikimedia.org/wikipedia/commons/2/29/Drawing_of_the_word_Wikipedia_transformed_into_a_wav_file_%28Coagula_software%29.wav'
$ mpg123 -w test.wav -q test.mp3
$ b3sum test*.wav
ef1ec2c71350ddec9b7a4d4143075020270651a2ffd1a605031ba7db43929cf9  test.wav
13cf3f16d09e7e120a3cd1d3dec500c8283c4cae1dd4278defc4505ba1801463  test2.wav

Let’s compress the WAV files with both FLAC and WavPack using maximum compression.

$ flac --version
flac 1.4.2
$ wavpack --version
wavpack 5.6.0
libwavpack 5.6.0
$ flac -8 --silent test.wav
$ flac -8 --silent test2.wav
$ wavpack -hhx -q test.wav
$ wavpack -hhx -q test2.wav
$ du -h ./test*
532K	./test.flac
252K	./test.mp3
2.1M	./test.wav
524K	./test.wv
124K	./test2.flac
432K	./test2.wav
140K	./test2.wv

Now we’ll decompress the files and checksum the source and the result.

$ flac -d -o test.flac.wav --silent test.flac
$ flac -d -o test2.flac.wav --silent test2.flac
$ wvunpack -o test.wv.wav -q test.wv
$ wvunpack -o test2.wv.wav -q test2.wv
$ b3sum test*.wav
ef1ec2c71350ddec9b7a4d4143075020270651a2ffd1a605031ba7db43929cf9  test.flac.wav
ef1ec2c71350ddec9b7a4d4143075020270651a2ffd1a605031ba7db43929cf9  test.wav
ef1ec2c71350ddec9b7a4d4143075020270651a2ffd1a605031ba7db43929cf9  test.wv.wav
a81b51460336e39fd9e1055075e92a7d394da82be989232aa5b2b0ea3a670dfe  test2.flac.wav
13cf3f16d09e7e120a3cd1d3dec500c8283c4cae1dd4278defc4505ba1801463  test2.wav
13cf3f16d09e7e120a3cd1d3dec500c8283c4cae1dd4278defc4505ba1801463  test2.wv.wav

You can see the checksums are identical for test.wav. However, only the checksum of test2.wv.wav matches test2.wav; the file that passed through FLAC doesn’t match.

Let’s see that the files are in the same format.

$ file test2.wav
test2.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 22050 Hz
$ file test2.wv.wav
test2.wv.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 22050 Hz

An xxd(1) hexadecimal dumps shows the difference between test2.wav and test2.flac.wav.

$ xxd test2.wav | head -n 880
00000000: 5249 4646 a4b6 0600 5741 5645 666d 7420  RIFF....WAVEfmt
00000010: 1400 0000 0100 0200 2256 0000 8858 0100  ........"V...X..
00000020: 0400 1000 0000 0000 6461 7461 7cb6 0600  ........data|...
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
[...]
00003670: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003680: 0000 0000 0000 0000 0300 0000 feff 0000  ................
00003690: f7ff 0000 ffff 0000 0c00 0000 0800 0000  ................
000036a0: f8ff 0000 f5ff 0000 0100 0000 0800 0000  ................
000036b0: 0400 0000 0200 0000 ffff 0000 f2ff ffff  ................
000036c0: f3ff 0000 1400 0100 2500 0100 f5ff ffff  ........%.......
000036d0: c1ff ffff f0ff 0000 4e00 0200 3c00 0000  ........N...<...
000036e0: b7ff feff 93ff ffff 2700 0200 9500 0200  ........'.......
000036f0: 1300 ffff 5eff fdff a4ff 0000 8c00 0300  ....^...........
$ xxd test2.flac.wav | head -n 880
00000000: 5249 4646 a0b6 0600 5741 5645 666d 7420  RIFF....WAVEfmt
00000010: 1000 0000 0100 0200 2256 0000 8858 0100  ........"V...X..
00000020: 0400 1000 6461 7461 7cb6 0600 0000 0000  ....data|.......
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
[...]
00003670: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003680: 0000 0000 0300 0000 feff 0000 f7ff 0000  ................
00003690: ffff 0000 0c00 0000 0800 0000 f8ff 0000  ................
000036a0: f5ff 0000 0100 0000 0800 0000 0400 0000  ................
000036b0: 0200 0000 ffff 0000 f2ff ffff f3ff 0000  ................
000036c0: 1400 0100 2500 0100 f5ff ffff c1ff ffff  ....%...........
000036d0: f0ff 0000 4e00 0200 3c00 0000 b7ff feff  ....N...<.......
000036e0: 93ff ffff 2700 0200 9500 0200 1300 ffff  ....'...........
000036f0: 5eff fdff a4ff 0000 8c00 0300 a000 0100  ^...............

The same data is offset.

I think usually you don’t.

On a modern computer, you orobably would use gzip or Zstandard just to save yourself work. Lossless audio codecs do compress audio better than generic compressors. It is also the case that more audio players can play WavPack than .wav.gz or .wav.zst.

$ gzip -6 --keep test.wav
$ zstd -7q test.wav
$ du -h test.wav test.wav.gz test.wav.zst test.wv
2.1M	test.wav
1.5M	test.wav.gz
1.4M	test.wav.zst
524K	test.wv

You can when you are creating the WAV files.

No.

$ flac -8f --silent test2.wav && flac -df -o test2.flac.wav --silent test2.flac
$ b3sum test2.flac.wav
a81b51460336e39fd9e1055075e92a7d394da82be989232aa5b2b0ea3a670dfe  test2.flac.wav
$ flac -8f --keep-foreign-metadata --silent test2.wav && flac -df -o test2.flac.wav --keep-foreign-metadata --silent test2.flac
NOTE: --keep-foreign-metadata is a new feature; make sure to test the output file before deleting the original.
NOTE: --keep-foreign-metadata is a new feature; make sure to test the output file before deleting the original.
$ b3sum test2.flac.wav
a81b51460336e39fd9e1055075e92a7d394da82be989232aa5b2b0ea3a670dfe  test2.flac.wav

No, FLAC is a good default and entrenched. (I like FLAC!)