pcompress/archive/wavpack_helper.c
Moinak Ghosh e7081eb5a3 Git commit - rehash. Incorrect earlier commit.
Implement Separate metadata stream.
Fix blatant wrong check in Bzip2 compressor.
Implement E8E9 filter fallback in Dispack.
Improve dict buffer size checks.
Reduce thread count to control memory usage in archive mode.
2014-10-24 23:30:40 +05:30

679 lines
16 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* moinakg@belenix.org, http://moinakg.wordpress.com/
*
*/
/*
*/
#ifdef _ENABLE_WAVPACK_
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <utils.h>
#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_ */