/* cdr_compress.c - digital audio compression filter by "Benjamin C. W. Sittler" This program compresses audio for listening in noisy environments (car, etc.) It processes stereo big-endian 16-bit signed linear samples. */ #include #include #include #include #include #include #include /* maximum sample magnitude, expressed as a float */ #define MAX_SAMPLE 32768.0 int verbose = 0; unsigned long samples, rate = 44100UL; double max_gain = 50.0, volume = 0.1, seconds = 4.0, cr = 3.6; struct { char *name; FILE *stream; } infile, outfile; short *buffer = 0; /* compress a CDR stream */ int compress(filename) char *filename; { size_t nread; unsigned long head, middle, used, samples_read; double gain, sum, fsample, rms; gain = 1.0; sum = 0.0; infile.name = filename; if ((*infile.name == '-') && ! infile.name[1]) { infile.name = "stdin"; infile.stream = stdin; } else if (! (infile.stream = fopen (infile.name, "rb"))) { perror (infile.name); return 1; } head = used = samples_read = 0UL; middle = samples; while (((nread = fread ((void *) (buffer + head), sizeof *buffer, 1, infile.stream)) == 1) || (used >= samples)) { /* convert the newest sample */ if (nread) { samples_read ++; buffer [head] = ntohs (buffer [head]); fsample = 1.0 * buffer[head] / MAX_SAMPLE; sum += fsample * fsample; /* recalculate gain every other sample */ if (head & 1UL) { rms = sqrt (sum / (samples * 2UL)); gain = pow (10, (1 - cr) / cr * log10 (rms / volume)); if (gain > max_gain) gain = max_gain; if (verbose && ! (samples_read % rate)) { int i; int in_c, out_c; double gain_dB, in_dB, out_dB; gain_dB = 20 * log10 (gain); in_dB = 20 * log10 (rms / volume); out_dB = gain_dB + in_dB; fprintf (stderr, "\r%+6.1fdB %+6.1fdB gain = %+6.1fdB [", in_dB, gain_dB, out_dB); if (in_dB > out_dB) { double q; q = in_dB; in_dB = out_dB; out_dB = q; in_c = '<'; out_c = '*'; } else { in_c = '*'; out_c = '>'; } for (i = -33; i < 10; i ++) fputc ((i > (in_dB + 0.5)) ? ((i > (out_dB + 0.5)) ? (i ? ((abs (i) % 6) ? ' ' : ':') : '0') : out_c) : in_c, stderr); fputc (']', stderr); fflush (stderr); } } if (used < 2UL * samples) used ++; } else { buffer [head] = 0; if (used) used --; } /* increment circular buffer pointers */ head ++; head %= 2UL * samples; middle ++; middle %= 2UL * samples; /* head dropping items from the sum */ if (used == 2UL * samples) { fsample = 1.0 * buffer[head] / MAX_SAMPLE; sum -= fsample * fsample; /* we formerly bled a little off our sum every step */ /* sum -= volume * sum / (2UL * samples); */ if (sum < 0.0) sum = 0.0; } /* write the middle samples, compressed and converted */ if ((used >= samples) && (middle & 1UL)) { short converted[2]; long scaled; scaled = buffer [middle - 1] * gain; if (scaled >= MAX_SAMPLE) scaled = MAX_SAMPLE - 1; else if (scaled < -MAX_SAMPLE) scaled = -MAX_SAMPLE; converted [0] = htons (scaled); scaled = buffer [middle] * gain; if (scaled >= MAX_SAMPLE) scaled = MAX_SAMPLE - 1; else if (scaled < -MAX_SAMPLE) scaled = -MAX_SAMPLE; converted [1] = htons (scaled); if (! (fwrite ((void *) converted, sizeof *converted, sizeof converted/sizeof *converted, outfile.stream))) { perror (outfile.name); exit (1); } } } if (! feof (infile.stream)) { perror (infile.name); return 1; } if (verbose) { unsigned long hundredths; hundredths = (samples_read / 2UL) * 100.0 / rate; fprintf (stderr, "\n%s: compressed [%lu:%2.2lu.%2.2lu]\n", infile.name, hundredths / 6000UL, (hundredths / 100UL) % 60UL, hundredths % 100UL); } fclose (infile.stream); return 0; } /* parse command-line arguments */ int parse(argc, argv) char **argv; { int i; while ((i = getopt (argc, argv, "c:hm:o:r:s:t:v")) != -1) switch (i) { case 'c': cr = strtod (optarg, &optarg); if (*optarg) { fprintf (stderr, "%s: option requires a numeric argument -- %c\n", *argv, i); goto usage; } if (cr <= 0.0) { fprintf (stderr, "%s: compression ratio must be positive\n", *argv); goto usage; } break; case 'h': printf ("Usage: %s [options] [files]\n", *argv); printf (" -c NUM set the compression ratio to NUM (default: %g)\n", cr); printf (" -h print this summary and exit\n"); printf (" -m NUM[dB] set maximum gain to NUM (default: %gdB)\n", 20 * log10 (max_gain)); printf (" [Add the dB suffix when setting gain in decibels.]\n"); printf (" -o FILE output to FILE (default: %s)\n", outfile.name ? outfile.name : "stdout"); printf (" -r NUM set the sample rate to NUM Hz (default: %lu)\n", rate); printf (" -s NUM set window size to NUM seconds (default: %g)\n", seconds); printf (" -t NUM set target RMS volume level to NUM (default: %g)\n", volume); printf (" -v generate verbose status information\n"); printf ("If no files are given, input is from stdin.\n"); exit (0); case 'm': max_gain = strtod (optarg, &optarg); if (! strcasecmp (optarg, "dB")) { max_gain = pow (10, max_gain / 20); } else if (*optarg) { fprintf (stderr, "%s: option requires a numeric argument -- %c\n", *argv, i); goto usage; } break; case 'o': if ((*optarg == '-') && ! optarg[1]) outfile.name = 0; else outfile.name = optarg; break; case 'r': rate = strtoul (optarg, &optarg, 0); if (*optarg || (rate < 1UL)) { fprintf (stderr, "%s: option requires a positive integer argument -- %c\n", *argv, i); goto usage; } break; case 's': seconds = strtod (optarg, &optarg); if (*optarg) { fprintf (stderr, "%s: option requires a numeric argument -- %c\n", *argv, i); goto usage; } break; case 't': volume = strtod (optarg, &optarg); if (*optarg) { fprintf (stderr, "%s: option requires a numeric argument -- %c\n", *argv, i); goto usage; } break; case 'v': verbose = 1; break; default: goto usage; } return 0; usage: printf ("Usage: %s [-h] [options] [files]\n", *argv); exit (2); } /* central dispatcher */ int main(argc, argv) char **argv; { int retval; retval = 0; parse (argc, argv); samples = seconds * rate; if (samples < 3) { fprintf (stderr, "%s: seconds must be at least %g\n", *argv, 3.0 / rate); return 2; } if (! outfile.name) { outfile.name = "stdout"; outfile.stream = stdout; } else if (! (outfile.stream = fopen (outfile.name, "wb"))) { perror (outfile.name); return 1; } if (! (buffer = (short *) malloc (samples * 2 * sizeof *buffer))) { perror ("malloc"); return 1; } if (optind < argc) while (optind < argc) retval += compress (argv[optind ++]); else retval += compress ("-"); free ((void *) buffer); return retval; }