/* * This file is a part of Pcompress, a chunked parallel multi- * algorithm lossless compression and decompression program. * * Copyright (C) 2012-2013 Moinak Ghosh. All rights reserved. * Use is subject to license terms. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. * If not, see . * * moinakg@belenix.org, http://moinakg.wordpress.com/ * */ /* */ #ifdef _ENABLE_WAVPACK_ #include #include #include #include #include #include #include #include "wavpack.h" #ifdef __cplusplus extern "C" { #endif typedef struct write_data_s { uint32_t bytes_written, first_block_size; uint32_t bufsize; uchar_t *buf; int error; } write_data; typedef struct read_data_s { uint32_t bytes_read; uint32_t bufsize; uchar_t *buf; } read_data; #define TRUE 1 #define FALSE 0 /* * Utility functions to read and write to memory areas as if they are files. */ static int write_block (void *id, void *data, int32_t length) { write_data *wid = (write_data *) id; if (wid->error) return FALSE; if (wid && wid->buf && data && length) { if (wid->bytes_written + length <= wid->bufsize) { memcpy(wid->buf + wid->bytes_written, data, length); wid->bytes_written += length; if (!wid->first_block_size) wid->first_block_size = length; } else { wid->error = 1; return FALSE; } } return TRUE; } static int read_block(read_data *rdat, uchar_t *tgt, uint32_t len, uint32_t *numread) { uint32_t numcopy; if (rdat->bufsize - rdat->bytes_read >= len) numcopy = len; else numcopy = rdat->bufsize - rdat->bytes_read; if (numcopy == 0) return (FALSE); memcpy(tgt, rdat->buf + rdat->bytes_read, numcopy); rdat->bytes_read += numcopy; *numread = numcopy; return (TRUE); } /* * This function returns a direct pointer into the data area rather than * copying the data into another supplied buffer. This is faster than * the traditional read semantics as in read_block() above. */ static int read_block_ref(read_data *rdat, uchar_t **ref, uint32_t len, uint32_t *numread) { uint32_t numcopy; if (rdat->bufsize - rdat->bytes_read >= len) numcopy = len; else numcopy = rdat->bufsize - rdat->bytes_read; if (numcopy == 0) return (FALSE); *ref = rdat->buf + rdat->bytes_read; rdat->bytes_read += numcopy; *numread = numcopy; return (TRUE); } /* * Memory buffer I/O functions mirroring semantics of stdio, for Wavpack * I/O ops structure. */ static int32_t read_bytes(void *id, void *data, int32_t bcount) { read_data *rdat = (read_data *)id; uint32_t numread; if (!read_block(rdat, (uchar_t *)data, bcount, &numread)) { return (0); } return (numread); } static uint32_t get_pos(void *id) { read_data *rdat = (read_data *)id; return (rdat->bytes_read); } static int set_pos_abs(void *id, uint32_t pos) { read_data *rdat = (read_data *)id; if (pos > rdat->bufsize) pos = rdat->bufsize; rdat->bytes_read = pos; return (0); } static int set_pos_rel(void *id, int32_t delta, int mode) { read_data *rdat = (read_data *)id; int64_t br; br = rdat->bytes_read; if (mode == SEEK_SET) { br = delta; } else if (mode == SEEK_CUR) { br += delta; } else if (mode == SEEK_END) { br = rdat->bufsize - delta; } else { errno = EINVAL; return (-1); } if (br < 0) { errno = EINVAL; return (-1); } if (br > rdat->bufsize) br = rdat->bufsize; rdat->bytes_read = br; return (0); } static int push_back_byte(void *id, int c) { read_data *rdat = (read_data *)id; if (rdat->bytes_read > 0) { rdat->bytes_read--; rdat->buf[rdat->bytes_read] = c; return (c); } else { return (EOF); } } static uint32_t get_length(void *id) { read_data *rdat = (read_data *)id; return (rdat->bufsize); } static int can_seek(void *id) { return (TRUE); } static WavpackStreamReader memreader = { read_bytes, get_pos, set_pos_abs, set_pos_rel, push_back_byte, get_length, can_seek, write_block }; #define INPUT_SAMPLES 65536 static int pack_audio(WavpackContext *wpc, read_data *rdat) { uint32_t samples_remaining, input_samples = INPUT_SAMPLES, samples_read = 0; int bytes_per_sample; int32_t *sample_buffer; unsigned char *input_buffer; // don't use an absurd amount of memory just because we have an absurd number of channels while (input_samples * sizeof (int32_t) * WavpackGetNumChannels(wpc) > 2048*1024) input_samples >>= 1; WavpackPackInit(wpc); bytes_per_sample = WavpackGetBytesPerSample(wpc) * WavpackGetNumChannels(wpc); sample_buffer = malloc(input_samples * sizeof (int32_t) * WavpackGetNumChannels(wpc)); samples_remaining = WavpackGetNumSamples(wpc); while (1) { uint32_t bytes_to_read, bytes_read = 0; unsigned int sample_count; if (samples_remaining > input_samples) bytes_to_read = input_samples * bytes_per_sample; else bytes_to_read = samples_remaining * bytes_per_sample; samples_remaining -= bytes_to_read / bytes_per_sample; read_block_ref(rdat, &input_buffer, bytes_to_read, &bytes_read); samples_read += sample_count = bytes_read / bytes_per_sample; if (!sample_count) { break; } else { unsigned int cnt = sample_count * WavpackGetNumChannels(wpc); unsigned char *sptr = input_buffer; int32_t *dptr = sample_buffer; switch (WavpackGetBytesPerSample(wpc)) { case 1: while (cnt--) *dptr++ = *sptr++ - 128; break; case 2: while (cnt--) { *dptr++ = sptr[0] | ((int32_t)(signed char) sptr[1] << 8); sptr += 2; } break; case 3: while (cnt--) { *dptr++ = sptr[0] | ((int32_t) sptr[1] << 8) | ((int32_t)(signed char) sptr[2] << 16); sptr += 3; } break; case 4: while (cnt--) { *dptr++ = sptr[0] | ((int32_t) sptr[1] << 8) | ((int32_t) sptr[2] << 16) | ((int32_t)(signed char) sptr[3] << 24); sptr += 4; } break; } } if (!WavpackPackSamples(wpc, sample_buffer, sample_count)) { free(sample_buffer); return (0); } } free(sample_buffer); if (!WavpackFlushSamples(wpc)) { return (0); } return (1); } /* * Helper routine for wavpack. Higher level encoding interface adapted from * pack_file() in cli/wavpack.c and unpack_file() in cli/wvunpack.c */ size_t wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf, int cmp_level) { uint32_t total_samples = 0, bcount; WavpackConfig loc_config; RiffChunkHeader riff_chunk_header; write_data wv_dat; read_data rd_dat; ChunkHeader chunk_header; WaveHeader WaveHeader; WavpackContext *wpc; int adobe_mode, result; memset(&WaveHeader, 0, sizeof (WaveHeader)); memset(&wv_dat, 0, sizeof (wv_dat)); memset(&rd_dat, 0, sizeof (rd_dat)); memset(&loc_config, 0, sizeof (loc_config)); adobe_mode = 0; if (cmp_level < 6) { loc_config.flags |= CONFIG_FAST_FLAG; } else if (cmp_level > 8) { loc_config.flags |= CONFIG_HIGH_FLAG; } *out_buf = (uchar_t *)malloc(len); if (*out_buf == NULL) { log_msg(LOG_ERR, 1, "malloc failed."); return (0); } wv_dat.buf = *out_buf; wv_dat.bufsize = len; wpc = WavpackOpenFileOutput(write_block, &wv_dat, NULL); rd_dat.buf = in_buf; rd_dat.bufsize = len; // Read (and copy to output) initial RIFF form header if (!read_block(&rd_dat, (uchar_t *)&riff_chunk_header, sizeof (RiffChunkHeader), &bcount) || bcount != sizeof (RiffChunkHeader) || strncmp(riff_chunk_header.ckID, "RIFF", 4) || strncmp(riff_chunk_header.formType, "WAVE", 4)) { WavpackCloseFile(wpc); return (0); } if (!WavpackAddWrapper(wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) { WavpackCloseFile(wpc); return (0); } // loop through all elements of the RIFF wav header // (until the data chunk) and copy them to the output // while (1) { if (!read_block(&rd_dat, (uchar_t *)&chunk_header, sizeof (ChunkHeader), &bcount) || bcount != sizeof (ChunkHeader)) { WavpackCloseFile(wpc); return (0); } if (!WavpackAddWrapper(wpc, &chunk_header, sizeof (ChunkHeader))) { WavpackCloseFile(wpc); return (0); } WavpackLittleEndianToNative (&chunk_header, ChunkHeaderFormat); // if it's the format chunk, we want to get some info out of there and // make sure it's a .wav file we can handle // if (strncmp(chunk_header.ckID, "fmt ", 4) == 0) { int supported = TRUE, format; if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) || !read_block(&rd_dat, (uchar_t *)&WaveHeader, chunk_header.ckSize, &bcount) || bcount != chunk_header.ckSize) { WavpackCloseFile(wpc); return (0); } if (!WavpackAddWrapper(wpc, &WaveHeader, chunk_header.ckSize)) { WavpackCloseFile(wpc); return (0); } WavpackLittleEndianToNative (&WaveHeader, WaveHeaderFormat); if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2) adobe_mode = 1; format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ? WaveHeader.SubFormat : WaveHeader.FormatTag; loc_config.bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.ValidBitsPerSample) ? WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample; if (format != 1 && format != 3) supported = FALSE; if (format == 3 && loc_config.bits_per_sample != 32) supported = FALSE; if (!WaveHeader.NumChannels || WaveHeader.NumChannels > 256 || WaveHeader.BlockAlign / WaveHeader.NumChannels < (loc_config.bits_per_sample + 7) / 8 || WaveHeader.BlockAlign / WaveHeader.NumChannels > 4 || WaveHeader.BlockAlign % WaveHeader.NumChannels) supported = FALSE; if (loc_config.bits_per_sample < 1 || loc_config.bits_per_sample > 32) supported = FALSE; if (!supported) { WavpackCloseFile(wpc); return (0); } if (chunk_header.ckSize < 40) { if (WaveHeader.NumChannels <= 2) loc_config.channel_mask = 0x5 - WaveHeader.NumChannels; else if (WaveHeader.NumChannels <= 18) loc_config.channel_mask = (1 << WaveHeader.NumChannels) - 1; else loc_config.channel_mask = 0x3ffff; } else { loc_config.channel_mask = WaveHeader.ChannelMask; } if (format == 3) { loc_config.float_norm_exp = 127; } else if (adobe_mode && WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) { if (WaveHeader.BitsPerSample == 24) loc_config.float_norm_exp = 127 + 23; else if (WaveHeader.BitsPerSample == 32) loc_config.float_norm_exp = 127 + 15; } } else if (strncmp(chunk_header.ckID, "data", 4) == 0) { // on the data chunk, get size and exit loop if (!WaveHeader.NumChannels) { // make sure we saw a "fmt" chunk... WavpackCloseFile(wpc); return (0); } if (len && len - chunk_header.ckSize > 16777216) { WavpackCloseFile(wpc); return (0); } total_samples = chunk_header.ckSize / WaveHeader.BlockAlign; if (!total_samples) { WavpackCloseFile(wpc); return (0); } loc_config.bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels; loc_config.num_channels = WaveHeader.NumChannels; loc_config.sample_rate = WaveHeader.SampleRate; break; } else { // just copy unknown chunks to output int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L; uchar_t *rbuf; rbuf = NULL; // Silence compiler if (!read_block_ref(&rd_dat, &rbuf, bytes_to_copy, &bcount) || bcount != bytes_to_copy || !WavpackAddWrapper(wpc, rbuf, bytes_to_copy)) { WavpackCloseFile(wpc); return (0); } } } if (!WavpackSetConfiguration(wpc, &loc_config, total_samples)) { WavpackCloseFile(wpc); return (0); } // pack the audio portion of the data now; result = pack_audio(wpc, &rd_dat); /* * if everything went well (and we're not ignoring length) try to read * anything else that might be appended to the audio data and write that * to the WavPack metadata as "wrapper" */ if (result) { uchar_t *buff; buff = NULL; // Silence compiler while (read_block_ref(&rd_dat, &buff, 16, &bcount) && bcount) { if (!WavpackAddWrapper (wpc, buff, bcount)) { WavpackCloseFile(wpc); return (0); } } } // we're now done with any WavPack blocks, so flush any remaining data if (result && !WavpackFlushSamples (wpc)) { WavpackCloseFile(wpc); return (0); } WavpackCloseFile(wpc); return (wv_dat.bytes_written); } /* * Reformat samples from longs in processor's native endian mode to * little-endian data with (possibly) less than 4 bytes / sample. */ static unsigned char * format_samples(int bps, unsigned char *dst, int32_t *src, uint32_t samcnt) { int32_t temp; switch (bps) { case 1: while (samcnt--) *dst++ = *src++ + 128; break; case 2: while (samcnt--) { *dst++ = (unsigned char) (temp = *src++); *dst++ = (unsigned char) (temp >> 8); } break; case 3: while (samcnt--) { *dst++ = (unsigned char) (temp = *src++); *dst++ = (unsigned char) (temp >> 8); *dst++ = (unsigned char) (temp >> 16); } break; case 4: while (samcnt--) { *dst++ = (unsigned char) (temp = *src++); *dst++ = (unsigned char) (temp >> 8); *dst++ = (unsigned char) (temp >> 16); *dst++ = (unsigned char) (temp >> 24); } break; } return dst; } size_t wavpack_filter_decode(uchar_t *in_buf, size_t len, uchar_t **out_buf, ssize_t out_len) { write_data wr_dat; read_data rd_dat; WavpackContext *wpc; int bytes_per_sample, num_channels, bps, result; int32_t *temp_buffer; uchar_t *output_ptr = NULL, *output_buffer = NULL; uint32_t total_unpacked_samples = 0, output_buffer_size = 0; char error[80]; rd_dat.buf = in_buf; rd_dat.bufsize = len; rd_dat.bytes_read = 0; wpc = WavpackOpenFileInputEx(&memreader, &rd_dat, NULL, error, OPEN_WRAPPER, 0); if (!wpc) { log_msg(LOG_ERR, 0, error); return (0); } num_channels = WavpackGetNumChannels(wpc); bps = WavpackGetBytesPerSample(wpc); bytes_per_sample = num_channels * bps; *out_buf = (uchar_t *)malloc(out_len); if (*out_buf == NULL) { log_msg(LOG_ERR, 1, "malloc failed."); return (0); } /* * Must start with a wrapper. */ wr_dat.buf = *out_buf; wr_dat.bufsize = len; wr_dat.bytes_written = 0; wr_dat.first_block_size = 0; wr_dat.error = 0; if (WavpackGetWrapperBytes(wpc)) { if (!write_block(&wr_dat, WavpackGetWrapperData(wpc), WavpackGetWrapperBytes(wpc))) { WavpackFreeWrapper(wpc); WavpackCloseFile(wpc); log_msg(LOG_ERR, 0, "Wavpack: Header write failed."); return (0); } WavpackFreeWrapper(wpc); } else { log_msg(LOG_ERR, 0, "Wavpack: RIFF wrapper size is zero. File corrupt?"); WavpackCloseFile(wpc); return (0); } result = TRUE; temp_buffer = malloc (4096L * num_channels * 4); output_buffer_size = 1024 * 256; output_buffer = malloc(output_buffer_size); output_ptr = output_buffer; while (result) { uint32_t samples_to_unpack, samples_unpacked; samples_to_unpack = (output_buffer_size - (uint32_t)(output_ptr - output_buffer)) / bytes_per_sample; if (samples_to_unpack > 4096) samples_to_unpack = 4096; samples_unpacked = WavpackUnpackSamples(wpc, temp_buffer, samples_to_unpack); total_unpacked_samples += samples_unpacked; if (samples_unpacked) { output_ptr = format_samples(bps, output_ptr, temp_buffer, samples_unpacked * num_channels); } if (!samples_unpacked || (output_buffer_size - (output_ptr - output_buffer)) < (uint32_t)bytes_per_sample) { if (!write_block(&wr_dat, output_buffer, (uint32_t)(output_ptr - output_buffer))) { if (temp_buffer) free(temp_buffer); WavpackCloseFile(wpc); log_msg(LOG_ERR, 0, "Wavpack: Writing samples failed."); return (0); } output_ptr = output_buffer; } if (!samples_unpacked) break; } if (output_buffer) free(output_buffer); while (WavpackGetWrapperBytes(wpc)) { if (!write_block(&wr_dat, WavpackGetWrapperData(wpc), WavpackGetWrapperBytes(wpc))) { if (temp_buffer) free(temp_buffer); WavpackCloseFile(wpc); log_msg(LOG_ERR, 0, "Wavpack: Writing trailing data failed."); return (0); } WavpackFreeWrapper(wpc); WavpackUnpackSamples(wpc, temp_buffer, 1); // perhaps there's more RIFF info... } free(temp_buffer); WavpackCloseFile(wpc); return (wr_dat.bytes_written); } #ifdef __cplusplus } #endif #endif /* _ENABLE_WAVPACK_ */