//////////////////////////////////////////////////////////////////////////// // **** WAVPACK **** // // Hybrid Lossless Wavefile Compressor // // Copyright (c) 1998 - 2022 David Bryant. // // All Rights Reserved. // // Distributed under the BSD Software License (see license.txt) // //////////////////////////////////////////////////////////////////////////// // wave64.c // This module is a helper to the WavPack command-line programs to support Sony's // Wave64 WAV file variant. Note that unlike the WAV/RF64 version, this does not // fall back to conventional WAV in the < 4GB case. #include #include #include #include "wavpack.h" #include "utils.h" typedef struct { char ckID [16]; int64_t ckSize; char formType [16]; } Wave64FileHeader; typedef struct { char ckID [16]; int64_t ckSize; } Wave64ChunkHeader; #define Wave64ChunkHeaderFormat "88D" static const unsigned char riff_guid [16] = { 'r','i','f','f', 0x2e,0x91,0xcf,0x11,0xa5,0xd6,0x28,0xdb,0x04,0xc1,0x00,0x00 }; static const unsigned char wave_guid [16] = { 'w','a','v','e', 0xf3,0xac,0xd3,0x11,0x8c,0xd1,0x00,0xc0,0x4f,0x8e,0xdb,0x8a }; static const unsigned char fmt_guid [16] = { 'f','m','t',' ', 0xf3,0xac,0xd3,0x11,0x8c,0xd1,0x00,0xc0,0x4f,0x8e,0xdb,0x8a }; static const unsigned char data_guid [16] = { 'd','a','t','a', 0xf3,0xac,0xd3,0x11,0x8c,0xd1,0x00,0xc0,0x4f,0x8e,0xdb,0x8a }; #define WAVPACK_NO_ERROR 0 #define WAVPACK_SOFT_ERROR 1 #define WAVPACK_HARD_ERROR 2 extern int debug_logging_mode; int ParseWave64HeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config) { int64_t total_samples = 0, infilesize; Wave64ChunkHeader chunk_header; Wave64FileHeader filehdr; WaveHeader WaveHeader; int format_chunk = 0; uint32_t bcount; CLEAR (WaveHeader); infilesize = DoGetFileSize (infile); memcpy (&filehdr, fourcc, 4); if (!DoReadFile (infile, ((char *) &filehdr) + 4, sizeof (Wave64FileHeader) - 4, &bcount) || bcount != sizeof (Wave64FileHeader) - 4 || memcmp (filehdr.ckID, riff_guid, sizeof (riff_guid)) || memcmp (filehdr.formType, wave_guid, sizeof (wave_guid))) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (wpc, &filehdr, sizeof (filehdr))) { error_line ("%s", WavpackGetErrorMessage (wpc)); return WAVPACK_SOFT_ERROR; } #if 1 // this might be a little too picky... WavpackLittleEndianToNative (&filehdr, Wave64ChunkHeaderFormat); if (infilesize && !(config->qmode & QMODE_IGNORE_LENGTH) && filehdr.ckSize && filehdr.ckSize + 1 && filehdr.ckSize != infilesize) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } #endif // loop through all elements of the wave64 header // (until the data chuck) and copy them to the output file while (1) { if (!DoReadFile (infile, &chunk_header, sizeof (Wave64ChunkHeader), &bcount) || bcount != sizeof (Wave64ChunkHeader)) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (wpc, &chunk_header, sizeof (Wave64ChunkHeader))) { error_line ("%s", WavpackGetErrorMessage (wpc)); return WAVPACK_SOFT_ERROR; } WavpackLittleEndianToNative (&chunk_header, Wave64ChunkHeaderFormat); chunk_header.ckSize -= sizeof (chunk_header); // 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 (!memcmp (chunk_header.ckID, fmt_guid, sizeof (fmt_guid))) { int supported = TRUE, format; if (format_chunk++) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } chunk_header.ckSize = (chunk_header.ckSize + 7) & ~7L; if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) || !DoReadFile (infile, &WaveHeader, (uint32_t) chunk_header.ckSize, &bcount) || bcount != chunk_header.ckSize) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (wpc, &WaveHeader, (uint32_t) chunk_header.ckSize)) { error_line ("%s", WavpackGetErrorMessage (wpc)); return WAVPACK_SOFT_ERROR; } WavpackLittleEndianToNative (&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) config->qmode |= QMODE_ADOBE_MODE; format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ? WaveHeader.SubFormat : WaveHeader.FormatTag; config->bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.ValidBitsPerSample) ? WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample; if (format != 1 && format != 3) supported = FALSE; if (format == 3 && config->bits_per_sample != 32) supported = FALSE; if (!WaveHeader.NumChannels || WaveHeader.NumChannels > WAVPACK_MAX_CLI_CHANS || WaveHeader.BlockAlign / WaveHeader.NumChannels < (config->bits_per_sample + 7) / 8 || WaveHeader.BlockAlign / WaveHeader.NumChannels > 4 || WaveHeader.BlockAlign % WaveHeader.NumChannels) supported = FALSE; if (config->bits_per_sample < 1 || config->bits_per_sample > 32) supported = FALSE; if (!supported) { error_line ("%s is an unsupported .W64 format!", infilename); return WAVPACK_SOFT_ERROR; } if ((config->qmode & QMODE_EVEN_BYTE_DEPTH) && (config->bits_per_sample % 8)) config->bits_per_sample += 8 - (config->bits_per_sample % 8); if (chunk_header.ckSize < 40) { if (!config->channel_mask && !(config->qmode & QMODE_CHANS_UNASSIGNED)) { if (WaveHeader.NumChannels <= 2) config->channel_mask = 0x5 - WaveHeader.NumChannels; else if (WaveHeader.NumChannels <= 18) config->channel_mask = (1 << WaveHeader.NumChannels) - 1; else config->channel_mask = 0x3ffff; } } else if (WaveHeader.ChannelMask && (config->channel_mask || (config->qmode & QMODE_CHANS_UNASSIGNED))) { error_line ("this W64 file already has channel order information!"); return WAVPACK_SOFT_ERROR; } else if (WaveHeader.ChannelMask) config->channel_mask = WaveHeader.ChannelMask; if (format == 3) config->float_norm_exp = 127; else if ((config->qmode & QMODE_ADOBE_MODE) && WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) { if (WaveHeader.BitsPerSample == 24) config->float_norm_exp = 127 + 23; else if (WaveHeader.BitsPerSample == 32) config->float_norm_exp = 127 + 15; } if (debug_logging_mode) { if (config->float_norm_exp == 127) error_line ("data format: normalized 32-bit floating point"); else error_line ("data format: %d-bit integers stored in %d byte(s)", config->bits_per_sample, WaveHeader.BlockAlign / WaveHeader.NumChannels); } } else if (!memcmp (chunk_header.ckID, data_guid, sizeof (data_guid))) { // on the data chunk, get size and exit loop if (!WaveHeader.NumChannels) { // make sure we saw "fmt" chunk error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } if ((config->qmode & QMODE_IGNORE_LENGTH) || chunk_header.ckSize <= 0) { config->qmode |= QMODE_IGNORE_LENGTH; if (infilesize && DoGetFilePosition (infile) != -1) { total_samples = (infilesize - DoGetFilePosition (infile)) / WaveHeader.BlockAlign; if ((infilesize - DoGetFilePosition (infile)) % WaveHeader.BlockAlign) error_line ("warning: audio length does not divide evenly, %d bytes will be discarded!", (int)((infilesize - DoGetFilePosition (infile)) % WaveHeader.BlockAlign)); } else total_samples = -1; } else { if (infilesize && infilesize - chunk_header.ckSize > 16777216) { error_line ("this .W64 file has over 16 MB of extra RIFF data, probably is corrupt!"); return WAVPACK_SOFT_ERROR; } total_samples = chunk_header.ckSize / WaveHeader.BlockAlign; if (!total_samples) { error_line ("this .W64 file has no audio samples, probably is corrupt!"); return WAVPACK_SOFT_ERROR; } if (total_samples > MAX_WAVPACK_SAMPLES) { error_line ("%s has too many samples for WavPack!", infilename); return WAVPACK_SOFT_ERROR; } } config->bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels; config->num_channels = WaveHeader.NumChannels; config->sample_rate = WaveHeader.SampleRate; break; } else { // just copy unknown chunks to output file int bytes_to_copy = (chunk_header.ckSize + 7) & ~7L; char *buff; if (bytes_to_copy < 0 || bytes_to_copy > 4194304) { error_line ("%s is not a valid .W64 file!", infilename); return WAVPACK_SOFT_ERROR; } 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 || (!(config->qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (wpc, buff, bytes_to_copy))) { error_line ("%s", WavpackGetErrorMessage (wpc)); free (buff); return WAVPACK_SOFT_ERROR; } free (buff); } } if (!WavpackSetConfiguration64 (wpc, config, total_samples, NULL)) { error_line ("%s: %s", infilename, WavpackGetErrorMessage (wpc)); return WAVPACK_SOFT_ERROR; } return WAVPACK_NO_ERROR; }