/////////////////////////////////////////////////////////////////////////// // **** WAVPACK **** // // Hybrid Lossless Wavefile Compressor // // Copyright (c) 1998 - 2005 Conifer Software. // // All Rights Reserved. // // Distributed under the BSD Software License (see license.txt) // //////////////////////////////////////////////////////////////////////////// // wputils.c // This module provides a high-level interface to reading and writing WavPack // files. WavPack input files can be opened as standard "C" streams using a // provided filename. However, an alternate entry uses stream-reading // callbacks to make using another file I/O method easy. Note that in this // case the user application is responsible for finding and opening the .wvc // file if the use of them is desired. // For writing WavPack files there are no I/O routines used; a callback for // writing completed blocks is provided. #include #include #include #include #include #ifdef WIN32 #include #endif #include "wavpack.h" #if !defined(WIN32) #define stricmp(x,y) strcasecmp(x,y) #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); #endif static void free_streams (WavpackContext *wpc); ///////////////////////////// local table storage //////////////////////////// const uint32_t sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; ///////////////////////////// executable code //////////////////////////////// #ifdef TAGS static int load_tag (WavpackContext *wpc); static int valid_tag (M_Tag *m_tag); static void free_tag (M_Tag *m_tag); #endif #if defined(UNPACK) || defined(INFO_ONLY) static uint32_t read_next_header (WavpackStreamReader *reader, void *id, WavpackHeader *wphdr); static uint32_t seek_final_index (WavpackStreamReader *reader, void *id); // This code provides an interface between the reader callback mechanism that // WavPack uses internally and the standard fstream C library. #ifdef USE_FSTREAMS static int32_t read_bytes (void *id, void *data, int32_t bcount) { return fread (data, 1, bcount, (FILE*) id); } static uint32_t get_pos (void *id) { return ftell ((FILE*) id); } static int set_pos_abs (void *id, uint32_t pos) { return fseek (id, pos, SEEK_SET); } static int set_pos_rel (void *id, int32_t delta, int mode) { return fseek (id, delta, mode); } static int push_back_byte (void *id, int c) { return ungetc (c, id); } static uint32_t get_length (void *id) { FILE *file = id; struct stat statbuf; if (!file || fstat (fileno (file), &statbuf) || !(statbuf.st_mode & S_IFREG)) return 0; return statbuf.st_size; } static int can_seek (void *id) { FILE *file = id; struct stat statbuf; return file && !fstat (fileno (file), &statbuf) && (statbuf.st_mode & S_IFREG); } static int32_t write_bytes (void *id, void *data, int32_t bcount) { return fwrite (data, 1, bcount, (FILE*) id); } static WavpackStreamReader freader = { read_bytes, get_pos, set_pos_abs, set_pos_rel, push_back_byte, get_length, can_seek, write_bytes }; // This function attempts to open the specified WavPack file for reading. If // this fails for any reason then an appropriate message is copied to "error" // (which must accept 80 characters) and NULL is returned, otherwise a // pointer to a WavpackContext structure is returned (which is used to call // all other functions in this module). A filename beginning with "-" is // assumed to be stdin. The "flags" argument has the following bit mask // values to specify details of the open operation: // OPEN_WVC: attempt to open/read "correction" file // OPEN_TAGS: attempt to read ID3v1 / APEv2 tags (requires seekable file) // OPEN_WRAPPER: make audio wrapper available (i.e. RIFF) to caller // OPEN_2CH_MAX: open only first stream of multichannel file (usually L/R) // OPEN_NORMALIZE: normalize floating point data to +/- 1.0 (w/ offset exp) // OPEN_STREAMING: blindly unpacks blocks w/o regard to header file position // OPEN_EDIT_TAGS: allow editing of tags (file must be writable) // Version 4.2 of the WavPack library adds the OPEN_STREAMING flag. This is // essentially a "raw" mode where the library will simply decode any blocks // fed it through the reader callback, regardless of where those blocks came // from in a stream. The only requirement is that complete WavPack blocks are // fed to the decoder (and this may require multiple blocks in multichannel // mode) and that complete blocks are decoded (even if all samples are not // actually required). All the blocks must contain the same number of channels // and bit resolution, and the correction data must be either present or not. // All other parameters may change from block to block (like lossy/lossless). // Obviously, in this mode any seeking must be performed by the application // (and again, decoding must start at the beginning of the block containing // the seek sample). WavpackContext *WavpackOpenFileInput (const char *infilename, char *error, int flags, int norm_offset) { char *file_mode = (flags & OPEN_EDIT_TAGS) ? "r+b" : "rb"; FILE *wv_id, *wvc_id; WavpackContext *wpc; if (*infilename == '-') { wv_id = stdin; #if defined(WIN32) setmode (fileno (stdin), O_BINARY); #endif } else if ((wv_id = fopen (infilename, file_mode)) == NULL) { strcpy (error, (flags & OPEN_EDIT_TAGS) ? "can't open file for editing" : "can't open file"); return NULL; } if (wv_id != stdin && (flags & OPEN_WVC)) { char *in2filename = malloc (strlen (infilename) + 10); strcpy (in2filename, infilename); strcat (in2filename, "c"); wvc_id = fopen (in2filename, "rb"); free (in2filename); } else wvc_id = NULL; wpc = WavpackOpenFileInputEx (&freader, wv_id, wvc_id, error, flags, norm_offset); if (!wpc) { if (wv_id) fclose (wv_id); if (wvc_id) fclose (wvc_id); } else wpc->close_files = TRUE; return wpc; } #endif // This function is identical to WavpackOpenFileInput() except that instead // of providing a filename to open, the caller provides a pointer to a set of // reader callbacks and instances of up to two streams. The first of these // streams is required and contains the regular WavPack data stream; the second // contains the "correction" file if desired. Unlike the standard open // function which handles the correction file transparently, in this case it // is the responsibility of the caller to be aware of correction files. WavpackContext *WavpackOpenFileInputEx (WavpackStreamReader *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset) { WavpackContext *wpc = malloc (sizeof (WavpackContext)); WavpackStream *wps; uchar first_byte; uint32_t bcount; if (!wpc) { strcpy (error, "can't allocate memory"); return NULL; } CLEAR (*wpc); wpc->wv_in = wv_id; wpc->wvc_in = wvc_id; wpc->reader = reader; wpc->total_samples = (uint32_t) -1; wpc->norm_offset = norm_offset; wpc->open_flags = flags; wpc->filelen = wpc->reader->get_length (wpc->wv_in); #ifdef TAGS if ((flags & (OPEN_TAGS | OPEN_EDIT_TAGS)) && wpc->reader->can_seek (wpc->wv_in)) { load_tag (wpc); wpc->reader->set_pos_abs (wpc->wv_in, 0); } #endif #ifdef VER3 if (wpc->reader->read_bytes (wpc->wv_in, &first_byte, 1) != 1) { strcpy (error, "can't read all of WavPack file!"); return WavpackCloseFile (wpc); } wpc->reader->push_back_byte (wpc->wv_in, first_byte); if (first_byte == 'R') return open_file3 (wpc, error); #endif wpc->streams [0] = wps = malloc (sizeof (WavpackStream)); wpc->num_streams = 1; CLEAR (*wps); while (!wps->wphdr.block_samples) { wpc->filepos = wpc->reader->get_pos (wpc->wv_in); bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr); if (bcount == (uint32_t) -1) { strcpy (error, "not compatible with this version of WavPack file!"); return WavpackCloseFile (wpc); } wpc->filepos += bcount; wps->blockbuff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->blockbuff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { strcpy (error, "can't read all of WavPack file!"); return WavpackCloseFile (wpc); } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (error, "not compatible with this version of WavPack file!"); return WavpackCloseFile (wpc); } if (wps->wphdr.block_samples && !(flags & OPEN_STREAMING)) { if (wps->wphdr.total_samples == (uint32_t) -1 && wpc->reader->can_seek (wpc->wv_in)) { uint32_t pos_save = wpc->reader->get_pos (wpc->wv_in); uint32_t final_index = seek_final_index (wpc->reader, wpc->wv_in); if (final_index != (uint32_t) -1) wpc->total_samples = final_index - wps->wphdr.block_index; wpc->reader->set_pos_abs (wpc->wv_in, pos_save); } else wpc->total_samples = wps->wphdr.total_samples; } if (wpc->wvc_in && wps->wphdr.block_samples && (wps->wphdr.flags & HYBRID_FLAG)) { wpc->file2len = wpc->reader->get_length (wpc->wvc_in); wpc->wvc_flag = TRUE; } if (wpc->wvc_flag) { wpc->file2pos = wpc->reader->get_pos (wpc->wvc_in); bcount = read_next_header (wpc->reader, wpc->wvc_in, &wps->wphdr); if (bcount == (uint32_t) -1) { strcpy (error, "not compatible with this version of correction file!"); return WavpackCloseFile (wpc); } wpc->file2pos += bcount; wps->block2buff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->block2buff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { strcpy (error, "problem with correction file!"); return WavpackCloseFile (wpc); } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (error, "not compatible with this version of correction file!"); return WavpackCloseFile (wpc); } } if (!unpack_init (wpc)) { strcpy (error, wpc->error_message [0] ? wpc->error_message : "not compatible with this version of WavPack file!"); return WavpackCloseFile (wpc); } } wpc->config.flags &= ~0xff; wpc->config.flags |= wps->wphdr.flags & 0xff; wpc->config.bytes_per_sample = (wps->wphdr.flags & BYTES_STORED) + 1; wpc->config.float_norm_exp = wps->float_norm_exp; wpc->config.bits_per_sample = (wpc->config.bytes_per_sample * 8) - ((wps->wphdr.flags & SHIFT_MASK) >> SHIFT_LSB); if (!wpc->config.sample_rate) { if (!wps || !wps->wphdr.block_samples || (wps->wphdr.flags & SRATE_MASK) == SRATE_MASK) wpc->config.sample_rate = 44100; else wpc->config.sample_rate = sample_rates [(wps->wphdr.flags & SRATE_MASK) >> SRATE_LSB]; } if (!wpc->config.num_channels) { wpc->config.num_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2; wpc->config.channel_mask = 0x5 - wpc->config.num_channels; } if ((flags & OPEN_2CH_MAX) && !(wps->wphdr.flags & FINAL_BLOCK)) wpc->reduced_channels = (wps->wphdr.flags & MONO_FLAG) ? 1 : 2; return wpc; } // This function obtains general information about an open input file and // returns a mask with the following bit values: // MODE_WVC: a .wvc file has been found and will be used for lossless // MODE_LOSSLESS: file is lossless (either pure or hybrid) // MODE_HYBRID: file is hybrid mode (either lossy or lossless) // MODE_FLOAT: audio data is 32-bit ieee floating point // MODE_VALID_TAG: file conatins a valid ID3v1 or APEv2 tag // MODE_HIGH: file was created in "high" mode (information only) // MODE_FAST: file was created in "fast" mode (information only) // MODE_EXTRA: file was created using "extra" mode (information only) // MODE_APETAG: file contains a valid APEv2 tag // MODE_SFX: file was created as a "self-extracting" executable int WavpackGetMode (WavpackContext *wpc) { int mode = 0; if (wpc) { if (wpc->config.flags & CONFIG_HYBRID_FLAG) mode |= MODE_HYBRID; else if (!(wpc->config.flags & CONFIG_LOSSY_MODE)) mode |= MODE_LOSSLESS; if (wpc->wvc_flag) mode |= (MODE_LOSSLESS | MODE_WVC); if (wpc->lossy_blocks) mode &= ~MODE_LOSSLESS; if (wpc->config.flags & CONFIG_FLOAT_DATA) mode |= MODE_FLOAT; if (wpc->config.flags & CONFIG_HIGH_FLAG) mode |= MODE_HIGH; if (wpc->config.flags & CONFIG_FAST_FLAG) mode |= MODE_FAST; if (wpc->config.flags & CONFIG_EXTRA_MODE) mode |= MODE_EXTRA; if (wpc->config.flags & CONFIG_CREATE_EXE) mode |= MODE_SFX; #ifdef TAGS if (valid_tag (&wpc->m_tag)) { mode |= MODE_VALID_TAG; if (valid_tag (&wpc->m_tag) == 'A') mode |= MODE_APETAG; } #endif } return mode; } // This function returns the major version number of the WavPack program // (or library) that created the open file. Currently, this can be 1 to 4. // Minor versions are not recorded in WavPack files. int WavpackGetVersion (WavpackContext *wpc) { if (wpc) { #ifdef VER3 if (wpc->stream3) return get_version3 (wpc); #endif return 4; } return 0; } #endif #ifdef UNPACK // Unpack the specified number of samples from the current file position. // Note that "samples" here refers to "complete" samples, which would be // 2 longs for stereo files or even more for multichannel files, so the // required memory at "buffer" is 4 * samples * num_channels bytes. The // audio data is returned right-justified in 32-bit longs in the endian // mode native to the executing processor. So, if the original data was // 16-bit, then the values returned would be +/-32k. Floating point data // can also be returned if the source was floating point data (and this // can be optionally normalized to +/-1.0 by using the appropriate flag // in the call to WavpackOpenFileInput ()). The actual number of samples // unpacked is returned, which should be equal to the number requested unless // the end of fle is encountered or an error occurs. After all samples have // been unpacked then 0 will be returned. uint32_t WavpackUnpackSamples (WavpackContext *wpc, int32_t *buffer, uint32_t samples) { WavpackStream *wps = wpc->streams [wpc->current_stream = 0]; uint32_t bcount, samples_unpacked = 0, samples_to_unpack; int num_channels = wpc->config.num_channels; int file_done = FALSE; #ifdef VER3 if (wpc->stream3) return unpack_samples3 (wpc, buffer, samples); #endif while (samples) { if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples) { free_streams (wpc); wpc->filepos = wpc->reader->get_pos (wpc->wv_in); bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr); if (bcount == (uint32_t) -1) { file_done = TRUE; break; } if (wpc->open_flags & OPEN_STREAMING) wps->wphdr.block_index = wps->sample_index = 0; wpc->filepos += bcount; wps->blockbuff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->blockbuff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { strcpy (wpc->error_message, "can't read all of WavPack file!"); file_done = TRUE; break; } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (wpc->error_message, "not compatible with this version of WavPack file!"); wpc->crc_errors++; break; } if (wps->wphdr.block_samples && wpc->wvc_flag) { wpc->file2pos = wpc->reader->get_pos (wpc->wvc_in); bcount = read_next_header (wpc->reader, wpc->wvc_in, &wps->wphdr); if (bcount == (uint32_t) -1) { file_done = TRUE; break; } if (wpc->open_flags & OPEN_STREAMING) wps->wphdr.block_index = wps->sample_index = 0; wpc->file2pos += bcount; wps->block2buff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->block2buff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { file_done = TRUE; break; } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (wpc->error_message, "not compatible with this version of WavPack file!"); wpc->crc_errors++; break; } } if (!wps->wphdr.block_samples || wps->sample_index == wps->wphdr.block_index) if (!unpack_init (wpc)) { wpc->crc_errors++; break; } } if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || wps->sample_index >= wps->wphdr.block_index + wps->wphdr.block_samples) continue; if (wps->sample_index < wps->wphdr.block_index) { samples_to_unpack = wps->wphdr.block_index - wps->sample_index; if (samples_to_unpack > samples) samples_to_unpack = samples; wps->sample_index += samples_to_unpack; samples_unpacked += samples_to_unpack; samples -= samples_to_unpack; if (wpc->reduced_channels) samples_to_unpack *= wpc->reduced_channels; else samples_to_unpack *= num_channels; while (samples_to_unpack--) *buffer++ = 0; continue; } samples_to_unpack = wps->wphdr.block_index + wps->wphdr.block_samples - wps->sample_index; if (samples_to_unpack > samples) samples_to_unpack = samples; if (!wpc->reduced_channels && !(wps->wphdr.flags & FINAL_BLOCK)) { int32_t *temp_buffer = malloc (samples_to_unpack * 8), *src, *dst; int offset = 0; uint32_t samcnt; while (1) { if (wpc->current_stream == wpc->num_streams) { wps = wpc->streams [wpc->num_streams++] = malloc (sizeof (WavpackStream)); CLEAR (*wps); bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr); if (bcount == (uint32_t) -1) { file_done = TRUE; break; } if (wpc->open_flags & OPEN_STREAMING) wps->wphdr.block_index = wps->sample_index = 0; wps->blockbuff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->blockbuff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { strcpy (wpc->error_message, "can't read all of WavPack file!"); file_done = TRUE; break; } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (wpc->error_message, "not compatible with this version of WavPack file!"); wpc->crc_errors++; break; } if (wpc->wvc_flag) { bcount = read_next_header (wpc->reader, wpc->wvc_in, &wps->wphdr); if (bcount == (uint32_t) -1) { file_done = TRUE; break; } if (wpc->open_flags & OPEN_STREAMING) wps->wphdr.block_index = wps->sample_index = 0; wps->block2buff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->block2buff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { file_done = TRUE; break; } if (wps->wphdr.flags & UNKNOWN_FLAGS) { strcpy (wpc->error_message, "not compatible with this version of WavPack file!"); wpc->crc_errors++; break; } } if (!unpack_init (wpc)) { wpc->crc_errors++; break; } } else wps = wpc->streams [wpc->current_stream]; unpack_samples (wpc, src = temp_buffer, samples_to_unpack); samcnt = samples_to_unpack; dst = buffer + offset; if (wps->wphdr.flags & MONO_FLAG) { while (samcnt--) { dst [0] = *src++; dst += num_channels; } offset++; } else { while (samcnt--) { dst [0] = *src++; dst [1] = *src++; dst += num_channels; } offset += 2; } if (wps->wphdr.flags & FINAL_BLOCK) break; else wpc->current_stream++; } wps = wpc->streams [wpc->current_stream = 0]; free (temp_buffer); } else unpack_samples (wpc, buffer, samples_to_unpack); if (wpc->reduced_channels) buffer += samples_to_unpack * wpc->reduced_channels; else buffer += samples_to_unpack * num_channels; samples_unpacked += samples_to_unpack; samples -= samples_to_unpack; if (wps->sample_index == wps->wphdr.block_index + wps->wphdr.block_samples) { if (check_crc_error (wpc) && wps->blockbuff) { if (wpc->reader->can_seek (wpc->wv_in)) { int32_t rseek = ((WavpackHeader *) wps->blockbuff)->ckSize / 3; wpc->reader->set_pos_rel (wpc->wv_in, (rseek > 16384) ? -16384 : -rseek, SEEK_CUR); } if (wpc->wvc_flag && wps->block2buff && wpc->reader->can_seek (wpc->wvc_in)) { int32_t rseek = ((WavpackHeader *) wps->block2buff)->ckSize / 3; wpc->reader->set_pos_rel (wpc->wvc_in, (rseek > 16384) ? -16384 : -rseek, SEEK_CUR); } wpc->crc_errors++; } } if (wps->sample_index == wpc->total_samples) { file_done = TRUE; break; } } if (samples) { if (wps->wphdr.block_samples && (wps->wphdr.flags & INITIAL_BLOCK)) wps->sample_index = wps->wphdr.block_index + wps->wphdr.block_samples; if (!samples_unpacked && !file_done) { while (num_channels--) *buffer++ = 0; samples_unpacked++; } } return samples_unpacked; } #ifdef SEEKING static uint32_t find_sample (WavpackContext *wpc, void *infile, uint32_t header_pos, uint32_t sample); // Seek to the specifed sample index, returning TRUE on success. Note that // files generated with version 4.0 or newer will seek almost immediately. // Older files can take quite long if required to seek through unplayed // portions of the file, but will create a seek map so that reverse seeks // (or forward seeks to already scanned areas) will be very fast. int WavpackSeekSample (WavpackContext *wpc, uint32_t sample) { WavpackStream *wps = wpc->streams [wpc->current_stream = 0]; uint32_t bcount, samples_to_skip; int32_t *buffer; if (wpc->total_samples == (uint32_t) -1 || sample >= wpc->total_samples || !wpc->reader->can_seek (wpc->wv_in) || (wpc->open_flags & OPEN_STREAMING) || (wpc->wvc_flag && !wpc->reader->can_seek (wpc->wvc_in))) return FALSE; #ifdef VER3 if (wpc->stream3) return seek_sample3 (wpc, sample); #endif if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || sample < wps->wphdr.block_index || sample >= wps->wphdr.block_index + wps->wphdr.block_samples) { free_streams (wpc); wpc->filepos = find_sample (wpc, wpc->wv_in, wpc->filepos, sample); if (wpc->filepos == (uint32_t) -1) return FALSE; if (wpc->wvc_flag) { wpc->file2pos = find_sample (wpc, wpc->wvc_in, 0, sample); if (wpc->file2pos == (uint32_t) -1) return FALSE; } } if (!wps->blockbuff) { wpc->reader->set_pos_abs (wpc->wv_in, wpc->filepos); wpc->reader->read_bytes (wpc->wv_in, &wps->wphdr, sizeof (WavpackHeader)); little_endian_to_native (&wps->wphdr, WavpackHeaderFormat); wps->blockbuff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->blockbuff, &wps->wphdr, sizeof (WavpackHeader)); if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + sizeof (WavpackHeader), wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { free_streams (wpc); return FALSE; } if (wpc->wvc_flag) { wpc->reader->set_pos_abs (wpc->wvc_in, wpc->file2pos); wpc->reader->read_bytes (wpc->wvc_in, &wps->wphdr, sizeof (WavpackHeader)); little_endian_to_native (&wps->wphdr, WavpackHeaderFormat); wps->block2buff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->block2buff, &wps->wphdr, sizeof (WavpackHeader)); if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + sizeof (WavpackHeader), wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { free_streams (wpc); return FALSE; } } if (!unpack_init (wpc)) { free_streams (wpc); return FALSE; } } while (!wpc->reduced_channels && !(wps->wphdr.flags & FINAL_BLOCK)) { if (++wpc->current_stream == wpc->num_streams) { wps = wpc->streams [wpc->num_streams++] = malloc (sizeof (WavpackStream)); CLEAR (*wps); bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr); if (bcount == (uint32_t) -1) break; wps->blockbuff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->blockbuff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) { strcpy (wpc->error_message, "can't read all of WavPack file!"); break; } if (wpc->wvc_flag) { bcount = read_next_header (wpc->reader, wpc->wvc_in, &wps->wphdr); if (bcount == (uint32_t) -1) break; wps->block2buff = malloc (wps->wphdr.ckSize + 8); memcpy (wps->block2buff, &wps->wphdr, 32); if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + 32, wps->wphdr.ckSize - 24) != wps->wphdr.ckSize - 24) break; } if (!unpack_init (wpc)) break; } else wps = wpc->streams [wpc->current_stream]; } if (sample < wps->sample_index) for (wpc->current_stream = 0; wpc->current_stream < wpc->num_streams; wpc->current_stream++) if (!unpack_init (wpc)) return FALSE; samples_to_skip = sample - wps->sample_index; if (samples_to_skip) { buffer = malloc (samples_to_skip * 8); for (wpc->current_stream = 0; wpc->current_stream < wpc->num_streams; wpc->current_stream++) unpack_samples (wpc, buffer, samples_to_skip); free (buffer); } wpc->current_stream = 0; return TRUE; } #endif #ifdef TAGS // Count and return the total number of tag items in the specified file. int WavpackGetNumTagItems (WavpackContext *wpc) { int i = 0; while (WavpackGetTagItemIndexed (wpc, i, NULL, 0)) ++i; return i; } // Attempt to get the specified item from the specified file's ID3v1 or APEv2 // tag. The "size" parameter specifies the amount of space available at "value", // if the desired item will not fit in this space then ellipses (...) will // be appended and the string terminated. Only text data are supported. The // actual length of the string is returned (or 0 if no matching value found). // Note that with APEv2 tags the length might not be the same as the number of // characters because UTF-8 encoding is used. Also, APEv2 tags can have multiple // (NULL separated) strings for a single value (this is why the length is // returned). If this function is called with a NULL "value" pointer (or a // zero "length") then only the actual length of the value data is returned // (not counting the terminating NULL). This can be used to determine the // actual memory to be allocated beforehand. static int get_ape_tag_item (M_Tag *m_tag, const char *item, char *value, int size); static int get_id3_tag_item (M_Tag *m_tag, const char *item, char *value, int size); int WavpackGetTagItem (WavpackContext *wpc, const char *item, char *value, int size) { M_Tag *m_tag = &wpc->m_tag; if (value && size) *value = 0; if (m_tag->ape_tag_hdr.ID [0] == 'A') return get_ape_tag_item (m_tag, item, value, size); else if (m_tag->id3_tag.tag_id [0] == 'T') return get_id3_tag_item (m_tag, item, value, size); else return 0; } static int get_ape_tag_item (M_Tag *m_tag, const char *item, char *value, int size) { char *p = m_tag->ape_tag_data; char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr); int i; for (i = 0; i < m_tag->ape_tag_hdr.item_count; ++i) { int vsize, flags, isize; vsize = * (int32_t *) p; p += 4; flags = * (int32_t *) p; p += 4; isize = strlen (p); little_endian_to_native (&vsize, "L"); little_endian_to_native (&flags, "L"); if (p + isize + vsize + 1 > q) break; if (isize && vsize && !stricmp (item, p) && !(flags & 6)) { if (!value || !size) return vsize; if (vsize < size) { memcpy (value, p + isize + 1, vsize); value [vsize] = 0; return vsize; } else if (size >= 4) { memcpy (value, p + isize + 1, size - 1); value [size - 4] = value [size - 3] = value [size - 2] = '.'; value [size - 1] = 0; return size - 1; } else return 0; } else p += isize + vsize + 1; } return 0; } static void tagcpy (char *dest, char *src, int tag_size); static int get_id3_tag_item (M_Tag *m_tag, const char *item, char *value, int size) { char lvalue [64]; int len; lvalue [0] = 0; if (!stricmp (item, "title")) tagcpy (lvalue, m_tag->id3_tag.title, sizeof (m_tag->id3_tag.title)); else if (!stricmp (item, "artist")) tagcpy (lvalue, m_tag->id3_tag.artist, sizeof (m_tag->id3_tag.artist)); else if (!stricmp (item, "album")) tagcpy (lvalue, m_tag->id3_tag.album, sizeof (m_tag->id3_tag.album)); else if (!stricmp (item, "year")) tagcpy (lvalue, m_tag->id3_tag.year, sizeof (m_tag->id3_tag.year)); else if (!stricmp (item, "comment")) tagcpy (lvalue, m_tag->id3_tag.comment, sizeof (m_tag->id3_tag.comment)); else if (!stricmp (item, "track") && m_tag->id3_tag.comment [29] && !m_tag->id3_tag.comment [28]) sprintf (lvalue, "%d", m_tag->id3_tag.comment [29]); else return 0; len = strlen (lvalue); if (!value || !size) return len; if (len < size) { strcpy (value, lvalue); return len; } else if (size >= 4) { strncpy (value, lvalue, size - 1); value [size - 4] = value [size - 3] = value [size - 2] = '.'; value [size - 1] = 0; return size - 1; } else return 0; } // This function looks up the tag item name by index and is used when the // application wants to access all the items in the file's ID3v1 or APEv2 tag. // Note that this function accesses only the item's name; WavpackGetTagItem() // still must be called to get the actual value. The "size" parameter specifies // the amount of space available at "item", if the desired item will not fit in // this space then ellipses (...) will be appended and the string terminated. // The actual length of the string is returned (or 0 if no item exists for // index). If this function is called with a NULL "value" pointer (or a // zero "length") then only the actual length of the item name is returned // (not counting the terminating NULL). This can be used to determine the // actual memory to be allocated beforehand. static int get_ape_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size); static int get_id3_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size); int WavpackGetTagItemIndexed (WavpackContext *wpc, int index, char *item, int size) { M_Tag *m_tag = &wpc->m_tag; if (item && size) *item = 0; if (m_tag->ape_tag_hdr.ID [0] == 'A') return get_ape_tag_item_indexed (m_tag, index, item, size); else if (m_tag->id3_tag.tag_id [0] == 'T') return get_id3_tag_item_indexed (m_tag, index, item, size); else return 0; } static int get_ape_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size) { char *p = m_tag->ape_tag_data; char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr); int i; for (i = 0; i < m_tag->ape_tag_hdr.item_count && index >= 0; ++i) { int vsize, flags, isize; vsize = * (int32_t *) p; p += 4; flags = * (int32_t *) p; p += 4; isize = strlen (p); little_endian_to_native (&vsize, "L"); little_endian_to_native (&flags, "L"); if (p + isize + vsize + 1 > q) break; if (isize && vsize && !(flags & 6) && !index--) { if (!item || !size) return isize; if (isize < size) { memcpy (item, p, isize); item [isize] = 0; return isize; } else if (size >= 4) { memcpy (item, p, size - 1); item [size - 4] = item [size - 3] = item [size - 2] = '.'; item [size - 1] = 0; return size - 1; } else return 0; } else p += isize + vsize + 1; } return 0; } static int tagdata (char *src, int tag_size); static int get_id3_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size) { char lvalue [16]; int len; lvalue [0] = 0; if (tagdata (m_tag->id3_tag.title, sizeof (m_tag->id3_tag.title)) && !index--) strcpy (lvalue, "Title"); else if (tagdata (m_tag->id3_tag.artist, sizeof (m_tag->id3_tag.artist)) && !index--) strcpy (lvalue, "Artist"); else if (tagdata (m_tag->id3_tag.album, sizeof (m_tag->id3_tag.album)) && !index--) strcpy (lvalue, "Album"); else if (tagdata (m_tag->id3_tag.year, sizeof (m_tag->id3_tag.year)) && !index--) strcpy (lvalue, "Year"); else if (tagdata (m_tag->id3_tag.comment, sizeof (m_tag->id3_tag.comment)) && !index--) strcpy (lvalue, "Comment"); else if (m_tag->id3_tag.comment [29] && !m_tag->id3_tag.comment [28] && !index--) strcpy (lvalue, "Track"); else return 0; len = strlen (lvalue); if (!item || !size) return len; if (len < size) { strcpy (item, lvalue); return len; } else if (size >= 4) { strncpy (item, lvalue, size - 1); item [size - 4] = item [size - 3] = item [size - 2] = '.'; item [size - 1] = 0; return size - 1; } else return 0; } #endif #endif #ifdef PACK // Open context for writing WavPack files. The returned context pointer is used // in all following calls to the library. The "blockout" function will be used // to store the actual completed WavPack blocks and will be called with the id // pointers containing user defined data (one for the wv file and one for the // wvc file). A return value of NULL indicates that memory could not be // allocated for the context. WavpackContext *WavpackOpenFileOutput (WavpackBlockOutput blockout, void *wv_id, void *wvc_id) { WavpackContext *wpc = malloc (sizeof (WavpackContext)); if (!wpc) return NULL; CLEAR (*wpc); wpc->blockout = blockout; wpc->wv_out = wv_id; wpc->wvc_out = wvc_id; return wpc; } // Set configuration for writing WavPack files. This must be done before // sending any actual samples, however it is okay to send wrapper or other // metadata before calling this. The "config" structure contains the following // required information: // config->bytes_per_sample see WavpackGetBytesPerSample() for info // config->bits_per_sample see WavpackGetBitsPerSample() for info // config->channel_mask Microsoft standard (mono = 4, stereo = 3) // config->num_channels self evident // config->sample_rate self evident // In addition, the following fields and flags may be set: // config->flags: // -------------- // o CONFIG_HYBRID_FLAG select hybrid mode (must set bitrate) // o CONFIG_JOINT_STEREO select joint stereo (must set override also) // o CONFIG_JOINT_OVERRIDE override default joint stereo selection // o CONFIG_HYBRID_SHAPE select hybrid noise shaping (set override & // shaping_weight != 0.0) // o CONFIG_SHAPE_OVERRIDE override default hybrid noise shaping // (set CONFIG_HYBRID_SHAPE and shaping_weight) // o CONFIG_FAST_FLAG "fast" compression mode // o CONFIG_HIGH_FLAG "high" compression mode // o CONFIG_BITRATE_KBPS hybrid bitrate is kbps, not bits / sample // o CONFIG_CREATE_WVC create correction file // o CONFIG_OPTIMIZE_WVC maximize bybrid compression (-cc option) // o CONFIG_CALC_NOISE calc noise in hybrid mode // o CONFIG_EXTRA_MODE extra processing mode (slow!) // o CONFIG_SKIP_WVX no wvx stream for floats & large ints // o CONFIG_MD5_CHECKSUM specify if you plan to store MD5 signature // o CONFIG_CREATE_EXE specify if you plan to prepend sfx module // config->bitrate hybrid bitrate in either bits/sample or kbps // config->shaping_weight hybrid noise shaping coefficient override // config->block_samples force samples per WavPack block (0 = use deflt) // config->float_norm_exp select floating-point data (127 for +/-1.0) // config->xmode extra mode processing value override // If the number of samples to be written is known then it should be passed // here. If the duration is not known then pass -1. In the case that the size // is not known (or the writing is terminated early) then it is suggested that // the application retrieve the first block written and let the library update // the total samples indication. A function is provided to do this update and // it should be done to the "correction" file also. If this cannot be done // (because a pipe is being used, for instance) then a valid WavPack will still // be created, but when applications want to access that file they will have // to seek all the way to the end to determine the actual duration. Also, if // a RIFF header has been included then it should be updated as well or the // WavPack file will not be directly unpackable to a valid wav file (although // it will still be usable by itself). A return of FALSE indicates an error. static const uint32_t xtable [] = { 123, 3, 27, 59, 123, 187, 251 }; static const uint32_t f_xtable [] = { 251, 3, 27, 59, 123, 187, 251 }; static const uint32_t h_xtable [] = { 91, 3, 27, 91, 123, 187, 251 }; int WavpackSetConfiguration (WavpackContext *wpc, WavpackConfig *config, uint32_t total_samples) { uint32_t flags = (config->bytes_per_sample - 1), bps = 0, shift = 0; uint32_t chan_mask = config->channel_mask; int num_chans = config->num_channels; int i; wpc->total_samples = total_samples; wpc->config.sample_rate = config->sample_rate; wpc->config.num_channels = config->num_channels; wpc->config.channel_mask = config->channel_mask; wpc->config.bits_per_sample = config->bits_per_sample; wpc->config.bytes_per_sample = config->bytes_per_sample; wpc->config.block_samples = config->block_samples; wpc->config.flags = config->flags; if (config->float_norm_exp) { wpc->config.float_norm_exp = config->float_norm_exp; wpc->config.flags |= CONFIG_FLOAT_DATA; flags |= FLOAT_DATA; } else shift = (config->bytes_per_sample * 8) - config->bits_per_sample; for (i = 0; i < 15; ++i) if (wpc->config.sample_rate == sample_rates [i]) break; flags |= i << SRATE_LSB; flags |= shift << SHIFT_LSB; if (config->flags & CONFIG_HYBRID_FLAG) { flags |= HYBRID_FLAG | HYBRID_BITRATE | HYBRID_BALANCE; if (!(wpc->config.flags & CONFIG_SHAPE_OVERRIDE)) { wpc->config.flags |= CONFIG_HYBRID_SHAPE | CONFIG_AUTO_SHAPING; flags |= HYBRID_SHAPE | NEW_SHAPING; } else if (wpc->config.flags & CONFIG_HYBRID_SHAPE) { wpc->config.shaping_weight = config->shaping_weight; flags |= HYBRID_SHAPE | NEW_SHAPING; } if (wpc->config.flags & CONFIG_OPTIMIZE_WVC) flags |= CROSS_DECORR; if (config->flags & CONFIG_BITRATE_KBPS) { bps = floor (config->bitrate * 256000.0 / config->sample_rate / config->num_channels + 0.5); if (bps > (64 << 8)) bps = 64 << 8; } else bps = floor (config->bitrate * 256.0 + 0.5); } else flags |= CROSS_DECORR; if (!(config->flags & CONFIG_JOINT_OVERRIDE) || (config->flags & CONFIG_JOINT_STEREO)) flags |= JOINT_STEREO; if (config->flags & CONFIG_CREATE_WVC) wpc->wvc_flag = TRUE; wpc->stream_version = CUR_STREAM_VERS; for (wpc->current_stream = 0; num_chans; wpc->current_stream++) { WavpackStream *wps = malloc (sizeof (WavpackStream)); uint32_t stereo_mask, mono_mask; int pos, chans; wpc->streams [wpc->current_stream] = wps; CLEAR (*wps); for (pos = 1; pos <= 18; ++pos) { stereo_mask = 3 << (pos - 1); mono_mask = 1 << (pos - 1); if ((chan_mask & stereo_mask) == stereo_mask && (mono_mask & 0x251)) { chan_mask &= ~stereo_mask; chans = 2; break; } else if (chan_mask & mono_mask) { chan_mask &= ~mono_mask; chans = 1; break; } } if (pos == 19) chans = num_chans > 1 ? 2 : 1; num_chans -= chans; if (num_chans && wpc->current_stream == MAX_STREAMS - 1) break; memcpy (wps->wphdr.ckID, "wvpk", 4); wps->wphdr.ckSize = sizeof (WavpackHeader) - 8; wps->wphdr.total_samples = wpc->total_samples; wps->wphdr.version = wpc->stream_version; wps->wphdr.flags = flags; wps->bits = bps; if (!wpc->current_stream) wps->wphdr.flags |= INITIAL_BLOCK; if (!num_chans) wps->wphdr.flags |= FINAL_BLOCK; if (chans == 1) { wps->wphdr.flags &= ~(JOINT_STEREO | CROSS_DECORR | HYBRID_BALANCE); wps->wphdr.flags |= MONO_FLAG; } } wpc->num_streams = wpc->current_stream; wpc->current_stream = 0; if (num_chans) { strcpy (wpc->error_message, "too many channels!"); return FALSE; } if (config->flags & CONFIG_EXTRA_MODE) { if (config->flags & CONFIG_HIGH_FLAG) wpc->config.extra_flags = h_xtable [config->xmode]; else if (config->flags & CONFIG_FAST_FLAG) wpc->config.extra_flags = f_xtable [config->xmode]; else wpc->config.extra_flags = xtable [config->xmode]; if (config->flags & CONFIG_JOINT_OVERRIDE) wpc->config.extra_flags &= ~EXTRA_STEREO_MODES; } return TRUE; } // Prepare to actually pack samples by determining the size of the WavPack // blocks and allocating sample buffers and initializing each stream. Call // after WavpackSetConfiguration() and before WavpackPackSamples(). A return // of FALSE indicates an error. int WavpackPackInit (WavpackContext *wpc) { if (wpc->metabytes > 4096) write_metadata_block (wpc); if (wpc->config.block_samples) wpc->block_samples = wpc->config.block_samples; else { if (wpc->config.flags & CONFIG_HIGH_FLAG) wpc->block_samples = wpc->config.sample_rate; else if (!(wpc->config.sample_rate % 2)) wpc->block_samples = wpc->config.sample_rate / 2; else wpc->block_samples = wpc->config.sample_rate; while (wpc->block_samples * wpc->config.num_channels > 150000) wpc->block_samples /= 2; while (wpc->block_samples * wpc->config.num_channels < 40000) wpc->block_samples *= 2; } wpc->max_samples = wpc->block_samples + (wpc->block_samples >> 1); for (wpc->current_stream = 0; wpc->streams [wpc->current_stream]; wpc->current_stream++) { WavpackStream *wps = wpc->streams [wpc->current_stream]; wps->sample_buffer = malloc (wpc->max_samples * (wps->wphdr.flags & MONO_FLAG ? 4 : 8)); pack_init (wpc); } return TRUE; } // Pack the specified samples. Samples must be stored in longs in the native // endian format of the executing processor. The number of samples specified // indicates composite samples (sometimes called "frames"). So, the actual // number of data points would be this "sample_count" times the number of // channels. Note that samples are accumulated here until enough exist to // create a complete WavPack block (or several blocks for multichannel audio). // If an application wants to break a block at a specific sample, then it must // simply call WavpackFlushSamples() to force an early termination. Completed // WavPack blocks are send to the function provided in the initial call to // WavpackOpenFileOutput(). A return of FALSE indicates an error. static int pack_streams (WavpackContext *wpc, uint32_t block_samples); static int create_riff_header (WavpackContext *wpc); int WavpackPackSamples (WavpackContext *wpc, int32_t *sample_buffer, uint32_t sample_count) { int nch = wpc->config.num_channels; while (sample_count) { int32_t *source_pointer = sample_buffer; uint samples_to_copy; if (!wpc->riff_header_added && !wpc->riff_header_created && !create_riff_header (wpc)) return FALSE; if (wpc->acc_samples + sample_count > wpc->max_samples) samples_to_copy = wpc->max_samples - wpc->acc_samples; else samples_to_copy = sample_count; for (wpc->current_stream = 0; wpc->streams [wpc->current_stream]; wpc->current_stream++) { WavpackStream *wps = wpc->streams [wpc->current_stream]; int32_t *dptr, *sptr, cnt; dptr = wps->sample_buffer + wpc->acc_samples * (wps->wphdr.flags & MONO_FLAG ? 1 : 2); sptr = source_pointer; cnt = samples_to_copy; if (wps->wphdr.flags & MONO_FLAG) { while (cnt--) { *dptr++ = *sptr; sptr += nch; } source_pointer++; } else { while (cnt--) { *dptr++ = sptr [0]; *dptr++ = sptr [1]; sptr += nch; } source_pointer += 2; } } sample_buffer += samples_to_copy * nch; sample_count -= samples_to_copy; if ((wpc->acc_samples += samples_to_copy) == wpc->max_samples && !pack_streams (wpc, wpc->block_samples)) return FALSE; } return TRUE; } // Flush all accumulated samples into WavPack blocks. This is normally called // after all samples have been sent to WavpackPackSamples(), but can also be // called to terminate a WavPack block at a specific sample (in other words it // is possible to continue after this operation). This is also called to // dump non-audio blocks like those holding metadata for various purposes. // A return of FALSE indicates an error. int WavpackFlushSamples (WavpackContext *wpc) { while (wpc->acc_samples) { uint32_t block_samples; if (wpc->acc_samples > wpc->block_samples) block_samples = wpc->acc_samples / 2; else block_samples = wpc->acc_samples; if (!pack_streams (wpc, block_samples)) return FALSE; } if (wpc->metacount) write_metadata_block (wpc); return TRUE; } // Note: The following function is no longer required because a proper wav // header is now automatically generated for the application. However, if the // application wants to generate its own header or wants to include additional // chunks, then this function can still be used in which case the automatic // wav header generation is suppressed. // Add wrapper (currently RIFF only) to WavPack blocks. This should be called // before sending any audio samples for the RIFF header or after all samples // have been sent for any RIFF trailer. WavpackFlushSamples() should be called // between sending the last samples and calling this for trailer data to make // sure that headers and trailers don't get mixed up in very short files. If // the exact contents of the RIFF header are not known because, for example, // the file duration is uncertain or trailing chunks are possible, simply write // a "dummy" header of the correct length. When all data has been written it // will be possible to read the first block written and update the header // directly. An example of this can be found in the Audition filter. A // return of FALSE indicates an error. int WavpackAddWrapper (WavpackContext *wpc, void *data, uint32_t bcount) { uint32_t index = WavpackGetSampleIndex (wpc); uchar meta_id; if (!index || index == (uint32_t) -1) { wpc->riff_header_added = TRUE; meta_id = ID_RIFF_HEADER; } else meta_id = ID_RIFF_TRAILER; return add_to_metadata (wpc, data, bcount, meta_id); } // Store computed MD5 sum in WavPack metadata. Note that the user must compute // the 16 byte sum; it is not done here. A return of FALSE indicates an error. int WavpackStoreMD5Sum (WavpackContext *wpc, uchar data [16]) { return add_to_metadata (wpc, data, 16, ID_MD5_CHECKSUM); } static int create_riff_header (WavpackContext *wpc) { RiffChunkHeader riffhdr; ChunkHeader datahdr, fmthdr; WaveHeader wavhdr; uint32_t total_samples = wpc->total_samples, total_data_bytes; int32_t channel_mask = wpc->config.channel_mask; int32_t sample_rate = wpc->config.sample_rate; int bytes_per_sample = wpc->config.bytes_per_sample; int bits_per_sample = wpc->config.bits_per_sample; int format = (wpc->config.float_norm_exp) ? 3 : 1; int num_channels = wpc->config.num_channels; int wavhdrsize = 16; wpc->riff_header_created = TRUE; if (format == 3 && wpc->config.float_norm_exp != 127) { strcpy (wpc->error_message, "can't create valid RIFF wav header for non-normalized floating data!"); return FALSE; } if (total_samples != (uint32_t) -1) total_data_bytes = total_samples * bytes_per_sample * num_channels; else total_data_bytes = -1; CLEAR (wavhdr); wavhdr.FormatTag = format; wavhdr.NumChannels = num_channels; wavhdr.SampleRate = sample_rate; wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample; wavhdr.BlockAlign = bytes_per_sample * num_channels; wavhdr.BitsPerSample = bits_per_sample; if (num_channels > 2 || channel_mask != 0x5 - num_channels) { wavhdrsize = sizeof (wavhdr); wavhdr.cbSize = 22; wavhdr.ValidBitsPerSample = bits_per_sample; wavhdr.SubFormat = format; wavhdr.ChannelMask = channel_mask; wavhdr.FormatTag = 0xfffe; wavhdr.BitsPerSample = bytes_per_sample * 8; wavhdr.GUID [4] = 0x10; wavhdr.GUID [6] = 0x80; wavhdr.GUID [9] = 0xaa; wavhdr.GUID [11] = 0x38; wavhdr.GUID [12] = 0x9b; wavhdr.GUID [13] = 0x71; } strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID)); strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType)); if (total_data_bytes != (uint32_t) -1) riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (datahdr) + total_data_bytes; else riffhdr.ckSize = total_data_bytes; strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID)); fmthdr.ckSize = wavhdrsize; strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID)); datahdr.ckSize = total_data_bytes; // write the RIFF chunks up to just before the data starts native_to_little_endian (&riffhdr, ChunkHeaderFormat); native_to_little_endian (&fmthdr, ChunkHeaderFormat); native_to_little_endian (&wavhdr, WaveHeaderFormat); native_to_little_endian (&datahdr, ChunkHeaderFormat); return add_to_metadata (wpc, &riffhdr, sizeof (riffhdr), ID_RIFF_HEADER) && add_to_metadata (wpc, &fmthdr, sizeof (fmthdr), ID_RIFF_HEADER) && add_to_metadata (wpc, &wavhdr, wavhdrsize, ID_RIFF_HEADER) && add_to_metadata (wpc, &datahdr, sizeof (datahdr), ID_RIFF_HEADER); } static int pack_streams (WavpackContext *wpc, uint32_t block_samples) { uint32_t max_blocksize = block_samples * 10 + 4096, bcount; uchar *outbuff, *outend, *out2buff, *out2end; int result; out2buff = (wpc->wvc_flag) ? malloc (max_blocksize) : NULL; out2end = out2buff + max_blocksize; outbuff = malloc (max_blocksize); outend = outbuff + max_blocksize; for (wpc->current_stream = 0; wpc->streams [wpc->current_stream]; wpc->current_stream++) { WavpackStream *wps = wpc->streams [wpc->current_stream]; uint32_t flags = wps->wphdr.flags; flags &= ~MAG_MASK; flags += (1 << MAG_LSB) * ((flags & BYTES_STORED) * 8 + 7); wps->wphdr.block_index = wps->sample_index; wps->wphdr.block_samples = block_samples; wps->wphdr.flags = flags; wps->block2buff = out2buff; wps->block2end = out2end; wps->blockbuff = outbuff; wps->blockend = outend; result = pack_block (wpc, wps->sample_buffer); wps->blockbuff = wps->block2buff = NULL; if (!result) { strcpy (wpc->error_message, "output buffer overflowed!"); break; } bcount = ((WavpackHeader *) outbuff)->ckSize + 8; native_to_little_endian ((WavpackHeader *) outbuff, WavpackHeaderFormat); result = wpc->blockout (wpc->wv_out, outbuff, bcount); if (!result) { strcpy (wpc->error_message, "can't write WavPack data, disk probably full!"); break; } wpc->filelen += bcount; if (out2buff) { bcount = ((WavpackHeader *) out2buff)->ckSize + 8; native_to_little_endian ((WavpackHeader *) out2buff, WavpackHeaderFormat); result = wpc->blockout (wpc->wvc_out, out2buff, bcount); if (!result) { strcpy (wpc->error_message, "can't write WavPack data, disk probably full!"); break; } wpc->file2len += bcount; } if (wpc->acc_samples != block_samples) memcpy (wps->sample_buffer, wps->sample_buffer + block_samples * (flags & MONO_FLAG ? 1 : 2), (wpc->acc_samples - block_samples) * sizeof (int32_t) * (flags & MONO_FLAG ? 1 : 2)); } wpc->current_stream = 0; wpc->acc_samples -= block_samples; free (outbuff); if (out2buff) free (out2buff); return result; } // Given the pointer to the first block written (to either a .wv or .wvc file), // update the block with the actual number of samples written. If the wav // header was generated by the library, then it is updated also. This should // be done if WavpackSetConfiguration() was called with an incorrect number // of samples (or -1). It is the responsibility of the application to read and // rewrite the block. An example of this can be found in the Audition filter. void WavpackUpdateNumSamples (WavpackContext *wpc, void *first_block) { uint32_t wrapper_size; little_endian_to_native (first_block, WavpackHeaderFormat); ((WavpackHeader *) first_block)->total_samples = WavpackGetSampleIndex (wpc); if (wpc->riff_header_created) { if (WavpackGetWrapperLocation (first_block, &wrapper_size)) { RiffChunkHeader *riffhdr = WavpackGetWrapperLocation (first_block, 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); } } } native_to_little_endian (first_block, WavpackHeaderFormat); } // Note: The following function is no longer required because the wav header // automatically generated for the application will also be updated by // WavpackUpdateNumSamples (). However, if the application wants to generate // its own header or wants to include additional chunks, then this function // still must be used to update the application generated header. // Given the pointer to the first block written to a WavPack file, this // function returns the location of the stored RIFF header that was originally // written with WavpackAddWrapper(). This would normally be used to update // the wav header to indicate that a different number of samples was actually // written or if additional RIFF chunks are written at the end of the file. // The "size" parameter can be set to non-NULL to obtain the exact size of the // RIFF header, and the function will return FALSE if the header is not found // in the block's metadata (or it is not a valid WavPack block). It is the // responsibility of the application to read and rewrite the block. An example // of this can be found in the Audition filter. static void *find_metadata (void *wavpack_block, int desired_id, uint32_t *size); void *WavpackGetWrapperLocation (void *first_block, uint32_t *size) { void *loc; little_endian_to_native (first_block, WavpackHeaderFormat); loc = find_metadata (first_block, ID_RIFF_HEADER, size); native_to_little_endian (first_block, WavpackHeaderFormat); return loc; } static void *find_metadata (void *wavpack_block, int desired_id, uint32_t *size) { WavpackHeader *wphdr = wavpack_block; uchar *dp, meta_id, c1, c2; uint32_t bcount, meta_bc; if (strncmp (wphdr->ckID, "wvpk", 4)) return NULL; bcount = wphdr->ckSize - sizeof (WavpackHeader) + 8; dp = (uchar *)(wphdr + 1); while (bcount >= 2) { meta_id = *dp++; c1 = *dp++; meta_bc = c1 << 1; bcount -= 2; if (meta_id & ID_LARGE) { if (bcount < 2) break; c1 = *dp++; c2 = *dp++; meta_bc += ((uint32_t) c1 << 9) + ((uint32_t) c2 << 17); bcount -= 2; } if ((meta_id & ID_UNIQUE) == desired_id) { if (size) *size = meta_bc + ((meta_id & ID_ODD_SIZE) ? 1 : 0); return dp; } bcount -= meta_bc; } return FALSE; } #endif #ifdef TAGS // Limited functionality to append APEv2 tags to WavPack files when they are // created has been added for version 4.2. This function is used to append the // specified field to the tag being created. If no tag has been started, then // an empty one will be allocated first. When finished, use WavpackWriteTag() // to write the completed tag to the file. Note that ID3 tags are not // supported and that no editing of existing tags is allowed (there are several // fine libraries available for this). A size parameter is included so that // values containing multiple (NULL separated) strings can be written. int WavpackAppendTagItem (WavpackContext *wpc, const char *item, const char *value, int vsize) { M_Tag *m_tag = &wpc->m_tag; int isize = strlen (item); while (WavpackDeleteTagItem (wpc, item)); if (!m_tag->ape_tag_hdr.ID [0]) { strncpy (m_tag->ape_tag_hdr.ID, "APETAGEX", sizeof (m_tag->ape_tag_hdr.ID)); m_tag->ape_tag_hdr.version = 2000; m_tag->ape_tag_hdr.length = sizeof (m_tag->ape_tag_hdr); m_tag->ape_tag_hdr.item_count = 0; m_tag->ape_tag_hdr.flags = 0x80000000; } if (m_tag->ape_tag_hdr.ID [0] == 'A') { int new_item_len = vsize + isize + 9, flags = 0; char *p; m_tag->ape_tag_hdr.item_count++; m_tag->ape_tag_hdr.length += new_item_len; p = m_tag->ape_tag_data = realloc (m_tag->ape_tag_data, m_tag->ape_tag_hdr.length); p += m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr) - new_item_len; native_to_little_endian (&vsize, "L"); native_to_little_endian (&flags, "L"); * (int32_t *) p = vsize; p += 4; * (int32_t *) p = flags; p += 4; little_endian_to_native (&vsize, "L"); little_endian_to_native (&flags, "L"); strcpy (p, item); p += isize + 1; memcpy (p, value, vsize); return TRUE; } else return FALSE; } int WavpackDeleteTagItem (WavpackContext *wpc, const char *item) { M_Tag *m_tag = &wpc->m_tag; if (m_tag->ape_tag_hdr.ID [0] == 'A') { char *p = m_tag->ape_tag_data; char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr); int i; for (i = 0; i < m_tag->ape_tag_hdr.item_count; ++i) { int vsize, flags, isize; vsize = * (int32_t *) p; p += 4; flags = * (int32_t *) p; p += 4; isize = strlen (p); little_endian_to_native (&vsize, "L"); little_endian_to_native (&flags, "L"); if (p + isize + vsize + 1 > q) break; if (isize && vsize && !stricmp (item, p)) { char *d = p - 8; p += isize + vsize + 1; while (p < q) *d++ = *p++; m_tag->ape_tag_hdr.length = d - m_tag->ape_tag_data + sizeof (APE_Tag_Hdr); m_tag->ape_tag_hdr.item_count--; return 1; } else p += isize + vsize + 1; } } return 0; } // Once a APEv2 tag has been created with WavpackAppendTag(), this function is // used to write the completed tag to the end of the WavPack file. Note that // this function uses the same "blockout" function that is used to write // regular WavPack blocks, although that's where the similarity ends. static int write_tag_blockout (WavpackContext *wpc); static int write_tag_reader (WavpackContext *wpc); int WavpackWriteTag (WavpackContext *wpc) { if (wpc->blockout) return write_tag_blockout (wpc); else return write_tag_reader (wpc); } static int write_tag_blockout (WavpackContext *wpc) { M_Tag *m_tag = &wpc->m_tag; int result = TRUE; if (m_tag->ape_tag_hdr.ID [0] == 'A' && m_tag->ape_tag_hdr.item_count) { m_tag->ape_tag_hdr.flags |= 0x20000000; native_to_little_endian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); result = wpc->blockout (wpc->wv_out, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)); little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); if (m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr)) result = wpc->blockout (wpc->wv_out, m_tag->ape_tag_data, m_tag->ape_tag_hdr.length - sizeof (m_tag->ape_tag_hdr)); m_tag->ape_tag_hdr.flags &= ~0x20000000; native_to_little_endian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); result = wpc->blockout (wpc->wv_out, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)); little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); } if (!result) strcpy (wpc->error_message, "can't write WavPack data, disk probably full!"); return result; } static int write_tag_reader (WavpackContext *wpc) { M_Tag *m_tag = &wpc->m_tag; uint32_t tag_size = 0; int result = TRUE; if (m_tag->ape_tag_hdr.ID [0] == 'A' && m_tag->ape_tag_hdr.item_count && m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr)) tag_size = m_tag->ape_tag_hdr.length + sizeof (m_tag->ape_tag_hdr); result = (wpc->open_flags & OPEN_EDIT_TAGS) && wpc->reader->can_seek (wpc->wv_in) && !wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END); if (result && tag_size < -m_tag->tag_file_pos) { int nullcnt = -m_tag->tag_file_pos - tag_size; char zero [1] = { 0 }; while (nullcnt--) wpc->reader->write_bytes (wpc->wv_in, &zero, 1); } if (result && tag_size) { m_tag->ape_tag_hdr.flags |= 0x20000000; native_to_little_endian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); result = (wpc->reader->write_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr)); little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); result = (wpc->reader->write_bytes (wpc->wv_in, m_tag->ape_tag_data, m_tag->ape_tag_hdr.length - sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr)); m_tag->ape_tag_hdr.flags &= ~0x20000000; native_to_little_endian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); result = (wpc->reader->write_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr)); little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); } if (!result) strcpy (wpc->error_message, "can't write WavPack data, disk probably full!"); return result; } #endif // Get total number of samples contained in the WavPack file, or -1 if unknown uint32_t WavpackGetNumSamples (WavpackContext *wpc) { return wpc ? wpc->total_samples : (uint32_t) -1; } // Get the current sample index position, or -1 if unknown uint32_t WavpackGetSampleIndex (WavpackContext *wpc) { if (wpc) { #ifdef VER3 if (wpc->stream3) return get_sample_index3 (wpc); else if (wpc->streams [0]) return wpc->streams [0]->sample_index; #else if (wpc->streams [0]) return wpc->streams [0]->sample_index; #endif } return (uint32_t) -1; } // Get the number of errors encountered so far int WavpackGetNumErrors (WavpackContext *wpc) { return wpc ? wpc->crc_errors : 0; } // return TRUE if any uncorrected lossy blocks were actually written or read int WavpackLossyBlocks (WavpackContext *wpc) { return wpc ? wpc->lossy_blocks : 0; } // Calculate the progress through the file as a double from 0.0 (for begin) // to 1.0 (for done). A return value of -1.0 indicates that the progress is // unknown. double WavpackGetProgress (WavpackContext *wpc) { if (wpc && wpc->total_samples != (uint32_t) -1 && wpc->total_samples != 0) return (double) WavpackGetSampleIndex (wpc) / wpc->total_samples; else return -1.0; } // Return the total size of the WavPack file(s) in bytes. uint32_t WavpackGetFileSize (WavpackContext *wpc) { return wpc ? wpc->filelen + wpc->file2len : 0; } // Calculate the ratio of the specified WavPack file size to the size of the // original audio data as a double greater than 0.0 and (usually) smaller than // 1.0. A value greater than 1.0 represents "negative" compression and a // return value of 0.0 indicates that the ratio cannot be determined. double WavpackGetRatio (WavpackContext *wpc) { if (wpc && wpc->total_samples != (uint32_t) -1 && wpc->filelen) { double output_size = (double) wpc->total_samples * wpc->config.num_channels * wpc->config.bytes_per_sample; double input_size = (double) wpc->filelen + wpc->file2len; if (output_size >= 1.0 && input_size >= 1.0) return input_size / output_size; } return 0.0; } // Calculate the average bitrate of the WavPack file in bits per second. A // return of 0.0 indicates that the bitrate cannot be determined. An option is // provided to use (or not use) any attendant .wvc file. double WavpackGetAverageBitrate (WavpackContext *wpc, int count_wvc) { if (wpc && wpc->total_samples != (uint32_t) -1 && wpc->filelen) { double output_time = (double) wpc->total_samples / wpc->config.sample_rate; double input_size = (double) wpc->filelen + (count_wvc ? wpc->file2len : 0); if (output_time >= 1.0 && input_size >= 1.0) return input_size * 8.0 / output_time; } return 0.0; } #ifdef UNPACK // Calculate the bitrate of the current WavPack file block in bits per second. // This can be used for an "instant" bit display and gets updated from about // 1 to 4 times per second. A return of 0.0 indicates that the bitrate cannot // be determined. double WavpackGetInstantBitrate (WavpackContext *wpc) { if (wpc->stream3) return WavpackGetAverageBitrate (wpc, TRUE); if (wpc && wpc->streams [0] && wpc->streams [0]->wphdr.block_samples) { double output_time = (double) wpc->streams [0]->wphdr.block_samples / wpc->config.sample_rate; double input_size = 0; int si; for (si = 0; si < wpc->num_streams; ++si) { if (wpc->streams [si]->blockbuff) input_size += ((WavpackHeader *) wpc->streams [si]->blockbuff)->ckSize; if (wpc->streams [si]->block2buff) input_size += ((WavpackHeader *) wpc->streams [si]->block2buff)->ckSize; } if (output_time > 0.0 && input_size >= 1.0) return input_size * 8.0 / output_time; } return 0.0; } #endif // Close the specified WavPack file and release all resources used by it. // Returns NULL. WavpackContext *WavpackCloseFile (WavpackContext *wpc) { free_streams (wpc); if (wpc->streams [0]) free (wpc->streams [0]); #ifdef VER3 if (wpc->stream3) free_stream3 (wpc); #endif #if defined(UNPACK) || defined(INFO_ONLY) if (wpc->close_files) { #ifdef USE_FSTREAMS if (wpc->wv_in != NULL) fclose (wpc->wv_in); if (wpc->wvc_in != NULL) fclose (wpc->wvc_in); #endif } WavpackFreeWrapper (wpc); #endif #ifdef TAGS free_tag (&wpc->m_tag); #endif free (wpc); return NULL; } // Returns the sample rate of the specified WavPack file uint32_t WavpackGetSampleRate (WavpackContext *wpc) { return wpc ? wpc->config.sample_rate : 44100; } // Returns the number of channels of the specified WavPack file. Note that // this is the actual number of channels contained in the file even if the // OPEN_2CH_MAX flag was specified when the file was opened. int WavpackGetNumChannels (WavpackContext *wpc) { return wpc ? wpc->config.num_channels : 2; } int WavpackGetFloatNormExp (WavpackContext *wpc) { return wpc->config.float_norm_exp; } // Returns the actual number of valid bits per sample contained in the // original file, which may or may not be a multiple of 8. Floating data // always has 32 bits, integers may be from 1 to 32 bits each. When this // value is not a multiple of 8, then the "extra" bits are located in the // LSBs of the results. That is, values are right justified when unpacked // into ints, but are left justified in the number of bytes used by the // original data. int WavpackGetBitsPerSample (WavpackContext *wpc) { return wpc ? wpc->config.bits_per_sample : 16; } // Returns the number of bytes used for each sample (1 to 4) in the original // file. This is required information for the user of this module because the // audio data is returned in the LOWER bytes of the long buffer and must be // left-shifted 8, 16, or 24 bits if normalized longs are required. int WavpackGetBytesPerSample (WavpackContext *wpc) { return wpc ? wpc->config.bytes_per_sample : 2; } #if defined(UNPACK) || defined(INFO_ONLY) // If the OPEN_2CH_MAX flag is specified when opening the file, this function // will return the actual number of channels decoded from the file (which may // or may not be less than the actual number of channels, but will always be // 1 or 2). Normally, this will be the front left and right channels of a // multichannel file. int WavpackGetReducedChannels (WavpackContext *wpc) { if (wpc) return wpc->reduced_channels ? wpc->reduced_channels : wpc->config.num_channels; else return 2; } // These routines are used to access (and free) header and trailer data that // was retrieved from the Wavpack file. The header will be available before // the samples are decoded and the trailer will be available after all samples // have been read. uint32_t WavpackGetWrapperBytes (WavpackContext *wpc) { return wpc ? wpc->wrapper_bytes : 0; } uchar *WavpackGetWrapperData (WavpackContext *wpc) { return wpc ? wpc->wrapper_data : NULL; } void WavpackFreeWrapper (WavpackContext *wpc) { if (wpc && wpc->wrapper_data) { free (wpc->wrapper_data); wpc->wrapper_data = NULL; wpc->wrapper_bytes = 0; } } // Get any MD5 checksum stored in the metadata (should be called after reading // last sample or an extra seek will occur). A return value of FALSE indicates // that no MD5 checksum was stored. static int seek_md5 (WavpackStreamReader *reader, void *id, uchar data [16]); int WavpackGetMD5Sum (WavpackContext *wpc, uchar data [16]) { if (wpc->config.flags & CONFIG_MD5_CHECKSUM) { if (wpc->config.md5_read) { memcpy (data, wpc->config.md5_checksum, 16); return TRUE; } else if (wpc->reader->can_seek (wpc->wv_in)) { uint32_t pos_save = wpc->reader->get_pos (wpc->wv_in); wpc->config.md5_read = seek_md5 (wpc->reader, wpc->wv_in, wpc->config.md5_checksum); wpc->reader->set_pos_abs (wpc->wv_in, pos_save); if (wpc->config.md5_read) { memcpy (data, wpc->config.md5_checksum, 16); return TRUE; } else return FALSE; } } return FALSE; } #endif // Free all memory allocated for raw WavPack blocks (for all allocated streams) // and free all additonal streams. This does not free the default stream ([0]) // which is always kept around. static void free_streams (WavpackContext *wpc) { int si = wpc->num_streams; while (si--) { if (wpc->streams [si]->blockbuff) { free (wpc->streams [si]->blockbuff); wpc->streams [si]->blockbuff = NULL; } if (wpc->streams [si]->block2buff) { free (wpc->streams [si]->block2buff); wpc->streams [si]->block2buff = NULL; } if (wpc->streams [si]->sample_buffer) { free (wpc->streams [si]->sample_buffer); wpc->streams [si]->sample_buffer = NULL; } if (si) { wpc->num_streams--; free (wpc->streams [si]); wpc->streams [si] = NULL; } } wpc->current_stream = 0; } #ifdef TAGS // Return TRUE is a valid ID3v1 or APEv2 tag has been loaded. static int valid_tag (M_Tag *m_tag) { if (m_tag->ape_tag_hdr.ID [0] == 'A') return 'A'; else if (m_tag->id3_tag.tag_id [0] == 'T') return 'T'; else return 0; } // Free the data for any APEv2 tag that was allocated. static void free_tag (M_Tag *m_tag) { if (m_tag->ape_tag_data) { free (m_tag->ape_tag_data); m_tag->ape_tag_data = 0; } } #endif #if defined(UNPACK) || defined(INFO_ONLY) // 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 (WavpackStreamReader *reader, void *id, WavpackHeader *wphdr) { 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; memcpy (buffer, sp, bleft); } else bleft = 0; if (reader->read_bytes (id, buffer + bleft, sizeof (*wphdr) - bleft) != sizeof (*wphdr) - bleft) return -1; sp = buffer; if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' && !(*++sp & 1) && sp [2] < 16 && !sp [3] && sp [5] == 4 && sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff)) { memcpy (wphdr, buffer, sizeof (*wphdr)); little_endian_to_native (wphdr, WavpackHeaderFormat); return bytes_skipped; } while (sp < ep && *sp != 'w') sp++; if ((bytes_skipped += sp - buffer) > 1024 * 1024) return -1; } } // This function is used to seek to end of a file to determine its actual // length in samples by reading the last header block containing data. // Currently, all WavPack files contain the sample length in the first block // containing samples, however this might not always be the case. Obviously, // this function requires a seekable file or stream and leaves the file // pointer undefined. A return value of -1 indicates the length could not // be determined. static uint32_t seek_final_index (WavpackStreamReader *reader, void *id) { uint32_t result = (uint32_t) -1, bcount; WavpackHeader wphdr; if (reader->get_length (id) > 1200000L) reader->set_pos_rel (id, -1048576L, SEEK_END); while (1) { bcount = read_next_header (reader, id, &wphdr); if (bcount == (uint32_t) -1) return result; if (wphdr.block_samples && (wphdr.flags & FINAL_BLOCK)) result = wphdr.block_index + wphdr.block_samples; if (wphdr.ckSize > sizeof (WavpackHeader) - 8) reader->set_pos_rel (id, wphdr.ckSize - sizeof (WavpackHeader) + 8, SEEK_CUR); } } static int seek_md5 (WavpackStreamReader *reader, void *id, uchar data [16]) { uchar meta_id, c1, c2; uint32_t bcount, meta_bc; WavpackHeader wphdr; if (reader->get_length (id) > 1200000L) reader->set_pos_rel (id, -1048576L, SEEK_END); while (1) { bcount = read_next_header (reader, id, &wphdr); if (bcount == (uint32_t) -1) return FALSE; bcount = wphdr.ckSize - sizeof (WavpackHeader) + 8; while (bcount >= 2) { if (reader->read_bytes (id, &meta_id, 1) != 1 || reader->read_bytes (id, &c1, 1) != 1) return FALSE; meta_bc = c1 << 1; bcount -= 2; if (meta_id & ID_LARGE) { if (bcount < 2 || reader->read_bytes (id, &c1, 1) != 1 || reader->read_bytes (id, &c2, 1) != 1) return FALSE; meta_bc += ((uint32_t) c1 << 9) + ((uint32_t) c2 << 17); bcount -= 2; } if (meta_id == ID_MD5_CHECKSUM) return (meta_bc == 16 && bcount >= 16 && reader->read_bytes (id, data, 16) == 16); reader->set_pos_rel (id, meta_bc, SEEK_CUR); bcount -= meta_bc; } } } #ifdef SEEKING // Find a valid WavPack header, searching either from the current file position // (or from the specified position if not -1) and store it (endian corrected) // at the specified pointer. The return value is the exact file position of the // header, although we may have actually read past it. Because this function // is used for seeking to a specific audio sample, it only considers blocks // that contain audio samples for the initial stream to be valid. #define BUFSIZE 4096 static uint32_t find_header (WavpackStreamReader *reader, void *id, uint32_t filepos, WavpackHeader *wphdr) { char *buffer = malloc (BUFSIZE), *sp = buffer, *ep = buffer; if (filepos != (uint32_t) -1 && reader->set_pos_abs (id, filepos)) { free (buffer); return -1; } while (1) { int bleft; if (sp < ep) { bleft = ep - sp; memcpy (buffer, sp, bleft); ep -= (sp - buffer); sp = buffer; } else { if (sp > ep) if (reader->set_pos_rel (id, sp - ep, SEEK_CUR)) { free (buffer); return -1; } sp = ep = buffer; bleft = 0; } ep += reader->read_bytes (id, ep, BUFSIZE - bleft); if (ep - sp < 32) { free (buffer); return -1; } while (sp + 32 <= ep) if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' && !(*++sp & 1) && sp [2] < 16 && !sp [3] && sp [5] == 4 && sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff)) { memcpy (wphdr, sp - 4, sizeof (*wphdr)); little_endian_to_native (wphdr, WavpackHeaderFormat); if (wphdr->block_samples && (wphdr->flags & INITIAL_BLOCK)) { free (buffer); return reader->get_pos (id) - (ep - sp + 4); } if (wphdr->ckSize > 1024) sp += wphdr->ckSize - 1024; } } } // Find the WavPack block that contains the specified sample. If "header_pos" // is zero, then no information is assumed except the total number of samples // in the file and its size in bytes. If "header_pos" is non-zero then we // assume that it is the file position of the valid header image contained in // the first stream and we can limit our search to either the portion above // or below that point. If a .wvc file is being used, then this must be called // for that file also. static uint32_t find_sample (WavpackContext *wpc, void *infile, uint32_t header_pos, uint32_t sample) { WavpackStream *wps = wpc->streams [wpc->current_stream]; uint32_t file_pos1 = 0, file_pos2 = wpc->reader->get_length (infile); uint32_t sample_pos1 = 0, sample_pos2 = wpc->total_samples; double ratio = 0.96; int file_skip = 0; if (sample >= wpc->total_samples) return -1; if (header_pos && wps->wphdr.block_samples) { if (wps->wphdr.block_index > sample) { sample_pos2 = wps->wphdr.block_index; file_pos2 = header_pos; } else if (wps->wphdr.block_index + wps->wphdr.block_samples <= sample) { sample_pos1 = wps->wphdr.block_index; file_pos1 = header_pos; } else return header_pos; } while (1) { double bytes_per_sample; uint32_t seek_pos; bytes_per_sample = file_pos2 - file_pos1; bytes_per_sample /= sample_pos2 - sample_pos1; seek_pos = file_pos1 + (file_skip ? 32 : 0); seek_pos += (uint32_t)(bytes_per_sample * (sample - sample_pos1) * ratio); seek_pos = find_header (wpc->reader, infile, seek_pos, &wps->wphdr); if (seek_pos == (uint32_t) -1 || seek_pos >= file_pos2) { if (ratio > 0.0) { if ((ratio -= 0.24) < 0.0) ratio = 0.0; } else return -1; } else if (wps->wphdr.block_index > sample) { sample_pos2 = wps->wphdr.block_index; file_pos2 = seek_pos; } else if (wps->wphdr.block_index + wps->wphdr.block_samples <= sample) { if (seek_pos == file_pos1) file_skip = 1; else { sample_pos1 = wps->wphdr.block_index; file_pos1 = seek_pos; } } else return seek_pos; } } #endif #ifdef TAGS // This function attempts to load an ID3v1 or APEv2 tag from the specified // file into the specified M_Tag structure. The ID3 tag fits in completely, // but an APEv2 tag is variable length and so space must be allocated here // to accomodate the data, and this will need to be freed later. A return // value of TRUE indicates a valid tag was found and loaded. Note that the // file pointer is undefined when this function exits. static int load_tag (WavpackContext *wpc) { M_Tag *m_tag = &wpc->m_tag; CLEAR (*m_tag); while (1) { // attempt to find an APEv2 tag either at end-of-file or before a ID3v1 tag we found if (m_tag->id3_tag.tag_id [0] == 'T') wpc->reader->set_pos_rel (wpc->wv_in, -(sizeof (APE_Tag_Hdr) + sizeof (ID3_Tag)), SEEK_END); else wpc->reader->set_pos_rel (wpc->wv_in, -sizeof (APE_Tag_Hdr), SEEK_END); if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) == sizeof (APE_Tag_Hdr) && !strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) { little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); if (m_tag->ape_tag_hdr.version == 2000 && m_tag->ape_tag_hdr.item_count && m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr) && m_tag->ape_tag_hdr.length < (1024 * 1024) && (m_tag->ape_tag_data = malloc (m_tag->ape_tag_hdr.length)) != NULL) { if (m_tag->id3_tag.tag_id [0] == 'T') m_tag->tag_file_pos = -sizeof (ID3_Tag); else m_tag->tag_file_pos = 0; m_tag->tag_file_pos -= m_tag->ape_tag_hdr.length + sizeof (APE_Tag_Hdr); wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END); memset (m_tag->ape_tag_data, 0, m_tag->ape_tag_hdr.length); if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) != sizeof (APE_Tag_Hdr) || strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) { free (m_tag->ape_tag_data); CLEAR (*m_tag); return FALSE; // something's wrong... } little_endian_to_native (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format); if (m_tag->ape_tag_hdr.version != 2000 || !m_tag->ape_tag_hdr.item_count || m_tag->ape_tag_hdr.length < sizeof (m_tag->ape_tag_hdr) || m_tag->ape_tag_hdr.length > (1024 * 1024)) { free (m_tag->ape_tag_data); CLEAR (*m_tag); return FALSE; // something's wrong... } if (wpc->reader->read_bytes (wpc->wv_in, m_tag->ape_tag_data, m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr)) != m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr)) { free (m_tag->ape_tag_data); CLEAR (*m_tag); return FALSE; // something's wrong... } else { CLEAR (m_tag->id3_tag); // ignore ID3v1 tag if we found APEv2 tag return TRUE; } } } if (m_tag->id3_tag.tag_id [0] == 'T') { // settle for the ID3v1 tag that we found CLEAR (m_tag->ape_tag_hdr); return TRUE; } // look for ID3v1 tag if APEv2 tag not found during first pass m_tag->tag_file_pos = -sizeof (ID3_Tag); wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END); if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->id3_tag, sizeof (ID3_Tag)) != sizeof (ID3_Tag) || strncmp (m_tag->id3_tag.tag_id, "TAG", 3)) { CLEAR (*m_tag); return FALSE; // neither type of tag found } } } // Copy the specified ID3v1 tag value (with specified field size) from the // source pointer to the destination, eliminating leading spaces and trailing // spaces and nulls. static void tagcpy (char *dest, char *src, int tag_size) { char *s1 = src, *s2 = src + tag_size - 1; if (*s2 && !s2 [-1]) s2--; while (s1 <= s2) if (*s1 == ' ') ++s1; else if (!*s2 || *s2 == ' ') --s2; else break; while (*s1 && s1 <= s2) *dest++ = *s1++; *dest = 0; } static int tagdata (char *src, int tag_size) { char *s1 = src, *s2 = src + tag_size - 1; if (*s2 && !s2 [-1]) s2--; while (s1 <= s2) if (*s1 == ' ') ++s1; else if (!*s2 || *s2 == ' ') --s2; else break; return (*s1 && s1 <= s2); } #endif #endif