288 lines
12 KiB
C
288 lines
12 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** 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 <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#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;
|
|
}
|