//////////////////////////////////////////////////////////////////////////// // **** WAVPACK **** // // Hybrid Lossless Wavefile Compressor // // Copyright (c) 1998 - 2019 David Bryant. // // All Rights Reserved. // // Distributed under the BSD Software License (see license.txt) // //////////////////////////////////////////////////////////////////////////// // wvparser.c // This is a completely self-contained utility to parse complete Wavpack files // into their component blocks and display some informative details about them // (including all the "metadata" sub-blocks contained in each block). This can // be very handy for visualizing how WavPack files are structured or debugging. // Some sanity checking is performed, but no thorough verification is done // (and, of course, no decoding takes place). APEv2 tags are ignored as is any // other garbage between blocks. // Acts as a filter: WavPack file to stdin, output to stdout. #include #include #include #include #ifdef _WIN32 #include #endif #if defined(_MSC_VER) && _MSC_VER < 1600 typedef unsigned __int64 uint64_t; typedef unsigned __int32 uint32_t; typedef unsigned __int16 uint16_t; typedef unsigned __int8 uint8_t; typedef __int64 int64_t; typedef __int32 int32_t; typedef __int16 int16_t; typedef __int8 int8_t; #else #include #endif typedef int32_t (*read_stream)(void *, int32_t); ////////////////////////////// WavPack Header ///////////////////////////////// // Note that this is the ONLY structure that is written to (or read from) // WavPack 4.0 files, and is the preamble to every block in both the .wv // and .wvc files. typedef struct { char ckID [4]; uint32_t ckSize; int16_t version; unsigned char track_no, index_no; uint32_t total_samples, block_index, block_samples, flags, crc; } WavpackHeader; #define WavpackHeaderFormat "4LS2LLLLL" // or-values for "flags" #define BYTES_STORED 3 // 1-4 bytes/sample #define MONO_FLAG 4 // not stereo #define HYBRID_FLAG 8 // hybrid mode #define JOINT_STEREO 0x10 // joint stereo #define CROSS_DECORR 0x20 // no-delay cross decorrelation #define HYBRID_SHAPE 0x40 // noise shape (hybrid mode only) #define FLOAT_DATA 0x80 // ieee 32-bit floating point data #define INT32_DATA 0x100 // special extended int handling #define HYBRID_BITRATE 0x200 // bitrate noise (hybrid mode only) #define HYBRID_BALANCE 0x400 // balance noise (hybrid stereo mode only) #define INITIAL_BLOCK 0x800 // initial block of multichannel segment #define FINAL_BLOCK 0x1000 // final block of multichannel segment #define SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) #define MAG_LSB 18 #define MAG_MASK (0x1fL << MAG_LSB) #define SRATE_LSB 23 #define SRATE_MASK (0xfL << SRATE_LSB) #define FALSE_STEREO 0x40000000 // block is stereo, but data is mono #define NEW_SHAPING 0x20000000 // use IIR filter for negative shaping #define MONO_DATA (MONO_FLAG | FALSE_STEREO) // Introduced in WavPack 5.0: #define HAS_CHECKSUM 0x10000000 // block contains a trailing checksum #define DSD_FLAG 0x80000000 // block is encoded DSD (1-bit PCM) #define IGNORED_FLAGS 0x08000000 // reserved, but ignore if encountered #define UNKNOWN_FLAGS 0x00000000 // we no longer have any of these spares #define MIN_STREAM_VERS 0x402 // lowest stream version we'll decode #define MAX_STREAM_VERS 0x410 // highest stream version we'll decode or encode static const uint32_t sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; //////////////////////////// WavPack Metadata ///////////////////////////////// // This is an internal representation of metadata. typedef struct { int32_t byte_length; void *data; unsigned char id; } WavpackMetadata; #define ID_UNIQUE 0x3f #define ID_OPTIONAL_DATA 0x20 #define ID_ODD_SIZE 0x40 #define ID_LARGE 0x80 static const char *metadata_names [] = { "DUMMY", "ENCODER_INFO", "DECORR_TERMS", "DECORR_WEIGHTS", "DECORR_SAMPLES", "ENTROPY_VARS", "HYBRID_PROFILE", "SHAPING_WEIGHTS", "FLOAT_INFO", "INT32_INFO", "WV_BITSTREAM", "WVC_BITSTREAM", "WVX_BITSTREAM", "CHANNEL_INFO", "DSD_BLOCK", "UNASSIGNED", "UNASSIGNED", "RIFF_HEADER", "RIFF_TRAILER", "ALT_HEADER", "ALT_TRAILER", "CONFIG_BLOCK", "MD5_CHECKSUM", "SAMPLE_RATE", "ALT_EXTENSION", "ALT_MD5_CHECKSUM", "NEW_CONFIG", "CHANNEL_IDENTITIES", "UNASSIGNED", "UNASSIGNED", "UNASSIGNED", "BLOCK_CHECKSUM" }; static int32_t read_bytes (void *buff, int32_t bcount); static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr); static void little_endian_to_native (void *data, char *format); static void native_to_little_endian (void *data, char *format); static void parse_wavpack_block (unsigned char *block_data); static int verify_wavpack_block (unsigned char *buffer); static const char *sign_on = "\n" " WVPARSER WavPack Audio File Parser Test Filter Version 1.10\n" " Copyright (c) 1998 - 2020 David Bryant. All Rights Reserved.\n\n"; int main (int argc, char **argv) { uint32_t bcount, total_bytes, sample_rate, first_sample, last_sample = -1L; int channel_count, block_count; char flags_list [256]; FILE *outfile = NULL; WavpackHeader wphdr; if (argc > 1) { outfile = fopen (argv [1], "wb"); if (!outfile) { fprintf (stderr, "can't open file '%s'!\n", argv [1]); return 1; } } #ifdef _WIN32 setmode (_fileno (stdin), O_BINARY); #endif fprintf (stderr, "%s", sign_on); while (1) { // read next WavPack header bcount = read_next_header (read_bytes, &wphdr); if (bcount == (uint32_t) -1) { printf ("\nend of file\n\n"); break; } if (bcount) printf ("\nunknown data skipped, %u bytes\n", bcount); if (((wphdr.flags & SRATE_MASK) >> SRATE_LSB) == 15) { if (sample_rate != 44100) printf ("\nwarning: unknown sample rate...using 44100 default\n"); sample_rate = 44100; } else { sample_rate = sample_rates [(wphdr.flags & SRATE_MASK) >> SRATE_LSB]; if (wphdr.flags & DSD_FLAG) sample_rate *= 4; // most common multiplier (DSD64), but could be wrong } // basic summary of the block if ((wphdr.flags & INITIAL_BLOCK) || !wphdr.block_samples) printf ("\n"); if (wphdr.block_samples) { printf ("%s audio block, version 0x%03x, %u samples in %u bytes, time = %.2f-%.2f\n", (wphdr.flags & MONO_FLAG) ? "mono" : "stereo", wphdr.version, wphdr.block_samples, wphdr.ckSize + 8, (double) wphdr.block_index / sample_rate, (double) (wphdr.block_index + wphdr.block_samples - 1) / sample_rate); // now show information from the "flags" field of the header printf ("samples are %d bits in %d bytes, shifted %d bits, sample rate = %u Hz\n", (int)((wphdr.flags & MAG_MASK) >> MAG_LSB) + 1, (wphdr.flags & BYTES_STORED) + 1, (int)(wphdr.flags & SHIFT_MASK) >> SHIFT_LSB, sample_rate); flags_list [0] = 0; if (wphdr.flags) { if (wphdr.flags & INITIAL_BLOCK) strcat (flags_list, "INITIAL "); if (wphdr.flags & MONO_FLAG) strcat (flags_list, "MONO "); if (wphdr.flags & DSD_FLAG) strcat (flags_list, "DSD "); if (wphdr.flags & HYBRID_FLAG) strcat (flags_list, "HYBRID "); if (wphdr.flags & JOINT_STEREO) strcat (flags_list, "JOINT-STEREO "); if (wphdr.flags & CROSS_DECORR) strcat (flags_list, "CROSS-DECORR "); if (wphdr.flags & HYBRID_SHAPE) strcat (flags_list, "NOISE-SHAPING "); if (wphdr.flags & FLOAT_DATA) strcat (flags_list, "FLOAT "); if (wphdr.flags & INT32_DATA) strcat (flags_list, "INT32 "); if (wphdr.flags & HYBRID_BITRATE) strcat (flags_list, "HYBRID-BITRATE "); if (wphdr.flags & FALSE_STEREO) strcat (flags_list, "FALSE-STEREO "); if (wphdr.flags & NEW_SHAPING) strcat (flags_list, "NEW-SHAPING "); if (wphdr.flags & HAS_CHECKSUM) strcat (flags_list, "CHECKSUM "); if (wphdr.flags & (IGNORED_FLAGS | UNKNOWN_FLAGS)) strcat (flags_list, "UNKNOWN-FLAGS "); if (wphdr.flags & FINAL_BLOCK) strcat (flags_list, "FINAL"); } else strcat (flags_list, "none"); printf ("flags: %s\n", flags_list); } else printf ("non-audio block of %u bytes, version 0x%03x\n", wphdr.ckSize + 8, wphdr.version); // read and parse the actual block data (which is entirely composed of "meta" blocks) if (wphdr.ckSize > sizeof (WavpackHeader) - 8) { uint32_t block_size = wphdr.ckSize + 8; char *block_buff = malloc (block_size); memcpy (block_buff, &wphdr, sizeof (WavpackHeader)); read_bytes (block_buff + sizeof (WavpackHeader), block_size - sizeof (WavpackHeader)); if (outfile) // add condition here if desired to select only certain blocks be written fwrite (block_buff, block_size, 1, outfile); parse_wavpack_block (block_buff); free (block_buff); } // if there's audio samples in there do some other sanity checks (especially for multichannel) if (wphdr.block_samples) { if ((wphdr.flags & INITIAL_BLOCK) && wphdr.block_index != last_sample + 1) printf ("error: discontinuity detected!\n"); if (!(wphdr.flags & INITIAL_BLOCK)) if (first_sample != wphdr.block_index || last_sample != wphdr.block_index + wphdr.block_samples - 1) printf ("error: multichannel block mismatch detected!\n"); last_sample = (first_sample = wphdr.block_index) + wphdr.block_samples - 1; if (wphdr.flags & INITIAL_BLOCK) { channel_count = (wphdr.flags & MONO_FLAG) ? 1 : 2; total_bytes = wphdr.ckSize + 8; block_count = 1; } else { channel_count += (wphdr.flags & MONO_FLAG) ? 1 : 2; total_bytes += wphdr.ckSize + 8; block_count++; if (wphdr.flags & FINAL_BLOCK) printf ("multichannel: %d channels in %d blocks, %u bytes total\n", channel_count, block_count, total_bytes); } } } if (outfile) fclose (outfile); return 0; } // read the next metadata block, or return 0 if there aren't any more (or an error occurs) static int read_metadata_buff (WavpackMetadata *wpmd, unsigned char *blockbuff, unsigned char **buffptr) { WavpackHeader *wphdr = (WavpackHeader *) blockbuff; unsigned char *buffend = blockbuff + wphdr->ckSize + 8; if (buffend - *buffptr < 2) return 0; wpmd->id = *(*buffptr)++; wpmd->byte_length = *(*buffptr)++ << 1; if (wpmd->id & ID_LARGE) { wpmd->id &= ~ID_LARGE; if (buffend - *buffptr < 2) return 0; wpmd->byte_length += *(*buffptr)++ << 9; wpmd->byte_length += *(*buffptr)++ << 17; } if (wpmd->id & ID_ODD_SIZE) { if (!wpmd->byte_length) // odd size and zero length makes no sense return 0; wpmd->id &= ~ID_ODD_SIZE; wpmd->byte_length--; } if (wpmd->byte_length) { if (buffend - *buffptr < wpmd->byte_length + (wpmd->byte_length & 1)) { wpmd->data = NULL; return 0; } wpmd->data = *buffptr; (*buffptr) += wpmd->byte_length + (wpmd->byte_length & 1); } else wpmd->data = NULL; return 1; } // given a pointer to a WavPack block, parse all the "meta" blocks and display something about them static void parse_wavpack_block (unsigned char *block_data) { unsigned char *blockptr = block_data + sizeof (WavpackHeader); WavpackHeader *wphdr = (WavpackHeader *) block_data; int metadata_count = 0; WavpackMetadata wpmd; while (read_metadata_buff (&wpmd, block_data, &blockptr)) { metadata_count++; if (wpmd.id & 0x10) printf (" metadata: ID = 0x%02x (UNASSIGNED), size = %d bytes\n", wpmd.id, wpmd.byte_length); else if (wpmd.id & 0x20) printf (" metadata: ID = 0x%02x (%s), size = %d bytes\n", wpmd.id, metadata_names [wpmd.id - 0x10], wpmd.byte_length); else printf (" metadata: ID = 0x%02x (%s), size = %d bytes\n", wpmd.id, metadata_names [wpmd.id], wpmd.byte_length); } if (blockptr != block_data + wphdr->ckSize + 8) printf ("error: garbage at end of WavPack block\n"); if (!verify_wavpack_block (block_data)) printf ("error: checksum failure on WavPack block\n"); } // Quickly verify the referenced block. It is assumed that the WavPack header has been converted // to native endian format. If a block checksum is performed, that is done in little-endian // (file) format. It is also assumed that the caller has made sure that the block length // indicated in the header is correct (we won't overflow the buffer). If a checksum is present, // then it is checked, otherwise we just check that all the metadata blocks are formatted // correctly (without looking at their contents). Returns FALSE for bad block. #define ID_BLOCK_CHECKSUM (ID_OPTIONAL_DATA | 0xf) static int verify_wavpack_block (unsigned char *buffer) { WavpackHeader *wphdr = (WavpackHeader *) buffer; uint32_t checksum_passed = 0, bcount, meta_bc; unsigned char *dp, meta_id, c1, c2; if (strncmp (wphdr->ckID, "wvpk", 4) || wphdr->ckSize + 8 < sizeof (WavpackHeader)) return 0; bcount = wphdr->ckSize - sizeof (WavpackHeader) + 8; dp = (unsigned char *)(wphdr + 1); while (bcount >= 2) { meta_id = *dp++; c1 = *dp++; meta_bc = c1 << 1; bcount -= 2; if (meta_id & ID_LARGE) { if (bcount < 2) return 0; c1 = *dp++; c2 = *dp++; meta_bc += ((uint32_t) c1 << 9) + ((uint32_t) c2 << 17); bcount -= 2; } if (bcount < meta_bc) return 0; if ((meta_id & ID_UNIQUE) == ID_BLOCK_CHECKSUM) { unsigned char *csptr = buffer; int wcount = (int)(dp - 2 - buffer) >> 1; uint32_t csum = (uint32_t) -1; if ((meta_id & ID_ODD_SIZE) || meta_bc < 2 || meta_bc > 4) return 0; native_to_little_endian ((WavpackHeader *) buffer, WavpackHeaderFormat); while (wcount--) { csum = (csum * 3) + csptr [0] + (csptr [1] << 8); csptr += 2; } little_endian_to_native ((WavpackHeader *) buffer, WavpackHeaderFormat); if (meta_bc == 4) { if (*dp++ != (csum & 0xff) || *dp++ != ((csum >> 8) & 0xff) || *dp++ != ((csum >> 16) & 0xff) || *dp++ != ((csum >> 24) & 0xff)) return 0; } else { csum ^= csum >> 16; if (*dp++ != (csum & 0xff) || *dp++ != ((csum >> 8) & 0xff)) return 0; } checksum_passed++; } bcount -= meta_bc; dp += meta_bc; } return (bcount == 0) && (!(wphdr->flags & HAS_CHECKSUM) || checksum_passed); } static int32_t read_bytes (void *buff, int32_t bcount) { return fread (buff, 1, bcount, stdin); } // Read from current file position until a valid 32-byte WavPack 4.0 header is // found and read into the specified pointer. The number of bytes skipped is // returned. If no WavPack header is found within 1 meg, then a -1 is returned // to indicate the error. No additional bytes are read past the header and it // is returned in the processor's native endian mode. Seeking is not required. static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr) { unsigned char buffer [sizeof (*wphdr)], *sp = buffer + sizeof (*wphdr), *ep = sp; uint32_t bytes_skipped = 0; int bleft; while (1) { if (sp < ep) { bleft = ep - sp; memmove (buffer, sp, bleft); } else bleft = 0; if (infile (buffer + bleft, sizeof (*wphdr) - bleft) != (int32_t) sizeof (*wphdr) - bleft) return -1; sp = buffer; if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' && !(*++sp & 1) && sp [2] < 16 && !sp [3] && (sp [2] || sp [1] || *sp >= 24) && sp [5] == 4 && sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff) && sp [18] < 3 && !sp [19]) { memcpy (wphdr, buffer, sizeof (*wphdr)); little_endian_to_native (wphdr, WavpackHeaderFormat); return bytes_skipped; } // printf ("read_next_header() did not see valid block right away: %c %c %c %c\n", buffer [0], buffer [1], buffer [2], buffer [3]); while (sp < ep && *sp != 'w') sp++; if ((bytes_skipped += sp - buffer) > 1024 * 1024) return -1; } } // using the specified format string, convert the referenced structure from little-endian to native static void little_endian_to_native (void *data, char *format) { unsigned char *cp = (unsigned char *) data; int32_t temp; while (*format) { switch (*format) { case 'L': temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24); * (int32_t *) cp = temp; cp += 4; break; case 'S': temp = cp [0] + (cp [1] << 8); * (int16_t *) cp = (int16_t) temp; cp += 2; break; default: if (isdigit (*format)) cp += *format - '0'; break; } format++; } } static void native_to_little_endian (void *data, char *format) { unsigned char *cp = (unsigned char *) data; int32_t temp; while (*format) { switch (*format) { case 'L': temp = * (int32_t *) cp; *cp++ = (unsigned char) temp; *cp++ = (unsigned char) (temp >> 8); *cp++ = (unsigned char) (temp >> 16); *cp++ = (unsigned char) (temp >> 24); break; case 'S': temp = * (int16_t *) cp; *cp++ = (unsigned char) temp; *cp++ = (unsigned char) (temp >> 8); break; default: if (isdigit (*format)) cp += *format - '0'; break; } format++; } }