This post is going to cover the method I use for decoding magnetic stripe waveforms recorded using an audio interface.
For in-depth information on how magnetic stipes are encoded, see:
And many other websites which can be found by Google.
First off, an overview of what we’re working with. I am using raw PCM audio files in the format S16 LE at 48KHz. I’ve worked with 441.KHz streams reliably, and easily scaled up to 192KHz — overall, 48KHz seems optimal. A card swipe at 48KHz generally consist of about 10k samples, and are roughly 20kb. The data on the card is represented using amplitude peaks of alternating polarity.
Here are some screen captures of the waveform:
To get the bits from the stream, we have two overall steps:
- Find the location of each peak in the stream.
- Decode the frequency of the peaks to output 0s and 1s.
To find the locations of the peaks, I follow a simple model. I take the waveform and an offset of it, and determine where the original waveform and its offset intersect. Here is an example of an offset waveform:
I then mark each time the two waveforms intersect, ignoring any intersections over a certain threshold. The threshold is to eliminate intersects that occur around 0 amplitude. There will typically be more than one intersect found per peak:
In this example, with an amplitude threshold of 750, we have consistently found two intersects per peak. It is also possible to have situations where the little hump before the actual peak results in several intersects — all of these can be filtered out very simply.
The filtering process I apply is to group the intersects detected by amplitude polarity. For instance, in the above picture, the first positive amplitude two blue crosses around sample 1010 would be a group. The second group would be the two negative amplitude crosses around sample 1075, and so forth. Once I have a group, I can pick the highest absolute value of amplitude, and reliably assume this is the true peak.
Here is the signal, now filtered:
I can then move on to the second step, which is to use the frequency of the peaks to decode the stored bits.
A quick overview of how the encoding works:
- Beginning and end of stream are padded with 0s.
- Stream is self-clocking. The padding 0s tell you the clock speed, and you adjust the clock speed as needed while traversing the stream.
- A zero bit occurs when the distance between peaks is a full cycle. A one bit appears when there are two peaks in the full cycle.
Going through the list of peaks and determining the difference between them produces a valid bitstream.
Here’s an illistration of the start sentinel above broken into cycles:
As the stream progresses, the clockspeed will reduce significantly, generally starting around 90 samples and reducing to as few as 10 to 15 samples. Since it happens gradually, you can recalculate your clockspeed after every cycle.
At the end, the card I’m using as an example is decoded to:
00000000000000001101001101000011100100001110011110000 10000100010001010101101011011100100100000010100001000 10011111001011000100100111000001000000010000110000111 11100110000000000000000000000000000000000000000000000 00000000000000000000000000000000000
Ultimately, I’m left with the decoded data:
An Albertsons Supermarket card was used as the example data on this post. The PCM file used may be downloaded here.