1 ////////////////////////////////////////////////////////////////////////////
2 //                           **** WAVPACK ****                            //
3 //                  Hybrid Lossless Wavefile Compressor                   //
4 //                Copyright (c) 1998 - 2019 David Bryant.                 //
5 //                          All Rights Reserved.                          //
6 //      Distributed under the BSD Software License (see license.txt)      //
7 ////////////////////////////////////////////////////////////////////////////
8 
9 // riff.c
10 
11 // This module is a helper to the WavPack command-line programs to support WAV files
12 // (both MS standard and rf64 varients).
13 
14 #include <string.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 
18 #include "wavpack.h"
19 #include "utils.h"
20 
21 #pragma pack(push,4)
22 
23 typedef struct {
24     char ckID [4];
25     uint64_t chunkSize64;
26 } CS64Chunk;
27 
28 typedef struct {
29     uint64_t riffSize64, dataSize64, sampleCount64;
30     uint32_t tableLength;
31 } DS64Chunk;
32 
33 typedef struct {
34     char ckID [4];
35     uint32_t ckSize;
36     char junk [28];
37 } JunkChunk;
38 
39 #pragma pack(pop)
40 
41 #define CS64ChunkFormat "4D"
42 #define DS64ChunkFormat "DDDL"
43 
44 #define WAVPACK_NO_ERROR    0
45 #define WAVPACK_SOFT_ERROR  1
46 #define WAVPACK_HARD_ERROR  2
47 
48 extern int debug_logging_mode;
49 
ParseRiffHeaderConfig(FILE * infile,char * infilename,char * fourcc,WavpackContext * wpc,WavpackConfig * config)50 int ParseRiffHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config)
51 {
52     int is_rf64 = !strncmp (fourcc, "RF64", 4), got_ds64 = 0, format_chunk = 0;
53     int64_t total_samples = 0, infilesize;
54     RiffChunkHeader riff_chunk_header;
55     ChunkHeader chunk_header;
56     WaveHeader WaveHeader;
57     DS64Chunk ds64_chunk;
58     uint32_t bcount;
59 
60     CLEAR (WaveHeader);
61     CLEAR (ds64_chunk);
62     infilesize = DoGetFileSize (infile);
63 
64     if (!is_rf64 && infilesize >= 4294967296LL && !(config->qmode & QMODE_IGNORE_LENGTH)) {
65         error_line ("can't handle .WAV files larger than 4 GB (non-standard)!");
66         return WAVPACK_SOFT_ERROR;
67     }
68 
69     memcpy (&riff_chunk_header, fourcc, 4);
70 
71     if ((!DoReadFile (infile, ((char *) &riff_chunk_header) + 4, sizeof (RiffChunkHeader) - 4, &bcount) ||
72         bcount != sizeof (RiffChunkHeader) - 4 || strncmp (riff_chunk_header.formType, "WAVE", 4))) {
73             error_line ("%s is not a valid .WAV file!", infilename);
74             return WAVPACK_SOFT_ERROR;
75     }
76     else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
77         !WavpackAddWrapper (wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) {
78             error_line ("%s", WavpackGetErrorMessage (wpc));
79             return WAVPACK_SOFT_ERROR;
80     }
81 
82     // loop through all elements of the RIFF wav header
83     // (until the data chuck) and copy them to the output file
84 
85     while (1) {
86         if (!DoReadFile (infile, &chunk_header, sizeof (ChunkHeader), &bcount) ||
87             bcount != sizeof (ChunkHeader)) {
88                 error_line ("%s is not a valid .WAV file!", infilename);
89                 return WAVPACK_SOFT_ERROR;
90         }
91         else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
92             !WavpackAddWrapper (wpc, &chunk_header, sizeof (ChunkHeader))) {
93                 error_line ("%s", WavpackGetErrorMessage (wpc));
94                 return WAVPACK_SOFT_ERROR;
95         }
96 
97         WavpackLittleEndianToNative (&chunk_header, ChunkHeaderFormat);
98 
99         if (!strncmp (chunk_header.ckID, "ds64", 4)) {
100             if (chunk_header.ckSize < sizeof (DS64Chunk) ||
101                 !DoReadFile (infile, &ds64_chunk, sizeof (DS64Chunk), &bcount) ||
102                 bcount != sizeof (DS64Chunk)) {
103                     error_line ("%s is not a valid .WAV file!", infilename);
104                     return WAVPACK_SOFT_ERROR;
105             }
106             else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
107                 !WavpackAddWrapper (wpc, &ds64_chunk, sizeof (DS64Chunk))) {
108                     error_line ("%s", WavpackGetErrorMessage (wpc));
109                     return WAVPACK_SOFT_ERROR;
110             }
111 
112             got_ds64 = 1;
113             WavpackLittleEndianToNative (&ds64_chunk, DS64ChunkFormat);
114 
115             if (debug_logging_mode)
116                 error_line ("DS64: riffSize = %lld, dataSize = %lld, sampleCount = %lld, table_length = %d",
117                     (long long) ds64_chunk.riffSize64, (long long) ds64_chunk.dataSize64,
118                     (long long) ds64_chunk.sampleCount64, ds64_chunk.tableLength);
119 
120             if (ds64_chunk.tableLength * sizeof (CS64Chunk) != chunk_header.ckSize - sizeof (DS64Chunk)) {
121                 error_line ("%s is not a valid .WAV file!", infilename);
122                 return WAVPACK_SOFT_ERROR;
123             }
124 
125             while (ds64_chunk.tableLength--) {
126                 CS64Chunk cs64_chunk;
127                 if (!DoReadFile (infile, &cs64_chunk, sizeof (CS64Chunk), &bcount) ||
128                     bcount != sizeof (CS64Chunk) ||
129                     (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
130                     !WavpackAddWrapper (wpc, &cs64_chunk, sizeof (CS64Chunk)))) {
131                         error_line ("%s", WavpackGetErrorMessage (wpc));
132                         return WAVPACK_SOFT_ERROR;
133                 }
134             }
135         }
136         else if (!strncmp (chunk_header.ckID, "fmt ", 4)) {     // if it's the format chunk, we want to get some info out of there and
137             int supported = TRUE, format;                        // make sure it's a .wav file we can handle
138 
139             if (format_chunk++) {
140                 error_line ("%s is not a valid .WAV file!", infilename);
141                 return WAVPACK_SOFT_ERROR;
142             }
143 
144             if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) ||
145                 !DoReadFile (infile, &WaveHeader, chunk_header.ckSize, &bcount) ||
146                 bcount != chunk_header.ckSize) {
147                     error_line ("%s is not a valid .WAV file!", infilename);
148                     return WAVPACK_SOFT_ERROR;
149             }
150             else if (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
151                 !WavpackAddWrapper (wpc, &WaveHeader, chunk_header.ckSize)) {
152                     error_line ("%s", WavpackGetErrorMessage (wpc));
153                     return WAVPACK_SOFT_ERROR;
154             }
155 
156             WavpackLittleEndianToNative (&WaveHeader, WaveHeaderFormat);
157 
158             if (debug_logging_mode) {
159                 error_line ("format tag size = %d", chunk_header.ckSize);
160                 error_line ("FormatTag = %x, NumChannels = %d, BitsPerSample = %d",
161                     WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample);
162                 error_line ("BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d",
163                     WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond);
164 
165                 if (chunk_header.ckSize > 16)
166                     error_line ("cbSize = %d, ValidBitsPerSample = %d", WaveHeader.cbSize,
167                         WaveHeader.ValidBitsPerSample);
168 
169                 if (chunk_header.ckSize > 20)
170                     error_line ("ChannelMask = %x, SubFormat = %d",
171                         WaveHeader.ChannelMask, WaveHeader.SubFormat);
172             }
173 
174             if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2)
175                 config->qmode |= QMODE_ADOBE_MODE;
176 
177             format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ?
178                 WaveHeader.SubFormat : WaveHeader.FormatTag;
179 
180             config->bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.ValidBitsPerSample) ?
181                 WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample;
182 
183             if (format != 1 && format != 3)
184                 supported = FALSE;
185 
186             if (format == 3 && config->bits_per_sample != 32)
187                 supported = FALSE;
188 
189             if (!WaveHeader.NumChannels || WaveHeader.NumChannels > 256 ||
190                 WaveHeader.BlockAlign / WaveHeader.NumChannels < (config->bits_per_sample + 7) / 8 ||
191                 WaveHeader.BlockAlign / WaveHeader.NumChannels > 4 ||
192                 WaveHeader.BlockAlign % WaveHeader.NumChannels)
193                     supported = FALSE;
194 
195             if (config->bits_per_sample < 1 || config->bits_per_sample > 32)
196                 supported = FALSE;
197 
198             if (!supported) {
199                 error_line ("%s is an unsupported .WAV format!", infilename);
200                 return WAVPACK_SOFT_ERROR;
201             }
202 
203             if (chunk_header.ckSize < 40) {
204                 if (!config->channel_mask && !(config->qmode & QMODE_CHANS_UNASSIGNED)) {
205                     if (WaveHeader.NumChannels <= 2)
206                         config->channel_mask = 0x5 - WaveHeader.NumChannels;
207                     else if (WaveHeader.NumChannels <= 18)
208                         config->channel_mask = (1 << WaveHeader.NumChannels) - 1;
209                     else
210                         config->channel_mask = 0x3ffff;
211                 }
212             }
213             else if (WaveHeader.ChannelMask && (config->channel_mask || (config->qmode & QMODE_CHANS_UNASSIGNED))) {
214                 error_line ("this WAV file already has channel order information!");
215                 return WAVPACK_SOFT_ERROR;
216             }
217             else if (WaveHeader.ChannelMask)
218                 config->channel_mask = WaveHeader.ChannelMask;
219 
220             if (format == 3)
221                 config->float_norm_exp = 127;
222             else if ((config->qmode & QMODE_ADOBE_MODE) &&
223                 WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) {
224                     if (WaveHeader.BitsPerSample == 24)
225                         config->float_norm_exp = 127 + 23;
226                     else if (WaveHeader.BitsPerSample == 32)
227                         config->float_norm_exp = 127 + 15;
228 
229                     config->bits_per_sample = 32;   // make sure this is correct in Adobe modes
230             }
231 
232             if (debug_logging_mode) {
233                 if (config->float_norm_exp == 127)
234                     error_line ("data format: normalized 32-bit floating point");
235                 else if (config->float_norm_exp)
236                     error_line ("data format: 32-bit floating point (Audition %d:%d float type 1)",
237                         config->float_norm_exp - 126, 150 - config->float_norm_exp);
238                 else
239                     error_line ("data format: %d-bit integers stored in %d byte(s)",
240                         config->bits_per_sample, WaveHeader.BlockAlign / WaveHeader.NumChannels);
241             }
242         }
243         else if (!strncmp (chunk_header.ckID, "data", 4)) {             // on the data chunk, get size and exit loop
244 
245             int64_t data_chunk_size = (got_ds64 && chunk_header.ckSize == (uint32_t) -1) ?
246                 ds64_chunk.dataSize64 : chunk_header.ckSize;
247 
248 
249             if (!WaveHeader.NumChannels || (is_rf64 && !got_ds64)) {   // make sure we saw "fmt" and "ds64" chunks (if required)
250                 error_line ("%s is not a valid .WAV file!", infilename);
251                 return WAVPACK_SOFT_ERROR;
252             }
253 
254             if (infilesize && !(config->qmode & QMODE_IGNORE_LENGTH) && infilesize - data_chunk_size > 16777216) {
255                 error_line ("this .WAV file has over 16 MB of extra RIFF data, probably is corrupt!");
256                 return WAVPACK_SOFT_ERROR;
257             }
258 
259             if (config->qmode & QMODE_IGNORE_LENGTH) {
260                 if (infilesize && DoGetFilePosition (infile) != -1) {
261                     total_samples = (infilesize - DoGetFilePosition (infile)) / WaveHeader.BlockAlign;
262 
263                     if ((infilesize - DoGetFilePosition (infile)) % WaveHeader.BlockAlign)
264                         error_line ("warning: audio length does not divide evenly, %d bytes will be discarded!",
265                             (int)((infilesize - DoGetFilePosition (infile)) % WaveHeader.BlockAlign));
266                 }
267                 else
268                     total_samples = -1;
269             }
270             else {
271                 total_samples = data_chunk_size / WaveHeader.BlockAlign;
272 
273                 if (got_ds64 && total_samples != ds64_chunk.sampleCount64) {
274                     error_line ("%s is not a valid .WAV file!", infilename);
275                     return WAVPACK_SOFT_ERROR;
276                 }
277 
278                 if (!total_samples) {
279                     error_line ("this .WAV file has no audio samples, probably is corrupt!");
280                     return WAVPACK_SOFT_ERROR;
281                 }
282 
283                 if (total_samples > MAX_WAVPACK_SAMPLES) {
284                     error_line ("%s has too many samples for WavPack!", infilename);
285                     return WAVPACK_SOFT_ERROR;
286                 }
287             }
288 
289             config->bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels;
290             config->num_channels = WaveHeader.NumChannels;
291             config->sample_rate = WaveHeader.SampleRate;
292             break;
293         }
294         else {          // just copy unknown chunks to output file
295 
296             int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L;
297             char *buff;
298 
299             if (bytes_to_copy < 0 || bytes_to_copy > 4194304) {
300                 error_line ("%s is not a valid .WAV file!", infilename);
301                 return WAVPACK_SOFT_ERROR;
302             }
303 
304             buff = malloc (bytes_to_copy);
305 
306             if (debug_logging_mode)
307                 error_line ("extra unknown chunk \"%c%c%c%c\" of %d bytes",
308                     chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
309                     chunk_header.ckID [3], chunk_header.ckSize);
310 
311             if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||
312                 bcount != bytes_to_copy ||
313                 (!(config->qmode & QMODE_NO_STORE_WRAPPER) &&
314                 !WavpackAddWrapper (wpc, buff, bytes_to_copy))) {
315                     error_line ("%s", WavpackGetErrorMessage (wpc));
316                     free (buff);
317                     return WAVPACK_SOFT_ERROR;
318             }
319 
320             free (buff);
321         }
322     }
323 
324     if (!WavpackSetConfiguration64 (wpc, config, total_samples, NULL)) {
325         error_line ("%s: %s", infilename, WavpackGetErrorMessage (wpc));
326         return WAVPACK_SOFT_ERROR;
327     }
328 
329     return WAVPACK_NO_ERROR;
330 }
331