//////////////////////////////////////////////////////////////////////////// // **** WAVPACK **** // // Hybrid Lossless Wavefile Compressor // // Copyright (c) 1998 - 2005 Conifer Software. // // All Rights Reserved. // // Distributed under the BSD Software License (see license.txt) // //////////////////////////////////////////////////////////////////////////// // wavpack.c // This is the main module for the WavPack command-line compressor. #if defined(WIN32) #include #include #else #include #include #include #include #endif #include #include #include #include #include "wavpack.h" #include "md5.h" #if defined (__GNUC__) && !defined(WIN32) #include #include #include #else #include #endif #ifdef DEBUG_ALLOC #define malloc malloc_db #define realloc realloc_db #define free free_db void *malloc_db (uint32_t size); void *realloc_db (void *ptr, uint32_t size); void free_db (void *ptr); int32_t dump_alloc (void); static char *strdup (const char *s) { char *d = malloc (strlen (s) + 1); return strcpy (d, s); } #endif ///////////////////////////// local variable storage ////////////////////////// static const char *sign_on = "\n" " WAVPACK Hybrid Lossless Audio Compressor %s Version %s %s\n" " Copyright (c) 1998 - 2005 Conifer Software. All Rights Reserved.\n\n"; static const char *usage = #if defined (WIN32) " Usage: WAVPACK [-options] [@]infile[.wav]|- [[@]outfile[.wv]|outpath|-]\n" #else " Usage: WAVPACK [-options] [@]infile[.wav]|- [...] [-o [@]outfile[.wv]|outpath|-]\n" #endif " (default is lossless; infile may contain wildcards: ?,*)\n\n" " Options: -a = Adobe Audition (CoolEdit) mode for 32-bit floats\n" " -bn = enable hybrid compression, n = 2.0 to 23.9 bits/sample, or\n" " n = 24-9600 kbits/second (kbps)\n" " -c = create correction file (.wvc) for hybrid mode (=lossless)\n" " -cc = maximum hybrid compression (hurts lossy quality & decode speed)\n" " -d = delete source file if successful (use with caution!)\n" #if defined (WIN32) " -e = create self-extracting executable (needs wvselfx.exe)\n" #endif " -f = fast mode (fast, but some compromise in compression ratio)\n" " -h = high quality (best compression in all modes, but slower)\n" " -i = ignore length in wav header (no pipe output allowed)\n" " -jn = joint-stereo override (0 = left/right, 1 = mid/side)\n" #if defined (WIN32) " -l = run at low priority (for smoother multitasking)\n" #endif " -m = compute & store MD5 signature of raw audio data\n" " -n = calculate average and peak quantization noise (hybrid only)\n" #if !defined (WIN32) " -o FILENAME | PATH = specify output filename or path\n" #endif " -p = practical float storage (also 32-bit ints, not lossless)\n" " -r = generate new RIFF wav header (removing extra chunk info)\n" " -q = quiet (keep console output to a minimum)\n" " -sn = noise shaping override (hybrid only, n = -1.0 to 1.0, 0 = off)\n" " -t = copy input file's time stamp to output file(s)\n" " -w \"Field=Value\" = write specified metadata to APEv2 tag\n" " -x[n] = extra encode processing (optional n = 1-6 for less/more)\n" " -y = yes to all warnings (use with caution!)\n\n" " Web: Visit www.wavpack.com for latest version and info\n"; // this global is used to indicate the special "debug" mode where extra debug messages // are displayed and all messages are logged to the file \wavpack.log int debug_logging_mode; static int overwrite_all, num_files, file_index; #if defined (WIN32) static char *wvselfx_image; static uint32_t wvselfx_size; #endif /////////////////////////// local function declarations /////////////////////// static FILE *wild_fopen (char *filename, const char *mode); static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config); static int pack_audio (WavpackContext *wpc, FILE *infile); static void display_progress (double file_progress); #define NO_ERROR 0L #define SOFT_ERROR 1 #define HARD_ERROR 2 ////////////////////////////////////////////////////////////////////////////// // The "main" function for the command-line WavPack compressor. // ////////////////////////////////////////////////////////////////////////////// int main (argc, argv) int argc; char **argv; { int delete_source = 0, error_count = 0, tag_next_arg = 0, output_spec = 0; char *outfilename = NULL, *out2filename = NULL; char **matches = NULL; WavpackConfig config; int result, i; #if defined(WIN32) struct _finddata_t _finddata_t; char selfname [MAX_PATH]; if (GetModuleFileName (NULL, selfname, sizeof (selfname)) && filespec_name (selfname) && strupr (filespec_name (selfname)) && strstr (filespec_name (selfname), "DEBUG")) { char **argv_t = argv; int argc_t = argc; debug_logging_mode = TRUE; while (--argc_t) error_line ("arg %d: %s", argc - argc_t, *++argv_t); } strcpy (selfname, *argv); #else if (filespec_name (*argv)) if (strstr (filespec_name (*argv), "ebug") || strstr (filespec_name (*argv), "DEBUG")) { char **argv_t = argv; int argc_t = argc; debug_logging_mode = TRUE; while (--argc_t) error_line ("arg %d: %s", argc - argc_t, *++argv_t); } #endif CLEAR (config); // loop through command-line arguments while (--argc) #if defined (WIN32) if ((**++argv == '-' || **argv == '/') && (*argv)[1]) #else if ((**++argv == '-') && (*argv)[1]) #endif while (*++*argv) switch (**argv) { case 'Y': case 'y': overwrite_all = 1; break; case 'D': case 'd': delete_source = 1; break; case 'C': case 'c': if (config.flags & CONFIG_CREATE_WVC) config.flags |= CONFIG_OPTIMIZE_WVC; else config.flags |= CONFIG_CREATE_WVC; break; case 'X': case 'x': config.xmode = strtol (++*argv, argv, 10); if (config.xmode < 0 || config.xmode > 6) { error_line ("extra mode only goes from 1 to 6!"); ++error_count; } else config.flags |= CONFIG_EXTRA_MODE; --*argv; break; case 'F': case 'f': if (config.flags & CONFIG_FAST_FLAG) config.flags |= CONFIG_VERY_FAST_FLAG; else config.flags |= CONFIG_FAST_FLAG; break; case 'H': case 'h': if (config.flags & CONFIG_HIGH_FLAG) config.flags |= CONFIG_VERY_HIGH_FLAG; else config.flags |= CONFIG_HIGH_FLAG; break; case 'N': case 'n': config.flags |= CONFIG_CALC_NOISE; break; case 'A': case 'a': config.flags |= CONFIG_ADOBE_MODE; break; #if defined (WIN32) case 'E': case 'e': config.flags |= CONFIG_CREATE_EXE; break; case 'L': case 'l': SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS); break; #else case 'O': case 'o': output_spec = 1; break; #endif case 'T': case 't': config.flags |= CONFIG_COPY_TIME; break; case 'P': case 'p': config.flags |= CONFIG_SKIP_WVX; break; case 'Q': case 'q': config.flags |= CONFIG_QUIET_MODE; break; case 'M': case 'm': config.flags |= CONFIG_MD5_CHECKSUM; break; case 'I': case 'i': config.flags |= CONFIG_IGNORE_LENGTH; break; case 'R': case 'r': config.flags |= CONFIG_NEW_RIFF_HEADER; break; case 'K': case 'k': config.block_samples = strtol (++*argv, argv, 10); --*argv; break; case 'B': case 'b': config.flags |= CONFIG_HYBRID_FLAG; config.bitrate = strtod (++*argv, argv); --*argv; if (config.bitrate < 2.0 || config.bitrate > 9600.0) { error_line ("hybrid spec must be 2.0 to 9600!"); ++error_count; } if (config.bitrate >= 24.0) config.flags |= CONFIG_BITRATE_KBPS; break; case 'J': case 'j': switch (strtol (++*argv, argv, 10)) { case 0: config.flags |= CONFIG_JOINT_OVERRIDE; config.flags &= ~CONFIG_JOINT_STEREO; break; case 1: config.flags |= (CONFIG_JOINT_OVERRIDE | CONFIG_JOINT_STEREO); break; default: error_line ("-j0 or -j1 only!"); ++error_count; } --*argv; break; case 'S': case 's': config.shaping_weight = strtod (++*argv, argv); if (!config.shaping_weight) { config.flags |= CONFIG_SHAPE_OVERRIDE; config.flags &= ~CONFIG_HYBRID_SHAPE; } else if (config.shaping_weight >= -1.0 && config.shaping_weight <= 1.0) config.flags |= (CONFIG_HYBRID_SHAPE | CONFIG_SHAPE_OVERRIDE); else { error_line ("-s-1.00 to -s1.00 only!"); ++error_count; } --*argv; break; case 'W': case 'w': tag_next_arg = 1; break; default: error_line ("illegal option: %c !", **argv); ++error_count; } else if (tag_next_arg) { tag_next_arg = 0; config.tag_strings = realloc (config.tag_strings, ++config.num_tag_strings * sizeof (*config.tag_strings)); config.tag_strings [config.num_tag_strings - 1] = *argv; } #if defined (WIN32) else if (!num_files) { matches = realloc (matches, (num_files + 1) * sizeof (*matches)); matches [num_files] = malloc (strlen (*argv) + 10); strcpy (matches [num_files], *argv); if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' && !filespec_ext (matches [num_files])) strcat (matches [num_files], ".wav"); num_files++; } else if (!outfilename) { outfilename = malloc (strlen (*argv) + PATH_MAX); strcpy (outfilename, *argv); } else if (!out2filename) { out2filename = malloc (strlen (*argv) + PATH_MAX); strcpy (out2filename, *argv); } else { error_line ("extra unknown argument: %s !", *argv); ++error_count; } #else else if (output_spec) { outfilename = malloc (strlen (*argv) + PATH_MAX); strcpy (outfilename, *argv); output_spec = 0; } else { matches = realloc (matches, (num_files + 1) * sizeof (*matches)); matches [num_files] = malloc (strlen (*argv) + 10); strcpy (matches [num_files], *argv); if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' && !filespec_ext (matches [num_files])) strcat (matches [num_files], ".wav"); num_files++; } #endif setup_break (); // set up console and detect ^C and ^Break // check for various command-line argument problems if (!(~config.flags & (CONFIG_HIGH_FLAG | CONFIG_FAST_FLAG))) { error_line ("high and fast modes are mutually exclusive!"); ++error_count; } if ((config.flags & CONFIG_IGNORE_LENGTH) && outfilename && *outfilename == '-') { error_line ("can't ignore length in header when using stdout!"); ++error_count; } if (config.flags & CONFIG_HYBRID_FLAG) { if ((config.flags & CONFIG_CREATE_WVC) && outfilename && *outfilename == '-') { error_line ("can't create correction file when using stdout!"); ++error_count; } } else { if (config.flags & (CONFIG_CALC_NOISE | CONFIG_SHAPE_OVERRIDE | CONFIG_CREATE_WVC)) { error_line ("-s, -n and -c options are for hybrid mode (-b) only!"); ++error_count; } } if (!(config.flags & CONFIG_QUIET_MODE) && !error_count) fprintf (stderr, sign_on, VERSION_OS, VERSION_STR, DATE_STR); if (!num_files) { printf ("%s", usage); return 1; } // loop through any tag specification strings and check for file access for (i = 0; i < config.num_tag_strings; ++i) { char *cp = strchr (config.tag_strings [i], '='), *string = NULL; if (cp && cp > config.tag_strings [i]) { int item_len = cp - config.tag_strings [i]; if (cp [1] == '@') { FILE *file = wild_fopen (cp+2, "rb"); if (!file && filespec_name (matches [0]) && *matches [0] != '-') { char *temp = malloc (strlen (matches [0]) + PATH_MAX); strcpy (temp, matches [0]); strcpy (filespec_name (temp), cp+2); file = wild_fopen (temp, "rb"); free (temp); } if (!file && filespec_name (outfilename) && *outfilename != '-') { char *temp = malloc (strlen (outfilename) + PATH_MAX); strcpy (temp, outfilename); strcpy (filespec_name (temp), cp+2); file = wild_fopen (temp, "rb"); free (temp); } if (file) { uint32_t bcount, file_len; file_len = DoGetFileSize (file); if (file_len < 1048576 && (string = malloc (item_len + file_len + 2)) != NULL) { memcpy (string, config.tag_strings [i], item_len + 1); if (!DoReadFile (file, string + item_len + 1, file_len, &bcount) || bcount != file_len) { free (string); string = NULL; } else string [item_len + file_len + 1] = 0; } DoCloseHandle (file); } } else string = config.tag_strings [i]; } if (!string) { error_line ("error in tag spec: %s !", config.tag_strings [i]); ++error_count; } else { config.tag_strings [i] = string; // error_line ("final tag data: %s", string); } } if (error_count) return 1; // If we are trying to create self-extracting .exe files, this is where // we read the wvselfx.exe file into memory in preparation for pre-pending // it to the WavPack files. #if defined (WIN32) if (config.flags & CONFIG_CREATE_EXE) { FILE *wvselfx_file; uint32_t bcount; strcpy (filespec_name (selfname), "wvselfx.exe"); wvselfx_file = fopen (selfname, "rb"); if (!wvselfx_file) { _searchenv ("wvselfx.exe", "PATH", selfname); wvselfx_file = fopen (selfname, "rb"); } if (wvselfx_file) { wvselfx_size = DoGetFileSize (wvselfx_file); if (wvselfx_size && wvselfx_size != 26624 && wvselfx_size < 49152) { wvselfx_image = malloc (wvselfx_size); if (!DoReadFile (wvselfx_file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) { free (wvselfx_image); wvselfx_image = NULL; } } DoCloseHandle (wvselfx_file); } if (!wvselfx_image) { error_line ("wvselfx.exe file is not readable or is outdated!"); free (wvselfx_image); exit (1); } } #endif for (file_index = 0; file_index < num_files; ++file_index) { char *infilename = matches [file_index]; // If the single infile specification begins with a '@', then it // actually points to a file that contains the names of the files // to be converted. This was included for use by Wim Speekenbrink's // frontends, but could be used for other purposes. if (*infilename == '@') { FILE *list = fopen (infilename+1, "rt"); int di, c; for (di = file_index; di < num_files - 1; di++) matches [di] = matches [di + 1]; file_index--; num_files--; if (list == NULL) { error_line ("file %s not found!", infilename+1); free (infilename); return 1; } while ((c = getc (list)) != EOF) { while (c == '\n') c = getc (list); if (c != EOF) { char *fname = malloc (PATH_MAX); int ci = 0; do fname [ci++] = c; while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX); fname [ci++] = '\0'; fname = realloc (fname, ci); matches = realloc (matches, ++num_files * sizeof (*matches)); for (di = num_files - 1; di > file_index + 1; di--) matches [di] = matches [di - 1]; matches [++file_index] = fname; } } fclose (list); free (infilename); } #if defined (WIN32) else if (filespec_wild (infilename)) { FILE *list = fopen (infilename+1, "rt"); int di; for (di = file_index; di < num_files - 1; di++) matches [di] = matches [di + 1]; file_index--; num_files--; if ((i = _findfirst (infilename, &_finddata_t)) != -1L) { do { if (!(_finddata_t.attrib & _A_SUBDIR)) { matches = realloc (matches, ++num_files * sizeof (*matches)); for (di = num_files - 1; di > file_index + 1; di--) matches [di] = matches [di - 1]; matches [++file_index] = malloc (strlen (infilename) + strlen (_finddata_t.name) + 10); strcpy (matches [file_index], infilename); *filespec_name (matches [file_index]) = '\0'; strcat (matches [file_index], _finddata_t.name); } } while (_findnext (i, &_finddata_t) == 0); _findclose (i); } free (infilename); } #endif } // If the outfile specification begins with a '@', then it actually points // to a file that contains the output specification. This was included for // use by Wim Speekenbrink's frontends because certain filenames could not // be passed on the command-line, but could be used for other purposes. if (outfilename && outfilename [0] == '@') { FILE *list = fopen (outfilename+1, "rt"); int c; if (list == NULL) { error_line ("file %s not found!", outfilename+1); free(outfilename); return 1; } while ((c = getc (list)) == '\n'); if (c != EOF) { int ci = 0; do outfilename [ci++] = c; while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX); outfilename [ci] = '\0'; } else { error_line ("output spec file is empty!"); free(outfilename); fclose (list); return 1; } fclose (list); } if (out2filename && (num_files > 1 || !(config.flags & CONFIG_CREATE_WVC))) { error_line ("extra unknown argument: %s !", out2filename); return 1; } // if we found any files to process, this is where we start if (num_files) { char outpath, addext; if (outfilename && *outfilename != '-') { outpath = (filespec_path (outfilename) != NULL); if (num_files > 1 && !outpath) { error_line ("%s is not a valid output path", outfilename); free(outfilename); return 1; } } else outpath = 0; addext = !outfilename || outpath || !filespec_ext (outfilename); // loop through and process files in list for (file_index = 0; file_index < num_files; ++file_index) { if (check_break ()) break; // generate output filename if (outpath) { strcat (outfilename, filespec_name (matches [file_index])); if (filespec_ext (outfilename)) *filespec_ext (outfilename) = '\0'; } else if (!outfilename) { outfilename = malloc (strlen (matches [file_index]) + 10); strcpy (outfilename, matches [file_index]); if (filespec_ext (outfilename)) *filespec_ext (outfilename) = '\0'; } if (addext && *outfilename != '-') strcat (outfilename, (config.flags & CONFIG_CREATE_EXE) ? ".exe" : ".wv"); // if "correction" file is desired, generate name for that if (config.flags & CONFIG_CREATE_WVC) { if (!out2filename) { out2filename = malloc (strlen (outfilename) + 10); strcpy (out2filename, outfilename); } else { char *temp = malloc (strlen (outfilename) + PATH_MAX); strcpy (temp, outfilename); strcpy (filespec_name (temp), filespec_name (out2filename)); strcpy (out2filename, temp); free (temp); } if (filespec_ext (out2filename)) *filespec_ext (out2filename) = '\0'; strcat (out2filename, ".wvc"); } else out2filename = NULL; if (num_files > 1) fprintf (stderr, "\n%s:\n", matches [file_index]); result = pack_file (matches [file_index], outfilename, out2filename, &config); if (result != NO_ERROR) ++error_count; if (result == HARD_ERROR) break; // delete source file if that option is enabled if (result == NO_ERROR && delete_source) error_line ("%s source file %s", DoDeleteFile (matches [file_index]) ? "deleted" : "can't delete", matches [file_index]); // clean up in preparation for potentially another file if (outpath) *filespec_name (outfilename) = '\0'; else if (*outfilename != '-') { free (outfilename); outfilename = NULL; } if (out2filename) { free (out2filename); out2filename = NULL; } free (matches [file_index]); } if (num_files > 1) { if (error_count) fprintf (stderr, "\n **** warning: errors occurred in %d of %d files! ****\n", error_count, num_files); else if (!(config.flags & CONFIG_QUIET_MODE)) fprintf (stderr, "\n **** %d files successfully processed ****\n", num_files); } free (matches); } else { error_line ("nothing to do!"); ++error_count; } if (outfilename) free (outfilename); #ifdef DEBUG_ALLOC error_line ("malloc_count = %d", dump_alloc ()); #endif return error_count ? 1 : 0; } // This structure and function are used to write completed WavPack blocks in // a device independent way. typedef struct { uint32_t bytes_written, first_block_size; FILE *file; int error; } write_id; static int write_block (void *id, void *data, int32_t length) { write_id *wid = (write_id *) id; uint32_t bcount; if (wid->error) return FALSE; if (wid && wid->file && data && length) { if (!DoWriteFile (wid->file, data, length, &bcount) || bcount != length) { DoTruncateFile (wid->file); DoCloseHandle (wid->file); wid->file = NULL; wid->error = 1; return FALSE; } else { wid->bytes_written += length; if (!wid->first_block_size) wid->first_block_size = bcount; } } return TRUE; } // Special version of fopen() that allows a wildcard specification for the // filename. If a wildcard is specified, then it must match 1 and only 1 // file to be acceptable (i.e. it won't match just the "first" file). #if defined (WIN32) static FILE *wild_fopen (char *filename, const char *mode) { struct _finddata_t _finddata_t; char *matchname = NULL; FILE *res = NULL; int i; if (!filespec_wild (filename) || !filespec_name (filename)) return fopen (filename, mode); if ((i = _findfirst (filename, &_finddata_t)) != -1L) { do { if (!(_finddata_t.attrib & _A_SUBDIR)) { if (matchname) { free (matchname); matchname = NULL; break; } else { matchname = malloc (strlen (filename) + strlen (_finddata_t.name)); strcpy (matchname, filename); strcpy (filespec_name (matchname), _finddata_t.name); } } } while (_findnext (i, &_finddata_t) == 0); _findclose (i); } if (matchname) { res = fopen (matchname, mode); free (matchname); } return res; } #else static FILE *wild_fopen (char *filename, const char *mode) { return fopen (filename, mode); } #endif // This function packs a single file "infilename" and stores the result at // "outfilename". If "out2filename" is specified, then the "correction" // file would go there. The files are opened and closed in this function // and the "config" structure specifies the mode of compression. static void AnsiToUTF8 (char *string, int len); static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config) { uint32_t total_samples = 0, bcount; WavpackConfig loc_config = *config; RiffChunkHeader riff_chunk_header; write_id wv_file, wvc_file; ChunkHeader chunk_header; WaveHeader WaveHeader; WavpackContext *wpc; double dtime; FILE *infile; int result; #if defined(WIN32) struct _timeb time1, time2; #else struct timeval time1, time2; struct timezone timez; #endif CLEAR (wv_file); CLEAR (wvc_file); wpc = WavpackOpenFileOutput (write_block, &wv_file, out2filename ? &wvc_file : NULL); // open the source file for reading if (*infilename == '-') { infile = stdin; #if defined(WIN32) setmode (fileno (stdin), O_BINARY); #endif } else if ((infile = fopen (infilename, "rb")) == NULL) { error_line ("can't open file %s!", infilename); WavpackCloseFile (wpc); return SOFT_ERROR; } // check both output files for overwrite warning required if (*outfilename != '-' && !overwrite_all && (wv_file.file = fopen (outfilename, "rb")) != NULL) { DoCloseHandle (wv_file.file); fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (outfilename)); SetConsoleTitle ("overwrite?"); switch (yna ()) { case 'n': DoCloseHandle (infile); WavpackCloseFile (wpc); return SOFT_ERROR; case 'a': overwrite_all = 1; } } if (out2filename && !overwrite_all && (wvc_file.file = fopen (out2filename, "rb")) != NULL) { DoCloseHandle (wvc_file.file); fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (out2filename)); SetConsoleTitle ("overwrite?"); switch (yna ()) { case 'n': DoCloseHandle (infile); WavpackCloseFile (wpc); return SOFT_ERROR; case 'a': overwrite_all = 1; } } #if defined(WIN32) _ftime (&time1); #else gettimeofday(&time1,&timez); #endif // open output file for writing if (*outfilename == '-') { wv_file.file = stdout; #if defined(WIN32) setmode (fileno (stdout), O_BINARY); #endif } else if ((wv_file.file = fopen (outfilename, "w+b")) == NULL) { error_line ("can't create file %s!", outfilename); DoCloseHandle (infile); WavpackCloseFile (wpc); return SOFT_ERROR; } if (!(loc_config.flags & CONFIG_QUIET_MODE)) { if (*outfilename == '-') fprintf (stderr, "packing %s to stdout,", *infilename == '-' ? "stdin" : FN_FIT (infilename)); else if (out2filename) fprintf (stderr, "creating %s (+%s),", FN_FIT (outfilename), filespec_ext (out2filename)); else fprintf (stderr, "creating %s,", FN_FIT (outfilename)); } #if defined (WIN32) if (loc_config.flags & CONFIG_CREATE_EXE) if (!DoWriteFile (wv_file.file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) { error_line ("can't write WavPack data, disk probably full!"); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } #endif // if not in "raw" mode, read (and copy to output) initial RIFF form header if (!(loc_config.flags & CONFIG_RAW_FLAG)) { if ((!DoReadFile (infile, &riff_chunk_header, sizeof (RiffChunkHeader), &bcount) || bcount != sizeof (RiffChunkHeader) || strncmp (riff_chunk_header.ckID, "RIFF", 4) || strncmp (riff_chunk_header.formType, "WAVE", 4))) { error_line ("%s is not a valid .WAV file!", infilename); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) && !WavpackAddWrapper (wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) { error_line ("%s", wpc->error_message); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } } // if not in "raw" mode, loop through all elements of the RIFF wav header // (until the data chuck) and copy them to the output file while (!(loc_config.flags & CONFIG_RAW_FLAG)) { if (!DoReadFile (infile, &chunk_header, sizeof (ChunkHeader), &bcount) || bcount != sizeof (ChunkHeader)) { error_line ("%s is not a valid .WAV file!", infilename); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) && !WavpackAddWrapper (wpc, &chunk_header, sizeof (ChunkHeader))) { error_line ("%s", wpc->error_message); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } little_endian_to_native (&chunk_header, ChunkHeaderFormat); // if it's the format chunk, we want to get some info out of there and // make sure it's a .wav file we can handle if (!strncmp (chunk_header.ckID, "fmt ", 4)) { int supported = TRUE, format; if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) || !DoReadFile (infile, &WaveHeader, chunk_header.ckSize, &bcount) || bcount != chunk_header.ckSize) { error_line ("%s is not a valid .WAV file!", infilename); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) && !WavpackAddWrapper (wpc, &WaveHeader, chunk_header.ckSize)) { error_line ("%s", wpc->error_message); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } little_endian_to_native (&WaveHeader, WaveHeaderFormat); if (debug_logging_mode) { error_line ("format tag size = %d", chunk_header.ckSize); error_line ("FormatTag = %x, NumChannels = %d, BitsPerSample = %d", WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample); error_line ("BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d", WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond); if (chunk_header.ckSize > 16) error_line ("cbSize = %d, ValidBitsPerSample = %d", WaveHeader.cbSize, WaveHeader.ValidBitsPerSample); if (chunk_header.ckSize > 20) error_line ("ChannelMask = %x, SubFormat = %d", WaveHeader.ChannelMask, WaveHeader.SubFormat); } if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2) loc_config.flags |= CONFIG_ADOBE_MODE; format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ? WaveHeader.SubFormat : WaveHeader.FormatTag; loc_config.bits_per_sample = chunk_header.ckSize == 40 ? WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample; if (format != 1 && format != 3) supported = FALSE; if (!WaveHeader.NumChannels || WaveHeader.BlockAlign / WaveHeader.NumChannels > 4) supported = FALSE; if (loc_config.bits_per_sample < 1 || loc_config.bits_per_sample > 32) supported = FALSE; if (!supported) { error_line ("%s is an unsupported .WAV format!", infilename); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } if (chunk_header.ckSize < 40) { if (WaveHeader.NumChannels <= 2) loc_config.channel_mask = 0x5 - WaveHeader.NumChannels; else loc_config.channel_mask = (1 << WaveHeader.NumChannels) - 1; } else loc_config.channel_mask = WaveHeader.ChannelMask; if (format == 3) loc_config.float_norm_exp = 127; else if ((loc_config.flags & CONFIG_ADOBE_MODE) && WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) { if (WaveHeader.BitsPerSample == 24) loc_config.float_norm_exp = 127 + 23; else if (WaveHeader.BitsPerSample == 32) loc_config.float_norm_exp = 127 + 15; } if (debug_logging_mode) { if (loc_config.float_norm_exp == 127) error_line ("data format: normalized 32-bit floating point"); else if (loc_config.float_norm_exp) error_line ("data format: 32-bit floating point (Audition %d:%d float type 1)", loc_config.float_norm_exp - 126, 150 - loc_config.float_norm_exp); else error_line ("data format: %d-bit integers stored in %d byte(s)", loc_config.bits_per_sample, WaveHeader.BlockAlign / WaveHeader.NumChannels); } } else if (!strncmp (chunk_header.ckID, "data", 4)) { // on the data chunk, get size and exit loop total_samples = chunk_header.ckSize / WaveHeader.BlockAlign; break; } else { // just copy unknown chunks to output file int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L; char *buff = malloc (bytes_to_copy); if (debug_logging_mode) error_line ("extra unknown chunk \"%c%c%c%c\" of %d bytes", chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2], chunk_header.ckID [3], chunk_header.ckSize); if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) || bcount != bytes_to_copy || (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) && !WavpackAddWrapper (wpc, buff, bytes_to_copy))) { error_line ("%s", wpc->error_message); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); free (buff); WavpackCloseFile (wpc); return SOFT_ERROR; } free (buff); } } loc_config.bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels; loc_config.num_channels = WaveHeader.NumChannels; loc_config.sample_rate = WaveHeader.SampleRate; WavpackSetConfiguration (wpc, &loc_config, total_samples); // if we are creating a "correction" file, open it now for writing if (out2filename) { if ((wvc_file.file = fopen (out2filename, "w+b")) == NULL) { error_line ("can't create correction file!"); DoCloseHandle (infile); DoCloseHandle (wv_file.file); DoDeleteFile (outfilename); WavpackCloseFile (wpc); return SOFT_ERROR; } } // pack the audio portion of the file now result = pack_audio (wpc, infile); // if everything went well (and we're not ignoring length) try to read // anything else that might be appended to the audio data and write that // to the WavPack metadata as "wrapper" if (result == NO_ERROR && !(loc_config.flags & CONFIG_IGNORE_LENGTH)) { uchar buff [16]; while (DoReadFile (infile, buff, sizeof (buff), &bcount) && bcount) if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) && !WavpackAddWrapper (wpc, buff, bcount)) { error_line ("%s", wpc->error_message); result = HARD_ERROR; break; } } DoCloseHandle (infile); // we're now done with input file, so close // we're now done with any WavPack blocks, so flush any remaining data if (result == NO_ERROR && !WavpackFlushSamples (wpc)) { error_line ("%s", wpc->error_message); result = HARD_ERROR; } // if still no errors, check to see if we need to create & write a tag // (which is NOT stored in regular WavPack blocks) if (result == NO_ERROR && config->num_tag_strings) { int i; for (i = 0; i < config->num_tag_strings; ++i) { int item_len = strchr (config->tag_strings [i], '=') - config->tag_strings [i]; int value_len = strlen (config->tag_strings [i]) - item_len - 1; if (value_len) { char *item = malloc (item_len + 1); char *value = malloc (value_len * 2 + 1); strncpy (item, config->tag_strings [i], item_len); item [item_len] = 0; strcpy (value, config->tag_strings [i] + item_len + 1); AnsiToUTF8 (value, value_len * 2 + 1); WavpackAppendTagItem (wpc, item, value, strlen (value)); free (value); free (item); } } if (!WavpackWriteTag (wpc)) { error_line ("%s", wpc->error_message); result = HARD_ERROR; } } // At this point we're done writing to the output files. However, in some // situations we might have to back up and re-write the initial blocks. // Currently the only case is if we're ignoring length. if (result == NO_ERROR && WavpackGetNumSamples (wpc) != WavpackGetSampleIndex (wpc)) { if (loc_config.flags & CONFIG_IGNORE_LENGTH) { char *block_buff = malloc (wv_file.first_block_size); uint32_t wrapper_size; if (block_buff && !DoSetFilePositionAbsolute (wv_file.file, 0) && DoReadFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) && bcount == wv_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) { WavpackUpdateNumSamples (wpc, block_buff); if (WavpackGetWrapperLocation (block_buff, &wrapper_size)) { RiffChunkHeader *riffhdr = WavpackGetWrapperLocation (block_buff, NULL); ChunkHeader *datahdr = (ChunkHeader *)((char *) riffhdr + wrapper_size - sizeof (ChunkHeader)); uint32_t data_size = WavpackGetSampleIndex (wpc) * WavpackGetNumChannels (wpc) * WavpackGetBytesPerSample (wpc); if (!strncmp (riffhdr->ckID, "RIFF", 4)) { little_endian_to_native (riffhdr, ChunkHeaderFormat); riffhdr->ckSize = wrapper_size + data_size - 8; native_to_little_endian (riffhdr, ChunkHeaderFormat); } if (!strncmp (datahdr->ckID, "data", 4)) { little_endian_to_native (datahdr, ChunkHeaderFormat); datahdr->ckSize = data_size; native_to_little_endian (datahdr, ChunkHeaderFormat); } } if (DoSetFilePositionAbsolute (wv_file.file, 0) || !DoWriteFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) || bcount != wv_file.first_block_size) { error_line ("couldn't update WavPack header with actual length!!"); result = SOFT_ERROR; } free (block_buff); } else { error_line ("couldn't update WavPack header with actual length!!"); result = SOFT_ERROR; } if (result == NO_ERROR && wvc_file.file) { block_buff = malloc (wvc_file.first_block_size); if (block_buff && !DoSetFilePositionAbsolute (wvc_file.file, 0) && DoReadFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) && bcount == wvc_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) { WavpackUpdateNumSamples (wpc, block_buff); if (DoSetFilePositionAbsolute (wvc_file.file, 0) || !DoWriteFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) || bcount != wvc_file.first_block_size) { error_line ("couldn't update WavPack header with actual length!!"); result = SOFT_ERROR; } } else { error_line ("couldn't update WavPack header with actual length!!"); result = SOFT_ERROR; } free (block_buff); } if (result == NO_ERROR) error_line ("warning: length was %s by %d samples, corrected", WavpackGetSampleIndex (wpc) < total_samples ? "short" : "long", abs (total_samples - WavpackGetSampleIndex (wpc))); } else { error_line ("couldn't read all samples, file may be corrupt!!"); result = SOFT_ERROR; } } // at this point we're done with the files, so close 'em whether there // were any other errors or not if (!DoCloseHandle (wv_file.file)) { error_line ("can't close WavPack file!"); if (result == NO_ERROR) result = SOFT_ERROR; } if (out2filename && !DoCloseHandle (wvc_file.file)) { error_line ("can't close correction file!"); if (result == NO_ERROR) result = SOFT_ERROR; } // if there were any errors, delete the output files, close the context, // and return the error if (result != NO_ERROR) { DoDeleteFile (outfilename); if (out2filename) DoDeleteFile (out2filename); WavpackCloseFile (wpc); return result; } if (result == NO_ERROR && (loc_config.flags & CONFIG_COPY_TIME)) if (!copy_timestamp (infilename, outfilename) || (out2filename && !copy_timestamp (infilename, out2filename))) error_line ("failure copying time stamp!"); // compute and display the time consumed along with some other details of // the packing operation, and then return NO_ERROR #if defined(WIN32) _ftime (&time2); dtime = time2.time + time2.millitm / 1000.0; dtime -= time1.time + time1.millitm / 1000.0; #else gettimeofday(&time2,&timez); dtime = time2.tv_sec + time2.tv_usec / 1000000.0; dtime -= time1.tv_sec + time1.tv_usec / 1000000.0; #endif if ((loc_config.flags & CONFIG_CALC_NOISE) && pack_noise (wpc, NULL) > 0.0) { int full_scale_bits = WavpackGetBitsPerSample (wpc); double full_scale_rms = 0.5, sum, peak; while (full_scale_bits--) full_scale_rms *= 2.0; full_scale_rms = full_scale_rms * (full_scale_rms - 1.0) * 0.5; sum = pack_noise (wpc, &peak); error_line ("ave noise = %.2f dB, peak noise = %.2f dB", log10 (sum / WavpackGetNumSamples (wpc) / full_scale_rms) * 10, log10 (peak / full_scale_rms) * 10); } if (!(loc_config.flags & CONFIG_QUIET_MODE)) { char *file, *fext, *oper, *cmode, cratio [16] = ""; if (outfilename && *outfilename != '-') { file = FN_FIT (outfilename); fext = wvc_file.bytes_written ? " (+.wvc)" : ""; oper = "created"; } else { file = (*infilename == '-') ? "stdin" : FN_FIT (infilename); fext = ""; oper = "packed"; } if (WavpackLossyBlocks (wpc)) { cmode = "lossy"; if (WavpackGetAverageBitrate (wpc, TRUE) != 0.0) sprintf (cratio, ", %d kbps", (int) (WavpackGetAverageBitrate (wpc, TRUE) / 1000.0)); } else { cmode = "lossless"; if (WavpackGetRatio (wpc) != 0.0) sprintf (cratio, ", %.2f%%", 100.0 - WavpackGetRatio (wpc) * 100.0); } error_line ("%s %s%s in %.2f secs (%s%s)", oper, file, fext, dtime, cmode, cratio); } WavpackCloseFile (wpc); return NO_ERROR; } // This function handles the actual audio data compression. It assumes that the // input file is positioned at the beginning of the audio data and that the // WavPack configuration has been set. This is where the conversion from RIFF // little-endian standard the executing processor's format is done and where // (if selected) the MD5 sum is calculated and displayed. #define INPUT_SAMPLES 65536 static int pack_audio (WavpackContext *wpc, FILE *infile) { uint32_t samples_remaining, samples_read = 0; double progress = -1.0; int bytes_per_sample; int32_t *sample_buffer; uchar *input_buffer; MD5_CTX md5_context; if (wpc->config.flags & CONFIG_MD5_CHECKSUM) MD5Init (&md5_context); WavpackPackInit (wpc); bytes_per_sample = WavpackGetBytesPerSample (wpc) * WavpackGetNumChannels (wpc); input_buffer = malloc (INPUT_SAMPLES * bytes_per_sample); sample_buffer = malloc (INPUT_SAMPLES * sizeof (int32_t) * WavpackGetNumChannels (wpc)); samples_remaining = WavpackGetNumSamples (wpc); while (1) { uint32_t bytes_to_read, bytes_read = 0; uint sample_count; if ((wpc->config.flags & CONFIG_IGNORE_LENGTH) || samples_remaining > INPUT_SAMPLES) bytes_to_read = INPUT_SAMPLES * bytes_per_sample; else bytes_to_read = samples_remaining * bytes_per_sample; samples_remaining -= bytes_to_read / bytes_per_sample; DoReadFile (infile, input_buffer, bytes_to_read, &bytes_read); samples_read += sample_count = bytes_read / bytes_per_sample; if (wpc->config.flags & CONFIG_MD5_CHECKSUM) MD5Update (&md5_context, input_buffer, bytes_read); if (!sample_count) break; if (sample_count) { uint cnt = sample_count * WavpackGetNumChannels (wpc); uchar *sptr = input_buffer; int32_t *dptr = sample_buffer; switch (WavpackGetBytesPerSample (wpc)) { case 1: while (cnt--) *dptr++ = *sptr++ - 128; break; case 2: while (cnt--) { *dptr++ = sptr [0] | ((int32_t)(signed char) sptr [1] << 8); sptr += 2; } break; case 3: while (cnt--) { *dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t)(signed char) sptr [2] << 16); sptr += 3; } break; case 4: while (cnt--) { *dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t) sptr [2] << 16) | ((int32_t)(signed char) sptr [3] << 24); sptr += 4; } break; } } if (!WavpackPackSamples (wpc, sample_buffer, sample_count)) { error_line ("%s", wpc->error_message); free (sample_buffer); free (input_buffer); return HARD_ERROR; } if (check_break ()) { fprintf (stderr, "^C\n"); free (sample_buffer); free (input_buffer); return SOFT_ERROR; } if (WavpackGetProgress (wpc) != -1.0 && progress != floor (WavpackGetProgress (wpc) * 100.0 + 0.5)) { int nobs = progress == -1.0; progress = WavpackGetProgress (wpc); display_progress (progress); progress = floor (progress * 100.0 + 0.5); if (!(wpc->config.flags & CONFIG_QUIET_MODE)) fprintf (stderr, "%s%3d%% done...", nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress); } } free (sample_buffer); free (input_buffer); if (!WavpackFlushSamples (wpc)) { error_line ("%s", wpc->error_message); return HARD_ERROR; } if (wpc->config.flags & CONFIG_MD5_CHECKSUM) { char md5_string [] = "original md5 signature: 00000000000000000000000000000000"; uchar md5_digest [16]; int i; MD5Final (md5_digest, &md5_context); for (i = 0; i < 16; ++i) sprintf (md5_string + 24 + (i * 2), "%02x", md5_digest [i]); if (!(wpc->config.flags & CONFIG_QUIET_MODE)) error_line (md5_string); WavpackStoreMD5Sum (wpc, md5_digest); } return NO_ERROR; } // Convert the Unicode wide-format string into a UTF-8 string using no more // than the specified buffer length. The wide-format string must be NULL // terminated and the resulting string will be NULL terminated. The actual // number of characters converted (not counting terminator) is returned, which // may be less than the number of characters in the wide string if the buffer // length is exceeded. static int WideCharToUTF8 (const ushort *Wide, uchar *pUTF8, int len) { const ushort *pWide = Wide; int outndx = 0; while (*pWide) { if (*pWide < 0x80 && outndx + 1 < len) pUTF8 [outndx++] = (uchar) *pWide++; else if (*pWide < 0x800 && outndx + 2 < len) { pUTF8 [outndx++] = (uchar) (0xc0 | ((*pWide >> 6) & 0x1f)); pUTF8 [outndx++] = (uchar) (0x80 | (*pWide++ & 0x3f)); } else if (outndx + 3 < len) { pUTF8 [outndx++] = (uchar) (0xe0 | ((*pWide >> 12) & 0xf)); pUTF8 [outndx++] = (uchar) (0x80 | ((*pWide >> 6) & 0x3f)); pUTF8 [outndx++] = (uchar) (0x80 | (*pWide++ & 0x3f)); } else break; } pUTF8 [outndx] = 0; return pWide - Wide; } // Convert a Ansi string into its Unicode UTF-8 format equivalent. The // conversion is done in-place so the maximum length of the string buffer must // be specified because the string may become longer or shorter. If the // resulting string will not fit in the specified buffer size then it is // truncated. static void AnsiToUTF8 (char *string, int len) { int max_chars = strlen (string); #if defined(WIN32) ushort *temp = (ushort *) malloc ((max_chars + 1) * 2); MultiByteToWideChar (CP_ACP, 0, string, -1, temp, max_chars + 1); WideCharToUTF8 (temp, (uchar *) string, len); #else char *temp = malloc (len); char *outp = temp; char *inp = string; size_t insize = max_chars; size_t outsize = len - 1; int err = 0; char *old_locale; memset(temp, 0, len); old_locale = setlocale (LC_CTYPE, ""); iconv_t converter = iconv_open ("UTF-8", ""); err = iconv (converter, &inp, &insize, &outp, &outsize); iconv_close (converter); setlocale (LC_CTYPE, old_locale); if (err == -1) { free(temp); return; } memmove (string, temp, len); #endif free (temp); } ////////////////////////////////////////////////////////////////////////////// // This function displays the progress status on the title bar of the DOS // // window that WavPack is running in. The "file_progress" argument is for // // the current file only and ranges from 0 - 1; this function takes into // // account the total number of files to generate a batch progress number. // ////////////////////////////////////////////////////////////////////////////// static void display_progress (double file_progress) { char title [40]; file_progress = (file_index + file_progress) / num_files; sprintf (title, "%d%% (WavPack)", (int) ((file_progress * 100.0) + 0.5)); SetConsoleTitle (title); }