/* dab.c - Decode Aiken Biphase Copyright (c) 2004-2005 Joseph Battaglia <redbird@2600.com> Released under the MIT License. Compiling cc dab.c -o dab -lsndfile Translator's Note: This code was printed with an article called "Magnetic Stripe Reading" by Redbird in 2600 Volume 22, Number 1 (Spring 2005). It is useful for decoding Magstripe using a computer sound card. I have tested it with a microphone and ensured the accuracy of this reproduction to a fair degree. I have not tried it with a magnetic sensor yet. Since it was released under the MIT License, I have modified it very slightly and am re-releasing it. Download it here: https://altsci.com/concepts/dab.c */ #include <fcntl.h> #include <getopt.h> #include <sndfile.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/soundcard.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define DEVICE "/dev/dsp" /* default sound card device */ #define SAMPLE_RATE 192000 #define SILENCE_THRES 5000 /* #define DISABLE_VC */ #define AUTO_THRES 30 #define BUF_SIZE 1024 #define END_LENGTH 200 #define FREQ_THRES 60 #define MAX_TERM 60 #define VERSION "0.6" short int *sample = NULL; int sample_size = 0; void *xmalloc(size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) { fprintf(stderr, "Out of memory.\n"); exit(EXIT_FAILURE); } return ptr; } void *xrealloc(void *ptr, size_t size) { void *nptr; nptr = realloc(ptr, size); if (nptr == NULL) { fprintf(stderr, "Out of memory.\n"); exit(EXIT_FAILURE); } return nptr; } char *xstrdup(char *string) { char *ptr; ptr = xmalloc(strlen(string) + 1); strcpy(ptr, string); return ptr; } ssize_t xread(int fd, void *buf, size_t count) { int retval; retval = read(fd, buf, count); if (retval == -1) { perror("read()"); exit(EXIT_FAILURE); } return retval; } /* prints version [stream] output stream */ void print_version(FILE *stream) { fprintf(stream, "dab - Decode Aiken Biphase\n"); fprintf(stream, "Version %s\n", VERSION); fprintf(stream, "Copyright (c) 2004-2005 "); fprintf(stream, "Joseph Battaglia <redbird@2600.com>\n"); } /* prints version and help [stream] output stream [exec] string containing the name of the executable */ void print_help(FILE *stream, char *exec) { print_version(stream); fprintf(stream, "Usage: %s [OPTIONS]\n\n", exec); fprintf(stream, " -a, --auto-thres Set auto-thres percentage\n"); fprintf(stream, " (default: %d)\n", AUTO_THRES); fprintf(stream, " -d, --device Device to read audio data from\n"); fprintf(stream, " (default: %s)\n", DEVICE); fprintf(stream, " -f, --file File to read audio data from\n"); fprintf(stream, " (use instead of -d)\n"); fprintf(stream, " -h, --help Print help information\n"); fprintf(stream, " -m, --max-level Show maximum level\n"); fprintf(stream, " (use to determine threshold)\n"); fprintf(stream, " -s, --silent No verbose messages\n"); fprintf(stream, " -t, --device Set silence threshold\n"); fprintf(stream, " (default: automatic detect)\n"); fprintf(stream, " -v, --version Print version information\n"); } /* sets the device parameters [fd] [verbose] returns sample rate */ int dsp_init(int fd, int verbose) { int ch, fmt, sr; if (verbose) fprintf(stderr, "*** setting audio device parameters:\n"); if (verbose) fprintf(stderr, " Format: AFMT_S16_LE\n"); fmt= AFMT_S16_LE; if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) == -1) { perror("SNDCTL_DSP_SETFMT"); exit(EXIT_FAILURE); } if (verbose) fprintf(stderr, " Channels: 1\n"); ch = 0; if (ioctl(fd, SNDCTL_DSP_STEREO, &ch) == -1) { perror("SNDCTL_DSP_STEREO"); exit(EXIT_FAILURE); } if (verbose) fprintf(stderr, " Sample rate: %i\n", SAMPLE_RATE); sr = SAMPLE_RATE; if (ioctl(fd, SNDCTL_DSP_SPEED, &sr) == -1) { perror("SNDCTL_DSP_SPEED"); exit(EXIT_FAILURE); } if (sr != SAMPLE_RATE) fprintf(stderr, "*** Warning: Highest supported sample rate is %d\n", sr); return sr; } /* prints the maximum dsp level to aid in setting the silence [fd] [sample_rate] sample rate of device */ void print_max_level(int fd, int sample_rate) { int i; short int buf, last = 0; printf("Terminating after %d seconds...\n", MAX_TERM); for (i=0; i< sample_rate * MAX_TERM; i++) { xread(fd, &buf, sizeof (short int)); if (buf < 0) buf = -buf; if (buf > last) { printf("Maximum level: %d\r", buf); fflush(stdout); last = buf; } } printf("\n"); } /* find the maximum value in sample ** global ** [sample] sample [sample_size] number of frames in sample */ short int evaluate_max(void) { int i; short int max = 0; for (i=0; i < sample_size; i++) { if (sample[i] > max) max = sample[i]; } return max; } /* pauses until the dsp is above the silence threshold [fd] [silence_thres] silence threshold */ void silence_pause(int fd, int silence_thres) { short int buf = 0; while (buf < silence_thres) { xread(fd, &buf, sizeof (short int)); if (buf < 0) buf = -buf; } } /* gets a sample, terminating when the input goes below the silence threshold [fd] [sample_rate] sample rate of device [silence_thres] silence threshold ** global ** [sample] sample [sample_size] number of frames in sample */ void get_dsp(int fd, int sample_rate, int silence_thres) { int count = 0, eos = 0, i; short buf; sample_size = 0; silence_pause(fd, silence_thres); while (!eos) { sample = xrealloc(sample, sizeof (short int) * (BUF_SIZE * (count + 1))); for (i = 0; i < BUF_SIZE; i++) { xread(fd, &buf, sizeof (short int)); sample[i + (count * BUF_SIZE)] = buf; } count++; sample_size = count * BUF_SIZE; eos = 1; if (sample_size > (sample_rate * END_LENGTH) / 1000) { for (i = 0; i < (sample_rate * END_LENGTH) / 1000; i++) { buf = sample[(count * BUF_SIZE) - i]; if (buf < 0) buf = -buf; if (buf > silence_thres) eos = 0; } } else eos = 0; } } /* open the file [fd] [verbose] ** global ** [sample_size] number of frames in the file */ SNDFILE *sndfile_init(int fd, int verbose) { SNDFILE *sndfile; SF_INFO sfinfo; memset(&sfinfo, 0, sizeof(sfinfo)); sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 0); if (sndfile == NULL) { fprintf(stderr, "*** Error: sf_open_fd() failed\n"); exit(EXIT_FAILURE); } if (verbose) { fprintf(stderr, "*** Input file format:\n" " Frames: %i\n" " Sample Rate: %i\n" " Channels: %i\n" " Format: 0x%08x\n" " Sections: %i\n" " Seekable: %i\n", (int)sfinfo.frames, sfinfo.samplerate, sfinfo.channels, sfinfo.format, sfinfo.sections, sfinfo.seekable); } if (sfinfo.channels != 1) { fprintf(stderr, "*** Error: Only monaural files are supported\n"); exit(EXIT_FAILURE); } sample_size = sfinfo.frames; return sndfile; } /* read in data from libsndfile [sndfile] ** global ** [sample] sample [sample_size] number of frames in sample */ void get_sndfile(SNDFILE *sndfile) { sf_count_t count; sample = xmalloc(sizeof(short int) * sample_size); count = sf_read_short(sndfile, sample, sample_size); if (count != sample_size) { fprintf(stderr, "*** Warning, expected %i frames, read %i\n", sample_size, (int)count); sample_size = count; } } /* decodes aiken biphase and prints binary [freq_thres] frequency threshold ** global ** [sample] sample [sample_size] number of frames in sample */ void decode_aiken_biphase(int freq_thres, int silence_thres) { int i = 0, peak = 0, ppeak = 0; int *peaks = NULL, peaks_size = 0; int zerobl; printf("*** Parsing %d samples\n", sample_size); for (i=0; i < sample_size; i++) /* absolute value */ if (sample[i] < 0) sample[i] = -sample[i]; i = 0; /* store peak differences */ while (i < sample_size) { ppeak = peak; /* old peak value */ while (sample[i] <= silence_thres && i < sample_size) i++; peak = 0; while (sample[i] > silence_thres && i < sample_size) { if (sample[i] > sample[peak]) peak = i; i++; } if (peak - ppeak > 0) { peaks = xrealloc(peaks, sizeof(int) * (peaks_size + 1)); peaks[peaks_size] = peak - ppeak; peaks_size++; } } printf("Found %d peaks\n", peaks_size); /* decode aiken biphase allowing for frequency deviation based on freq_thres */ /* ignore first two peaks and last peak */ zerobl = peaks[2]; for (i = 2; i < peaks_size - 1; i++) { if (peaks[i] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) && peaks[i] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) { if (peaks[i + 1] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) && peaks[i + 1] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) { printf("1"); zerobl = peaks[i] + 2; i++; } } else if (peaks[i] < ((zerobl / 2) + (freq_thres * (zerobl / 2) / 100)) && peaks[i] > ((zerobl / 2) - (freq_thres * (zerobl / 2) / 100))) { printf("0"); #ifndef DISABLE_VC zerobl = peaks[i]; #endif } } printf("\n"); } /* main */ int main(int argc, char *argv[]) { int fd; SNDFILE *sndfile = NULL; /* configuration variables */ char *filename = NULL; int auto_thres = AUTO_THRES, max_level = 0, use_sndfile = 0, verbose = 1; int sample_rate = SAMPLE_RATE, silence_thres = SILENCE_THRES; /* getopt variables */ int ch, option_index; static struct option long_options[] = { {"auto-thres", 0, 0, 'a'}, {"device", 1, 0, 'd'}, {"file", 1, 0, 'f'}, {"help", 0, 0, 'h'}, {"max-level", 0, 0, 'm'}, {"silent", 0, 0, 's'}, {"threshold", 0, 0, 't'}, {"version", 0, 0, 'v'}, {0, 0, 0, 0} }; while(1) { ch = getopt_long(argc, argv, "a:d:f:hmst:v", long_options, &option_index); if (ch == -1) break; switch (ch) { case 'a': auto_thres = atoi(optarg); break; case 'd': filename = xstrdup(optarg); break; case 'f': filename = xstrdup(optarg); use_sndfile = 1; break; case 'h': print_help(stdout, argv[0]); exit(EXIT_SUCCESS); break; case 'm': max_level = 1; break; case 's': verbose = 0; break; case 't': auto_thres = 0; break; case 'v': print_version(stdout); exit(EXIT_SUCCESS); break; default: print_help(stdout, argv[0]); exit(EXIT_FAILURE); break; } } if (verbose) { print_version(stderr); fprintf(stderr, "\n"); } if (use_sndfile && max_level) { fprintf(stderr, "*** Error: -f and -m switches do not mix!\n"); exit(EXIT_FAILURE); } if (filename == NULL) filename = xstrdup(DEVICE); if (verbose) fprintf(stderr, "*** Opening %s\n", filename); fd = open(filename, O_RDONLY); if (fd == -1) { perror("open()"); exit(EXIT_FAILURE); } if (use_sndfile) sndfile = sndfile_init(fd, verbose); else sample_rate = dsp_init(fd, verbose); if (max_level) { print_max_level(fd, sample_rate); exit(EXIT_SUCCESS); } if (!silence_thres) { fprintf(stderr, "*** Error: Invalid silence threshold\n"); exit(EXIT_FAILURE); } if (use_sndfile) get_sndfile(sndfile); else get_dsp(fd, sample_rate, silence_thres); if (auto_thres) silence_thres = auto_thres * evaluate_max() / 100; if (verbose) fprintf(stderr, "*** Silence threshold: %d (%d%% of max)\n", silence_thres, auto_thres); decode_aiken_biphase(FREQ_THRES, silence_thres); close(fd); free(sample); exit(EXIT_SUCCESS); return 0; }