Saturday, 21 May 2022

S/PDIF Bit Exactness

A few years ago, I added an external DAC to my PC, for better-quality sound when listening through headphones. The DAC was driven via a digital, optical S/PDIF connection from the computer, so it was electrically isolated from the PC. This avoided sources of noise, e.g. ground loops and interference from other PC components.


Audio pathway from my CDs to my headphones

In principle, this S/PDIF connection should be lossless, but I started to wonder if it really was. In particular, if I play a lossless copy of a CD, copied with (say) Exact Audio Copy, and then stored in a lossless format such as FLAC, will the signals reaching the DAC actually be 100% identical to the CD?

The obvious answer would be "yes" because every part of the pathway between the CD and the DAC is digital and lossless.

The actual answer turned out to be "not by default".

Windows allows multiple applications to play sound at the same time. The sounds are mixed together. Different applications can use different sample rates, and so the mixing is achieved by sample rate conversion to a fixed rate, which is 48kHz by default. When you play music from a CD (whether directly or via a compressed file), the music is resampled from 44.1kHz to 48kHz, introducing a tiny loss of quality. The mixer also reduces the volume slightly, again introducing a small loss of quality.

These losses are not audible to me, but I still wanted to find out if they could be avoided. This led me to create two tools to help me check a S/PDIF output for bit-exactness: that is, a digitally perfect representation of the source.

Capturing and analysing S/PDIF

I was given a Picoscope as a birthday present, so I decided to use it to analyse S/PDIF signals from the PC. Some Picoscopes are able to store huge amounts of data, but this is a low-cost device, so it can only store 12,500 samples at the S/PDIF frequency. However, this is more than enough. The Picoscope software is able to save captured waveforms as CSV files, so they can be analysed by other programs.

S/PDIF is a one-way serial protocol, well-documented online, and after some work I was able to write programs to decode the S/PDIF data from the CSV files, turning them back into samples. I put these programs on Github along with more technical details and links to the documentation that I used.


S/PDIF signal seen via oscilloscope

S/PDIF consists of packets carrying up to 24 bits of audio data, plus validity, parity and metadata ("subcode") bits. The data for the left and right channels is sent sequentially. In principle more than two channels can be supported, and the data rate is also arbitrary, which is why the protocol is able to support very high sample rates such as 192kHz. The bits are sent using a biphase mark code which means that the length of the pulses carries the data, rather than the logic level as in other serial protocols such as RS-232, though confusingly the biphase mark encoding is not used for the synchronisation codes at the beginning of each packet, and I was stumped by this for a while.

I generated simple test patterns to check that I could receive S/PDIF correctly. Playing silence was a useful test, since silence is unchanged by resampling. I moved on to more complex test patterns and my tools include a program to generate WAV files containing a very short test pattern which is repeated for 15 seconds. The WAV files are played using ordinary audio player software on the PC, and then received via S/PDIF using Picoscope. The oscilloscope output is decoded using a program and compared to the expected pattern.

Bit exactness

In the default configuration, sound is not bit-exact on Windows 10: the audio data sent by S/PDIF to the external DAC is not a digitally perfect representation of the audio data on the CD. This is true even if you are playing the CD directly, or using lossless file formats such as FLAC.

The inexactness comes from the sound mixer which allows multiple applications to play sound at the same time. By default, this resamples the sound to 16-bit 48kHz. You can alter the sound settings to resample to 44.1kHz, but even then, the volume of the audio data is slightly reduced by the mixer, so it is still not bit-exact.


Windows 10 mixer settings

The solution to this is to avoid the default sound API and play sound instead through the lower-level Windows Audio Session API (WASAPI) in exclusive mode. Audio player applications don't necessarily have support for WASAPI in exclusive mode: they tend to use sound APIs that existed in earlier versions of Windows. This is evident if they refer to "DirectSound" or "Primary Sound Driver", or give no control of the output at all (Spotify, for example, though Spotify always uses lossy compression anyway, so bit-exactness is irrelevant).

Other applications require output plugins for bit-exactness. Foobar 2000 has a WASAPI plugin which worked perfectly. But WASAPI support isn't a guarantee of bit-exactness. For instance, Audacity supports WASAPI, but in the version I tested, the output was not bit-exact, perhaps because of some internal mixing or resampling.


Foobar2000 WASAPI output plugin

I also tested audio player applications on Linux. Here I found that bit-exactness seemed to be the default, with tools such as "aplay" and "mplayer" both producing a perfect output.

FPGA Experiments

The Picoscope method for testing S/PDIF was rather awkward, and I wanted a real-time indication. I thought of making a hardware device with an S/PDIF input and some display to indicate correct reception of a test pattern. But the data rate makes this tricky. The oscilloscope had to capture S/PDIF data every 80ns in order to produce a signal that could be decoded reliably. This is beyond the capabilities of low-cost microcontrollers like Arduino, and though a Raspberry Pi would have the necessary processing capabilities, the GPIO ports would be too slow. A discrete logic interface would be impractically large due to the complexity of the S/PDIF encoding. The requirements pointed to an FPGA.

(Actually, a simpler answer is a second PC with an S/PDIF input: provided that this input is also bit-exact, it can be used to check for bit-exact output... this is, however, less fun, and may also fall foul of the simple copy protection scheme used by S/PDIF.)

In the ten years since I last used FPGAs (two jobs ago), there have been a number of big improvements. Devices made by Lattice such as the iCE40 have brought FPGAs into the hobbyist world, being affordable enough to appear on Arduino-like modules such as iceFUN. The official Lattice software (iCEcube2) is free with registration, and incredibly, there are even usable open-source tools which can also be used to make the FPGA programming files. I could not resist getting one of these devices and trying to build an S/PDIF tester.

This was challenging in various ways, but ultimately rewarding. I debugged a design using the GHDL logic simulator, using the CSV files from Picoscope as inputs, and then built it using iCEcube2 and loaded it onto the FPGA. The files are on Github.

It didn't work first time, but the biggest difficulty was not the FPGA design itself (which had already been tested in simulation) but rather with the physical interface to S/PDIF, which was much trickier than expected due to the need to shift the voltage to LVTTL for use by the FPGA, at a relatively high frequency. 3MHz is not a high frequency in computer terms, but it is a high frequency for discrete components such as bipolar transistors, and I found that level-shifting circuits which worked acceptably at 100kHz were completely useless at 1MHz. I learned some things about analogue electronics here, seeing for myself the challenges which once led to the development of TTL and ECL as fast interfaces for logic circuits. I eventually succeeded in building a usable level shifter with a FET, but buying an LVTTL-compatible optical interface was the easiest solution, and this can be seen below.


S/PDIF bit-exactness tester prototype

The eventual FPGA design is able to decode S/PDIF at up to 96kHz sample rates, detect the test pattern from the WAV files and indicate (using LEDs) whether it is being received perfectly or not.

Summary

S/PDIF connections are not necessary bit-exact as a result of software mixing. The audio data sent by S/PDIF is not necessarily a perfect representation of the audio data on a CD even if lossless files are used (e.g. FLAC). It is important to configure your audio player software correctly, if this matters to you.

A final thought: inexactness due to the Windows audio mixer is certainly not audible to me. If it can't be heard, does it matter? It matters to me only because I like things to work as well as they can, and I was curious about it.