ref: a06902b53f50964e680b284c7647e90175baac05
dir: /src/sox.c/
/* * Sox - The Swiss Army Knife of Audio Manipulation. * * This is the main function for the command line sox program. * * July 5, 1991 * Copyright 1991 Lance Norskog And Sundry Contributors * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Lance Norskog And Sundry Contributors are not responsible for * the consequences of using this software. * * Change History: * * June 1, 1998 - Chris Bagwell (cbagwell@sprynet.com) * Added patch to get volume working again. Based on patch sent from * Matija Nalis <mnalis@public.srce.hr>. * Added command line switches to force format to ADPCM or GSM. * * September 12, 1998 - Chris Bagwell (cbagwell@sprynet.com) * Reworked code that handled effects. Wasn't correctly draining * stereo effects and a few other problems. * Made command usage (-h) show supported effects and file formats. * (this is partially from a patch by Leigh Smith * leigh@psychokiller.dialix.oz.au). * */ #include "st_i.h" #include <stdio.h> #include <string.h> #include <stdlib.h> /* for malloc() */ #include <signal.h> #ifdef HAVE_ERRNO_H #include <errno.h> #endif #ifdef HAVE_UNISTD_H #include <unistd.h> /* for unlink() */ #endif #ifdef HAVE_GETOPT_H #include <getopt.h> #else #ifndef HAVE_GETOPT int getopt(int,char **,char *); extern char *optarg; extern int optind; #endif #endif #include <sys/types.h> /* for fstat() */ #include <sys/stat.h> /* for fstat() */ #ifdef _MSC_VER /* * __STDC__ is defined, so these symbols aren't created. */ #define S_IFMT _S_IFMT #define S_IFREG _S_IFREG #define fstat _fstat #define strdup _strdup #endif /* * SOX main program. * * Rewrite for new nicer option syntax. July 13, 1991. * Rewrite for separate effects library. Sep. 15, 1991. * Incorporate Jimen Ching's fixes for real library operation: Aug 3, 1994. * Rewrite for multiple effects: Aug 24, 1994. */ static int clipped = 0; /* Volume change clipping errors */ static int writing = 1; /* are we writing to a file? assume yes. */ static int soxpreview = 0; /* preview mode */ static int user_abort = 0; static int quiet = 0; static int status = 0; static unsigned long input_samples = 0; static unsigned long read_samples = 0; static unsigned long output_samples = 0; static st_sample_t ibufl[ST_BUFSIZ/2]; /* Left/right interleave buffers */ static st_sample_t ibufr[ST_BUFSIZ/2]; static st_sample_t obufl[ST_BUFSIZ/2]; static st_sample_t obufr[ST_BUFSIZ/2]; typedef struct file_options { char *filename; char *filetype; st_signalinfo_t info; double volume; char uservolume; char *comment; } file_options_t; /* local forward declarations */ static void doopts(file_options_t *fo, int, char **); static void usage(char *) NORET; static void process(void); static void print_input_status(int input); static void update_status(void); static void statistics(void); static st_sample_t volumechange(st_sample_t *buf, st_ssize_t ct, double vol); static void parse_effects(int argc, char **argv); static void check_effects(void); static void start_effects(void); static void reserve_effect_buf(void); static int flow_effect_out(void); static int flow_effect(int); static int drain_effect_out(void); static int drain_effect(int); static void release_effect_buf(void); static void stop_effects(void); void cleanup(void); static void sigint(int s); #define MAX_INPUT_FILES 32 #define MAX_FILES MAX_INPUT_FILES + 1 #ifdef SOXMIX #define REQUIRED_INPUT_FILES 2 #else #define REQUIRED_INPUT_FILES 1 #endif /* Array's tracking input and output files */ static file_options_t *file_opts[MAX_FILES]; static ft_t file_desc[MAX_FILES]; static int file_count = 0; static int input_count = 0; /* We parse effects into a temporary effects table and then place into * the real effects table. This makes it easier to reorder some effects * as needed. For instance, we can run a resampling effect before * converting a mono file to stereo. This allows the resample to work * on half the data. * * Real effects table only needs to be 2 entries bigger then the user * specified table. This is because at most we will need to add * a resample effect and a channel averaging effect. */ #define MAX_EFF 16 #define MAX_USER_EFF 14 /* * efftab[0] is a dummy entry used only as an input buffer for * reading input data into. * * If one was to support effects for quad-channel files, there would * need to be an effect table for each channel to handle effects * that done st ST_EFF_MCHAN. */ static struct st_effect efftab[MAX_EFF]; /* left/mono channel effects */ static struct st_effect efftabR[MAX_EFF];/* right channel effects */ static int neffects; /* # of effects to run on data */ static int input_eff; /* last input effect with data */ static struct st_effect user_efftab[MAX_USER_EFF]; static int nuser_effects; int main(int argc, char **argv) { file_options_t *fo; int i; myname = argv[0]; /* Loop over arguments and filenames, stop when an effect name is * found. */ while (optind < argc && st_checkeffect(argv[optind]) != ST_SUCCESS) { if (file_count >= MAX_FILES) { st_fail("to many filenames. max of %d input files and 1 output files\n", MAX_INPUT_FILES); } /* * Its possible to not specify the output filename by using * "-e" option. This allows effects to be ran on data but * no output file to be written. */ if (strcmp(argv[optind], "-e") == 0) { /* -e option found. Only thing valid after an -e * option are effects. */ optind++; if (optind >= argc || st_checkeffect(argv[optind]) == ST_SUCCESS) { writing = 0; } else { usage("Can only specify \"-e\" for output filenames"); } } else { fo = (file_options_t *)calloc(sizeof(file_options_t), 1); fo->info.size = -1; fo->info.encoding = -1; fo->info.channels = -1; fo->volume = 1.0; file_opts[file_count++] = fo; doopts(fo, argc, argv); if (optind < argc) { fo->filename = strdup(argv[optind]); optind++; } else { usage("missing filename"); } } } /* while (commandline options) */ if (writing) input_count = file_count - 1; else input_count = file_count; /* Make sure we got at least the required # of input filenames */ if (input_count < REQUIRED_INPUT_FILES) usage("Not enough input or output filenames specified"); for (i = 0; i < input_count; i++) { #ifdef SOXMIX /* When mixing audio, default to input side volume * adjustments that will make sure no clipping will * occur. Users most likely won't be happy with * this and will want to override it. */ if (!file_opts[i]->uservolume) file_opts[i]->volume = 1.0 / input_count; #endif file_desc[i] = st_open_read(file_opts[i]->filename, &file_opts[i]->info, file_opts[i]->filetype); if (!file_desc[i]) { /* st_open_read() will call st_warn for most errors. * Rely on that printing something. */ cleanup(); exit(2); } } /* Loop through the reset of the arguments looking for effects */ parse_effects(argc, argv); signal(SIGINT, sigint); signal(SIGTERM, sigint); process(); statistics(); for (i = 0; i < file_count; i++) free(file_desc[i]); if (status) { if (user_abort) fprintf(stderr, "\n\nAborted.\n"); else fprintf(stderr, "\n\nDone.\n"); } return(0); } #ifdef HAVE_GETOPT_H static char *getoptstr = "+r:v:t:c:phsuUAaigbwlfdxVSq"; #else static char *getoptstr = "r:v:t:c:phsuUAaigbwlfdxVSq"; #endif static void doopts(file_options_t *fo, int argc, char **argv) { int c, i; char *str; while ((c = getopt(argc, argv, getoptstr)) != -1) { switch(c) { case 'p': soxpreview++; break; case 'h': usage((char *)0); /* no return from above */ case 't': fo->filetype = optarg; if (fo->filetype[0] == '.') fo->filetype++; break; case 'r': str = optarg; if ((!sscanf(optarg, "%u", &fo->info.rate)) || (fo->info.rate <= 0)) st_fail("-r must be given a positive integer"); break; case 'v': str = optarg; if (!sscanf(str, "%lf", &fo->volume)) st_fail("Volume value '%s' is not a number", optarg); fo->uservolume = 1; if (fo->volume < 0.0) st_report("Volume adjustment is negative. This will result in a phase change\n"); break; case 'c': str = optarg; if (!sscanf(str, "%d", &i)) st_fail("-c must be given a number"); /* Since we use -1 as a special internal value, * we must do some extra logic so user doesn't * get confused when we translate -1 to mean * something valid. */ if (i < 1) st_fail("-c must be given a positive number"); fo->info.channels = i; break; case 'b': fo->info.size = ST_SIZE_BYTE; break; case 'w': fo->info.size = ST_SIZE_WORD; break; case 'l': fo->info.size = ST_SIZE_DWORD; break; case 'd': fo->info.size = ST_SIZE_DDWORD; break; case 's': fo->info.encoding = ST_ENCODING_SIGN2; break; case 'u': fo->info.encoding = ST_ENCODING_UNSIGNED; break; case 'U': fo->info.encoding = ST_ENCODING_ULAW; if (fo->info.size == -1) fo->info.size = ST_SIZE_BYTE; break; case 'A': fo->info.encoding = ST_ENCODING_ALAW; if (fo->info.size == -1) fo->info.size = ST_SIZE_BYTE; break; case 'f': fo->info.encoding = ST_ENCODING_FLOAT; break; case 'a': fo->info.encoding = ST_ENCODING_ADPCM; break; case 'i': fo->info.encoding = ST_ENCODING_IMA_ADPCM; break; case 'g': fo->info.encoding = ST_ENCODING_GSM; break; case 'x': fo->info.swap = 1; break; case 'V': verbose = 1; break; case 'S': status = 1; quiet = 0; break; case 'q': status = 0; quiet = 1; break; } } } static int compare_input(ft_t ft1, ft_t ft2) { if (ft1->info.rate != ft2->info.rate) return ST_EOF; if (ft1->info.size != ft2->info.size) return ST_EOF; if (ft1->info.encoding != ft2->info.encoding) return ST_EOF; if (ft1->info.channels != ft2->info.channels) return ST_EOF; return ST_SUCCESS; } void optimize_trim(void) { /* Speed hack. If the "trim" effect is the first effect then * peak inside its "effect descriptor" and see what the * start location is. This has to be done after its start() * is called to have the correct location. * Also, only do this when only working with one input file. * This is because the logic to do it for multiple files is * complex and problably never used. */ if (input_count == 1 && neffects > 1 && strcmp(efftab[1].name, "trim") == 0) { if ((file_desc[0]->h->flags & ST_FILE_SEEK) && file_desc[0]->seekable) { if (st_seek(file_desc[0], st_trim_get_start(&efftab[1]), ST_SEEK_SET) != ST_EOF) { /* Assuming a failed seek stayed where it was. If the * seek worked then reset the start location of * trim so that it thinks user didn't request a skip. */ st_trim_clear_start(&efftab[1]); } } } } /* * Process input file -> effect table -> output file * one buffer at a time */ static void process(void) { int e, f, flowstatus = ST_SUCCESS; #ifndef SOXMIX int current_input; st_ssize_t ilen; #else st_size_t s; st_ssize_t ilen[MAX_INPUT_FILES]; st_sample_t *ibuf[MAX_INPUT_FILES]; #endif for (f = 0; f < input_count; f++) { st_report("Input file %s: using sample rate %lu\n\tsize %s, encoding %s, %d %s", file_desc[f]->filename, file_desc[f]->info.rate, st_sizes_str[(unsigned char)file_desc[f]->info.size], st_encodings_str[(unsigned char)file_desc[f]->info.encoding], file_desc[f]->info.channels, (file_desc[f]->info.channels > 1) ? "channels" : "channel"); if (file_desc[f]->comment) st_report("Input file %s: comment \"%s\"\n", file_desc[f]->filename, file_desc[f]->comment); } for (f = 1; f < input_count; f++) { if (compare_input(file_desc[0], file_desc[f]) != ST_SUCCESS) { st_fail("Input files must have the same rate, channels, data size, and encoding"); } } if (writing) { st_loopinfo_t loops[ST_MAX_NLOOPS]; double factor; int i; if (file_opts[file_count-1]->info.rate == 0) file_opts[file_count-1]->info.rate = file_desc[0]->info.rate; if (file_opts[file_count-1]->info.size == -1) file_opts[file_count-1]->info.size = file_desc[0]->info.size; if (file_opts[file_count-1]->info.encoding == -1) file_opts[file_count-1]->info.encoding = file_desc[0]->info.encoding; if (file_opts[file_count-1]->info.channels == -1) file_opts[file_count-1]->info.channels = file_desc[0]->info.channels; /* * copy loop info, resizing appropriately * it's in samples, so # channels don't matter * FIXME: This doesn't work for multi-file processing or * effects that change file length. */ factor = (double) file_opts[file_count-1]->info.rate / (double) file_desc[0]->info.rate; for(i = 0; i < ST_MAX_NLOOPS; i++) { loops[i].start = file_desc[0]->loops[i].start * factor; loops[i].length = file_desc[0]->loops[i].length * factor; loops[i].count = file_desc[0]->loops[i].count; loops[i].type = file_desc[0]->loops[i].type; } file_desc[file_count-1] = st_open_write_instr(file_opts[file_count-1]->filename, &file_opts[file_count-1]->info, file_opts[file_count-1]->filetype, file_desc[0]->comment, &file_desc[0]->instr, loops); if (!file_desc[file_count-1]) { /* st_open_write() will call st_warn for most errors. * Rely on that printing something. */ cleanup(); exit(2); } /* When writing to an audio device, auto turn on the * status display to match behavior of ogg123 status. * That is unless user requested us not to display] * anything. */ if (strcmp(file_desc[file_count-1]->filetype, "alsa") == 0 || strcmp(file_desc[file_count-1]->filetype, "ossdsp") == 0 || strcmp(file_desc[file_count-1]->filetype, "sunau") == 0) { if (!quiet) status = 1; } st_report("Output file %s: using sample rate %lu\n\tsize %s, encoding %s, %d %s", file_desc[file_count-1]->filename, file_desc[file_count-1]->info.rate, st_sizes_str[(unsigned char)file_desc[file_count-1]->info.size], st_encodings_str[(unsigned char)file_desc[file_count-1]->info.encoding], file_desc[file_count-1]->info.channels, (file_desc[file_count-1]->info.channels > 1) ? "channels" : "channel"); if (file_desc[file_count-1]->comment) st_report("Output file: comment \"%s\"\n", file_desc[file_count-1]->comment); } /* build efftab */ check_effects(); /* Start all effects */ start_effects(); /* Reserve an output buffer for all effects */ reserve_effect_buf(); /* Try to save some time if first effect is "trim" by seeking */ optimize_trim(); #ifdef SOXMIX for (f = 0; f < input_count; f++) { /* Treat overall length the same as longest input file. */ if (file_desc[f]->length > input_samples) input_samples = file_desc[f]->length; ibuf[f] = (st_sample_t *)malloc(ST_BUFSIZ * sizeof(st_sample_t)); if (!ibuf[f]) { st_fail("could not allocate memory"); } if (status) print_input_status(f); } #else current_input = 0; input_samples = file_desc[current_input]->length; if (status) print_input_status(current_input); #endif /* * Just like errno, we must set st_errno to known values before * calling I/O operations. */ for (f = 0; f < file_count; f++) file_desc[f]->st_errno = 0; input_eff = 0; /* mark chain as empty */ for(e = 1; e < neffects; e++) efftab[e].odone = efftab[e].olen = 0; /* Run input data through effects and get more until olen == 0 * (or ST_EOF). */ do { #ifndef SOXMIX ilen = st_read(file_desc[current_input], efftab[0].obuf, (st_ssize_t)ST_BUFSIZ); if (ilen > ST_BUFSIZ) { st_warn("WARNING: Corrupt value of %d! Assuming 0 bytes read.\n", ilen); ilen = 0; } /* FIXME: libst needs the feof() and ferror() concepts * to see if ST_EOF means a real failure. Until then we * must treat ST_EOF as just hiting the end of the file. */ if (ilen == ST_EOF) efftab[0].olen = 0; else efftab[0].olen = ilen; read_samples += efftab[0].olen; /* Some file handlers claim 0 bytes instead of returning * ST_EOF. In either case, attempt to go to the next * input file. */ if (ilen == ST_EOF || efftab[0].olen == 0) { if (current_input < input_count-1) { current_input++; input_samples = file_desc[current_input]->length; read_samples = 0; if (status) print_input_status(current_input); continue; } } /* Adjust input side volume based on value specified * by user for this file. */ if (file_opts[current_input]->volume != 1.0) clipped += volumechange(efftab[0].obuf, efftab[0].olen, file_opts[current_input]->volume); #else for (f = 0; f < input_count; f++) { ilen[f] = st_read(file_desc[f], ibuf[f], (st_ssize_t)ST_BUFSIZ); /* FIXME: libst needs the feof() and ferror() concepts * to see if ST_EOF means a real failure. Until then we * must treat ST_EOF as just hiting the end of the buffer. */ if (ilen[f] == ST_EOF) ilen[f] = 0; /* Only count read samples for first file in mix */ if (f == 0) read_samples += efftab[0].olen; /* Adjust input side volume based on value specified * by user for this file. */ if (file_opts[f]->volume != 1.0) clipped += volumechange(ibuf[f], ilen[f], file_opts[f]->volume); } /* FIXME: Should report if the size of the reads are not * the same. */ efftab[0].olen = 0; for (f = 0; f < input_count; f++) if ((st_size_t)ilen[f] > efftab[0].olen) efftab[0].olen = ilen[f]; for (s = 0; s < efftab[0].olen; s++) { /* Mix data together by summing samples together. * It is assumed that input side volume adjustments * will take care of any possible overflow. * By default, SoX sets the volume adjustment * to 1/input_count but the user can override this. * They probably will and some clipping will probably * occur because of this. */ for (f = 0; f < input_count; f++) { if (f == 0) efftab[0].obuf[s] = (s<(st_size_t)ilen[f]) ? ibuf[f][s] : 0; else if (s < (st_size_t)ilen[f]) { double sample; sample = efftab[0].obuf[s] + ibuf[f][s]; ST_SAMPLE_CLIP_COUNT(sample, clipped); efftab[0].obuf[s] = sample; } } } #endif efftab[0].odone = 0; /* If not writing and no effects are occuring then not much * reason to continue reading. This allows this case. Mainly * useful to print out info about input file header and quiet. */ if (!writing && neffects == 1) efftab[0].olen = 0; if (efftab[0].olen == 0) break; flowstatus = flow_effect_out(); if (status) update_status(); /* Quite reading/writing on user aborts. This will close * done the files nicely as if an EOF was reached on read. */ if (user_abort) break; /* Negative flowstatus says no more output will ever be generated. */ if (flowstatus == ST_EOF || (writing && file_desc[file_count-1]->st_errno)) break; } while (1); /* This will drain the effects */ drain_effect_out(); #ifdef SOXMIX /* Free input buffers now that they are not used */ for (f = 0; f < input_count; f++) { free(ibuf[f]); } #endif /* Free output buffers now that they won't be used */ release_effect_buf(); /* Very Important: * Effect stop is called BEFORE files close. * Effect may write out more data after. */ stop_effects(); for (f = 0; f < input_count; f++) { /* If problems closing input file, just warn user since * we are exiting anyways. */ if (st_close(file_desc[f]) == ST_EOF) st_warn("%s", file_desc[f]->st_errstr); } if (writing) { /* problem closing output file, just warn user since we * are exiting anyways. */ if (st_close(file_desc[file_count-1]) == ST_EOF) st_warn("%s", file_desc[file_count-1]->st_errstr); } } static void parse_effects(int argc, char **argv) { int argc_effect; nuser_effects = 0; while (optind < argc) { if (nuser_effects >= MAX_USER_EFF) { st_fail("To many effects specified.\n"); } argc_effect = st_geteffect_opt(&user_efftab[nuser_effects], argc - optind, &argv[optind]); if (argc_effect == ST_EOF) { int i1; fprintf(stderr, "%s: Known effects: ",myname); for (i1 = 0; st_effects[i1].name; i1++) fprintf(stderr, "%s ", st_effects[i1].name); fprintf(stderr, "\n\n"); st_fail("Effect '%s' is not known!", argv[optind]); } /* Skip past effect name */ optind++; (*user_efftab[nuser_effects].h->getopts) (&user_efftab[nuser_effects], argc_effect, &argv[optind]); /* Skip past the effect arguments */ optind += argc_effect; nuser_effects++; } } /* * If no effect given, decide what it should be. * Smart ruleset for multiple effects in sequence. * Puts user-specified effect in right place. */ static void check_effects(void) { int i; int needchan = 0, needrate = 0, haschan = 0, hasrate = 0; int effects_mask = 0; if (writing) { needrate = (file_desc[0]->info.rate != file_desc[file_count-1]->info.rate); needchan = (file_desc[0]->info.channels != file_desc[file_count-1]->info.channels); } for (i = 0; i < nuser_effects; i++) { if (user_efftab[i].h->flags & ST_EFF_CHAN) { haschan++; } if (user_efftab[i].h->flags & ST_EFF_RATE) { hasrate++; } } if (haschan > 1) st_fail("Can not specify multiple effects that modify channel #"); if (hasrate > 1) st_report("Can not specify multiple effects that change sample rate"); /* If not writing output then do not worry about adding * channel and rate effects. This is just to speed things * up. */ if (!writing) { needchan = 0; needrate = 0; } /* --------- add the effects ------------------------ */ /* efftab[0] is always the input stream and always exists */ neffects = 1; /* If reducing channels then its faster to run all effects * after the avg effect. */ if (needchan && !(haschan) && (file_desc[0]->info.channels > file_desc[file_count-1]->info.channels)) { /* Find effect and update initial pointers */ st_geteffect(&efftab[neffects], "avg"); /* give default opts for added effects */ /* FIXME: Should look at return code and abort on ST_EOF */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], &file_desc[0]->info, &file_desc[file_count-1]->info, effects_mask); neffects++; } /* If reducing the number of samples, its faster to run all effects * after the resample effect. */ if (needrate && !(hasrate) && (file_desc[0]->info.rate > file_desc[file_count-1]->info.rate)) { if (soxpreview) st_geteffect(&efftab[neffects], "rate"); else st_geteffect(&efftab[neffects], "resample"); /* set up & give default opts for added effects */ /* FIXME: Should look at return code and abort on ST_EOF */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], &file_desc[0]->info, &file_desc[file_count-1]->info, effects_mask); /* Rate can't handle multiple channels so be sure and * account for that. */ if (efftab[neffects].ininfo.channels > 1) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* Copy over user specified effects into real efftab */ for(i = 0; i < nuser_effects; i++) { memcpy(&efftab[neffects], &user_efftab[i], sizeof(struct st_effect)); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], &file_desc[0]->info, &file_desc[file_count-1]->info, effects_mask); /* If this effect can't handle multiple channels then * account for this. */ if ((efftab[neffects].ininfo.channels > 1) && !(efftab[neffects].h->flags & ST_EFF_MCHAN)) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* If rate effect hasn't been added by now then add it here. * Check adding rate before avg because its faster to run * rate on less channels then more. */ if (needrate && !(effects_mask & ST_EFF_RATE)) { if (soxpreview) st_geteffect(&efftab[neffects], "rate"); else st_geteffect(&efftab[neffects], "resample"); /* set up & give default opts for added effects */ /* FIXME: Should look at return code and abort on ST_EOF */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], &file_desc[0]->info, &file_desc[file_count-1]->info, effects_mask); /* Rate can't handle multiple channels so be sure and * account for that. */ if (efftab[neffects].ininfo.channels > 1) { memcpy(&efftabR[neffects], &efftab[neffects], sizeof(struct st_effect)); } neffects++; } /* If code up until now still hasn't added avg effect then * do it now. */ if (needchan && !(effects_mask & ST_EFF_CHAN)) { st_geteffect(&efftab[neffects], "avg"); /* set up & give default opts for added effects */ /* FIXME: Should look at return code and abort on ST_EOF */ (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0, (char **)0); /* Copy format info to effect table */ effects_mask = st_updateeffect(&efftab[neffects], &file_desc[0]->info, &file_desc[file_count-1]->info, effects_mask); neffects++; } } static void start_effects(void) { int e; for(e = 1; e < neffects; e++) { (*efftab[e].h->start)(&efftab[e]); if (efftabR[e].name) (*efftabR[e].h->start)(&efftabR[e]); } } static void reserve_effect_buf(void) { int e; for(e = 0; e < neffects; e++) { efftab[e].obuf = (st_sample_t *)malloc(ST_BUFSIZ * sizeof(st_sample_t)); if (efftab[e].obuf == NULL) { st_fail("could not allocate memory"); } if (efftabR[e].name) { efftabR[e].obuf = (st_sample_t *)malloc(ST_BUFSIZ * sizeof(st_sample_t)); if (efftabR[e].obuf == NULL) { st_fail("could not allocate memory"); } } } } static int flow_effect_out(void) { int e, havedata, flowstatus = 0; int len, total; do { /* run entire chain BACKWARDS: pull, don't push.*/ /* this is because buffering system isn't a nice queueing system */ for(e = neffects - 1; e >= input_eff; e--) { /* flow_effect returns ST_EOF when it will not process * any more samples. This is used to bail out early. * Since we are "pulling" data, it is OK that we are not * calling any more previous effects since their output * would not be looked at anyways. */ flowstatus = flow_effect(e); if (flowstatus == ST_EOF) input_eff = e+1; /* If this buffer contains more input data then break out * of this loop now. This will allow us to loop back around * and reprocess the rest of this input buffer. * I suppose this could be an issue with some effects * if they crash when given small input buffers. * But I was more concerned that we would need to do * some type of garbage collection otherwise. By this I * mean that if we went ahead and processed an effect * lower in the chain, it might only have like 2 bytes * left at the end of this buffer to place its data in. * Software is more likely to refuse to handle that. */ if (efftab[e].odone < efftab[e].olen) { #if 0 fprintf(stderr, "Breaking out of loop to flush buffer\n"); #endif break; } } /* If outputing and output data was generated then write it */ if (writing && (efftab[neffects-1].olen>efftab[neffects-1].odone)) { /* Change the volume of this output data if needed. */ if (writing && file_opts[file_count-1]->volume != 1.0) clipped += volumechange(efftab[neffects-1].obuf, efftab[neffects-1].olen, file_opts[file_count-1]->volume); /* FIXME: Should look at return code and abort * on ST_EOF */ total = 0; do { /* Do not do any more writing during user aborts as * we may be stuck in an infinite writing loop. */ if (user_abort) return ST_EOF; len = st_write(file_desc[file_count-1], &efftab[neffects-1].obuf[total], (st_ssize_t)efftab[neffects-1].olen-total); if (len < 0) { st_warn("Error writing: %s", file_desc[file_count-1]->st_errstr); return ST_EOF; } total += len; } while (total < efftab[neffects-1].olen); output_samples += (total / file_desc[file_count-1]->info.channels); efftab[neffects-1].odone = efftab[neffects-1].olen = 0; } else { /* Make it look like everything was consumed */ output_samples += (efftab[neffects-1].olen / file_desc[file_count-1]->info.channels); efftab[neffects-1].odone = efftab[neffects-1].olen = 0; } /* if stuff still in pipeline, set up to flow effects again */ /* When all effects have reported ST_EOF then this check will * show no more data. */ havedata = 0; for(e = neffects - 1; e >= input_eff; e--) { /* If odone and olen are the same then this buffer * can be reused. */ if (efftab[e].odone == efftab[e].olen) efftab[e].odone = efftab[e].olen = 0; if (efftab[e].odone < efftab[e].olen) { /* Only mark that we have more data if a full * frame that can be written. * FIXME: If this error case happens for the * input buffer then the data will be lost and * will cause stereo channels to be inversed. */ if ((efftab[e].olen - efftab[e].odone) >= file_desc[file_count-1]->info.channels) havedata = 1; else st_warn("Received buffer with incomplete amount of samples."); /* Don't break out because other things are being * done in loop. */ } } if (!havedata && input_eff > 0) { /* If the input file is not returning data then * we must prime the pump using the drain effect. * After its primed, the loop will suck the data * threw. Once an input_eff stop reporting samples, * we will continue to the next until all are drained. */ while (input_eff < neffects) { int rc; rc = drain_effect(input_eff); if (rc == ST_EOF || efftab[input_eff].olen == 0) input_eff++; else { havedata = 1; break; } } } } while (havedata); /* If input_eff isn't pointing at fake first entry then there * is no need to read any more data from disk. Return this * fact to caller. */ if (input_eff > 0) { #if 0 fprintf(stderr, "Effect return ST_EOF\n"); #endif return ST_EOF; } return ST_SUCCESS; } static int flow_effect(int e) { st_ssize_t i, done, idone, odone, idonel, odonel, idoner, odoner; st_sample_t *ibuf, *obuf; int effstatus; /* Do not attempt to do any more effect processing during * user aborts as we may be stuck in an infinit flow loop. */ if (user_abort) return ST_EOF; /* I have no input data ? */ if (efftab[e-1].odone == efftab[e-1].olen) { #if 0 fprintf(stderr, "%s no data to pull to me!\n", efftab[e].name); #endif return 0; } if (! efftabR[e].name) { /* No stereo data, or effect can handle stereo data so * run effect over entire buffer. */ idone = efftab[e-1].olen - efftab[e-1].odone; odone = ST_BUFSIZ - efftab[e].olen; #if 0 fprintf(stderr, "pre %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); fprintf(stderr, "pre %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); #endif effstatus = (* efftab[e].h->flow)(&efftab[e], &efftab[e-1].obuf[efftab[e-1].odone], &efftab[e].obuf[efftab[e].olen], (st_size_t *)&idone, (st_size_t *)&odone); efftab[e-1].odone += idone; /* Leave efftab[e].odone were it was since we didn't consume data */ /*efftab[e].odone = 0;*/ efftab[e].olen += odone; #if 0 fprintf(stderr, "post %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); fprintf(stderr, "post %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); #endif done = idone + odone; } else { /* Put stereo data in two seperate buffers and run effect * on each of them. */ idone = efftab[e-1].olen - efftab[e-1].odone; odone = ST_BUFSIZ - efftab[e].olen; ibuf = &efftab[e-1].obuf[efftab[e-1].odone]; for(i = 0; i < idone; i += 2) { ibufl[i/2] = *ibuf++; ibufr[i/2] = *ibuf++; } /* left */ idonel = (idone + 1)/2; /* odd-length logic */ odonel = odone/2; #if 0 fprintf(stderr, "pre %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); fprintf(stderr, "pre %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); #endif effstatus = (* efftab[e].h->flow)(&efftab[e], ibufl, obufl, (st_size_t *)&idonel, (st_size_t *)&odonel); /* right */ idoner = idone/2; /* odd-length logic */ odoner = odone/2; /* FIXME: effstatus of previous operation is lost. */ effstatus = (* efftabR[e].h->flow)(&efftabR[e], ibufr, obufr, (st_size_t *)&idoner, (st_size_t *)&odoner); obuf = &efftab[e].obuf[efftab[e].olen]; /* This loop implies left and right effect will always output * the same amount of data. */ for(i = 0; i < odoner; i++) { *obuf++ = obufl[i]; *obuf++ = obufr[i]; } efftab[e-1].odone += idonel + idoner; /* Don't clear since nothng has been consumed yet */ /*efftab[e].odone = 0;*/ efftab[e].olen += odonel + odoner; #if 0 fprintf(stderr, "post %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); fprintf(stderr, "post %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); #endif done = idonel + idoner + odonel + odoner; } if (effstatus == ST_EOF) { return ST_EOF; } if (done == 0) st_fail("Effect took & gave no samples!"); return ST_SUCCESS; } static int drain_effect_out(void) { /* Skip past input effect since we know thats not needed */ if (input_eff == 0) input_eff = 1; /* Try to prime the pump with some data */ while (input_eff < neffects) { int rc; rc = drain_effect(input_eff); if (rc == ST_EOF || efftab[input_eff].olen == 0) input_eff++; else break; } /* Just do standard flow routines after the priming. */ return flow_effect_out(); } static int drain_effect(int e) { st_ssize_t i, olen, olenl, olenr; st_sample_t *obuf; int rc; if (! efftabR[e].name) { efftab[e].olen = ST_BUFSIZ; rc = (* efftab[e].h->drain)(&efftab[e],efftab[e].obuf, &efftab[e].olen); efftab[e].odone = 0; } else { int rc_l, rc_r; olen = ST_BUFSIZ; /* left */ olenl = olen/2; rc_l = (* efftab[e].h->drain)(&efftab[e], obufl, (st_size_t *)&olenl); /* right */ olenr = olen/2; rc_r = (* efftab[e].h->drain)(&efftabR[e], obufr, (st_size_t *)&olenr); if (rc_l == ST_EOF || rc_r == ST_EOF) rc = ST_EOF; else rc = ST_SUCCESS; obuf = efftab[e].obuf; /* This loop implies left and right effect will always output * the same amount of data. */ for(i = 0; i < olenr; i++) { *obuf++ = obufl[i]; *obuf++ = obufr[i]; } efftab[e].olen = olenl + olenr; efftab[e].odone = 0; } return rc; } static void release_effect_buf(void) { int e; for(e = 0; e < neffects; e++) { free(efftab[e].obuf); if (efftabR[e].obuf) free(efftabR[e].obuf); } } static void stop_effects(void) { int e; for (e = 1; e < neffects; e++) { (*efftab[e].h->stop)(&efftab[e]); if (efftabR[e].name) (* efftabR[e].h->stop)(&efftabR[e]); } } static void print_input_status(int input) { fprintf(stderr, "\nInput Filename : %s\n", file_desc[input]->filename); fprintf(stderr, "Sample Size : %s\n", st_size_bits_str[file_desc[input]->info.size]); fprintf(stderr, "Sample Encoding: %s\n", st_encodings_str[file_desc[input]->info.encoding]); fprintf(stderr, "Channels : %d\n", file_desc[input]->info.channels); fprintf(stderr, "Sample Rate : %d\n", file_desc[input]->info.rate); if (file_desc[input]->comment && *file_desc[input]->comment) fprintf(stderr, "Comments :\n%s\n", file_desc[input]->comment); fprintf(stderr, "\n"); } static void update_status(void) { int read_min, left_min, in_min; double read_sec, left_sec, in_sec; double read_time, left_time, in_time; float completed; double out_size; char unit; /* Currently, for both sox and soxmix, all input files must have * the same sample rate. So we can always just use the rate * of the first input file to compute time. */ read_time = (double)read_samples / (double)file_desc[0]->info.rate / (double)file_desc[0]->info.channels; read_min = read_time / 60; read_sec = (double)read_time - 60.0f * (double)read_min; out_size = output_samples / 1000000000.0; if (out_size >= 1.0) unit = 'G'; else { out_size = output_samples / 1000000.0; if (out_size >= 1.0) unit = 'M'; else { out_size = output_samples / 1000.0; if (out_size >= 1.0) unit = 'K'; else unit = ' '; } } if (input_samples) { in_time = (double)input_samples / (double)file_desc[0]->info.rate / (double)file_desc[0]->info.channels; left_time = in_time - read_time; if (left_time < 0) left_time = 0; completed = ((double)read_samples / (double)input_samples) * 100; if (completed < 0) completed = 0; } else { in_time = 0; left_time = 0; completed = 0; } left_min = left_time / 60; left_sec = (double)left_time - 60.0f * (double)left_min; in_min = in_time / 60; in_sec = (double)in_time - 60.0f * (double)in_min; fprintf(stderr, "\rTime: %02i:%05.2f [%02i:%05.2f] of %02i:%05.2f (% 5.1f%%) Output Buffer:% 7.2f%c", read_min, read_sec, left_min, left_sec, in_min, in_sec, completed, out_size, unit); } static void statistics(void) { if (clipped > 0) st_report("Volume change clipped %d samples", clipped); } static st_sample_t volumechange(st_sample_t *buf, st_ssize_t ct, double vol) { double y; st_sample_t *p,*top; st_ssize_t clips=0; p = buf; top = buf+ct; while (p < top) { y = vol * *p; ST_SAMPLE_CLIP_COUNT(y, clips); *p++ = y; } return clips; } #ifdef SOXMIX static char *usagestr = "[ gopts ] [ fopts ] ifile1 [fopts] ifile2 [ fopts ] ofile [ effect [ effopts ] ]"; #else static char *usagestr = "[ gopts ] [ fopts ] ifile [ fopts ] ofile [ effect [ effopts ] ]"; #endif static void usage(char *opt) { int i; fprintf(stderr, "%s: ", myname); if (verbose || !opt) fprintf(stderr, "%s\n\n", st_version()); fprintf(stderr, "Usage: %s\n\n", usagestr); if (opt) fprintf(stderr, "Failed: %s\n", opt); else { fprintf(stderr,"gopts: -e -h -p -q -S -V\n\n"); fprintf(stderr,"fopts: -r rate -c channels -s/-u/-U/-A/-a/-i/-g/-f -b/-w/-l/-d -v volume -x\n\n"); fprintf(stderr, "effect: "); for (i = 0; st_effects[i].name != NULL; i++) { fprintf(stderr, "%s ", st_effects[i].name); } fprintf(stderr, "\n\neffopts: depends on effect\n\n"); fprintf(stderr, "Supported file formats: "); for (i = 0; st_formats[i].names != NULL; i++) { /* only print the first name */ fprintf(stderr, "%s ", st_formats[i].names[0]); } fputc('\n', stderr); } exit(1); } /* called from util.c::st_fail() */ void cleanup(void) { int i; struct stat st; char *fn; /* Close the input file and outputfile before exiting*/ for (i = 0; i < input_count; i++) { if (file_desc[i]) { st_close(file_desc[i]); free(file_desc[i]); } } if (writing && file_desc[file_count-1]) { fstat(fileno(file_desc[file_count-1]->fp), &st); fn = strdup(file_desc[file_count-1]->filename); st_close(file_desc[file_count-1]); /* remove the output file because we failed, if it's ours. */ /* Don't if its not a regular file. */ if ((st.st_mode & S_IFMT) == S_IFREG) unlink(fn); free(fn); if (file_desc[file_count-1]) free(file_desc[file_count-1]); } } static void sigint(int s) { if (s == SIGINT || s == SIGTERM) { user_abort = 1; } }