316 lines
11 KiB
C
316 lines
11 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2019 David Bryant. //
|
|
// All Rights Reserved. //
|
|
// Distributed under the BSD Software License (see license.txt) //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// open_raw.c
|
|
|
|
// This code provides the ability to decode WavPack frames directly from
|
|
// memory for use in a streaming application. It can handle full blocks
|
|
// or the headerless block data provided by Matroska and the DirectShow
|
|
// WavPack splitter. For information about how Matroska stores WavPack,
|
|
// see: https://www.matroska.org/technical/specs/codecid/wavpack.html
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "wavpack_local.h"
|
|
|
|
typedef struct {
|
|
unsigned char *sptr, *dptr, *eptr, free_required;
|
|
} RawSegment;
|
|
|
|
typedef struct {
|
|
RawSegment *segments;
|
|
int num_segments, curr_segment;
|
|
unsigned char ungetc_char, ungetc_flag;
|
|
} WavpackRawContext;
|
|
|
|
static int32_t raw_read_bytes (void *id, void *data, int32_t bcount)
|
|
{
|
|
WavpackRawContext *rcxt = id;
|
|
unsigned char *outptr = data;
|
|
|
|
while (bcount) {
|
|
if (rcxt->ungetc_flag) {
|
|
*outptr++ = rcxt->ungetc_char;
|
|
rcxt->ungetc_flag = 0;
|
|
bcount--;
|
|
}
|
|
else if (rcxt->curr_segment < rcxt->num_segments) {
|
|
RawSegment *segptr = rcxt->segments + rcxt->curr_segment;
|
|
int bytes_to_copy = (int)(segptr->eptr - segptr->dptr);
|
|
|
|
if (bytes_to_copy > bcount)
|
|
bytes_to_copy = bcount;
|
|
|
|
memcpy (outptr, segptr->dptr, bytes_to_copy);
|
|
outptr += bytes_to_copy;
|
|
bcount -= bytes_to_copy;
|
|
|
|
if ((segptr->dptr += bytes_to_copy) == segptr->eptr)
|
|
rcxt->curr_segment++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
return (int32_t)(outptr - (unsigned char *) data);
|
|
}
|
|
|
|
static int32_t raw_write_bytes (void *id, void *data, int32_t bcount)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int64_t raw_get_pos (void *id)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int raw_set_pos_abs (void *id, int64_t pos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int raw_set_pos_rel (void *id, int64_t delta, int mode)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int raw_push_back_byte (void *id, int c)
|
|
{
|
|
WavpackRawContext *rcxt = id;
|
|
rcxt->ungetc_char = c;
|
|
rcxt->ungetc_flag = 1;
|
|
return c;
|
|
}
|
|
|
|
static int64_t raw_get_length (void *id)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int raw_can_seek (void *id)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int raw_close_stream (void *id)
|
|
{
|
|
WavpackRawContext *rcxt = id;
|
|
int i;
|
|
|
|
if (rcxt) {
|
|
for (i = 0; i < rcxt->num_segments; ++i)
|
|
if (rcxt->segments [i].sptr && rcxt->segments [i].free_required)
|
|
free (rcxt->segments [i].sptr);
|
|
|
|
if (rcxt->segments) free (rcxt->segments);
|
|
free (rcxt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static WavpackStreamReader64 raw_reader = {
|
|
raw_read_bytes, raw_write_bytes, raw_get_pos, raw_set_pos_abs, raw_set_pos_rel,
|
|
raw_push_back_byte, raw_get_length, raw_can_seek, NULL, raw_close_stream
|
|
};
|
|
|
|
// This function is similar to WavpackOpenFileInput() except that instead of
|
|
// providing a filename to open, the caller provides pointers to buffered
|
|
// WavPack frames (both standard and, optionally, correction data). It
|
|
// decodes only a single frame. Note that in this context, a "frame" is a
|
|
// collection of WavPack blocks that represent all the channels present. In
|
|
// the case of mono or [most] stereo streams, this is the same thing, but
|
|
// for multichannel streams each frame consists of several WavPack blocks
|
|
// (which can contain only 1 or 2 channels).
|
|
|
|
WavpackContext *WavpackOpenRawDecoder (
|
|
void *main_data, int32_t main_size,
|
|
void *corr_data, int32_t corr_size,
|
|
int16_t version, char *error, int flags, int norm_offset)
|
|
{
|
|
WavpackRawContext *raw_wv = NULL, *raw_wvc = NULL;
|
|
|
|
// if the WavPack data does not contain headers we assume Matroska-style storage
|
|
// and recreate the missing headers
|
|
|
|
if (strncmp (main_data, "wvpk", 4)) {
|
|
uint32_t multiple_blocks = 0, block_size, block_samples = 0, wphdr_flags, crc;
|
|
uint32_t main_bytes = main_size, corr_bytes = corr_size;
|
|
unsigned char *mcp = main_data;
|
|
unsigned char *ccp = corr_data;
|
|
int msi = 0, csi = 0;
|
|
|
|
raw_wv = malloc (sizeof (WavpackRawContext));
|
|
memset (raw_wv, 0, sizeof (WavpackRawContext));
|
|
|
|
if (corr_data && corr_size) {
|
|
raw_wvc = malloc (sizeof (WavpackRawContext));
|
|
memset (raw_wvc, 0, sizeof (WavpackRawContext));
|
|
}
|
|
|
|
while (main_bytes >= 12) {
|
|
if (!msi) {
|
|
block_samples = *mcp++;
|
|
block_samples += *mcp++ << 8;
|
|
block_samples += *mcp++ << 16;
|
|
block_samples += *mcp++ << 24;
|
|
main_bytes -= 4;
|
|
}
|
|
|
|
wphdr_flags = *mcp++;
|
|
wphdr_flags += *mcp++ << 8;
|
|
wphdr_flags += *mcp++ << 16;
|
|
wphdr_flags += *mcp++ << 24;
|
|
main_bytes -= 4;
|
|
|
|
// if the first block does not have the FINAL_BLOCK flag set,
|
|
// then there are multiple blocks
|
|
|
|
if (!msi && !(wphdr_flags & FINAL_BLOCK))
|
|
multiple_blocks = 1;
|
|
|
|
crc = *mcp++;
|
|
crc += *mcp++ << 8;
|
|
crc += *mcp++ << 16;
|
|
crc += *mcp++ << 24;
|
|
main_bytes -= 4;
|
|
|
|
if (multiple_blocks) {
|
|
block_size = *mcp++;
|
|
block_size += *mcp++ << 8;
|
|
block_size += *mcp++ << 16;
|
|
block_size += *mcp++ << 24;
|
|
main_bytes -= 4;
|
|
}
|
|
else
|
|
block_size = main_bytes;
|
|
|
|
if (block_size > main_bytes) {
|
|
if (error) strcpy (error, "main block overran available data!");
|
|
raw_close_stream (raw_wv);
|
|
raw_close_stream (raw_wvc);
|
|
return NULL;
|
|
}
|
|
else {
|
|
WavpackHeader *wphdr = malloc (sizeof (WavpackHeader));
|
|
memset (wphdr, 0, sizeof (WavpackHeader));
|
|
memcpy (wphdr->ckID, "wvpk", 4);
|
|
wphdr->ckSize = sizeof (WavpackHeader) - 8 + block_size;
|
|
SET_TOTAL_SAMPLES (*wphdr, block_samples);
|
|
wphdr->block_samples = block_samples;
|
|
wphdr->version = version;
|
|
wphdr->flags = wphdr_flags;
|
|
wphdr->crc = crc;
|
|
WavpackLittleEndianToNative (wphdr, WavpackHeaderFormat);
|
|
|
|
raw_wv->num_segments += 2;
|
|
raw_wv->segments = realloc (raw_wv->segments, sizeof (RawSegment) * raw_wv->num_segments);
|
|
raw_wv->segments [msi].dptr = raw_wv->segments [msi].sptr = (unsigned char *) wphdr;
|
|
raw_wv->segments [msi].eptr = raw_wv->segments [msi].dptr + sizeof (WavpackHeader);
|
|
raw_wv->segments [msi++].free_required = 1;
|
|
raw_wv->segments [msi].dptr = raw_wv->segments [msi].sptr = mcp;
|
|
raw_wv->segments [msi].eptr = raw_wv->segments [msi].dptr + block_size;
|
|
raw_wv->segments [msi++].free_required = 0;
|
|
main_bytes -= block_size;
|
|
mcp += block_size;
|
|
}
|
|
|
|
if (corr_data && corr_bytes >= 4) {
|
|
crc = *ccp++;
|
|
crc += *ccp++ << 8;
|
|
crc += *ccp++ << 16;
|
|
crc += *ccp++ << 24;
|
|
corr_bytes -= 4;
|
|
|
|
if (multiple_blocks) {
|
|
block_size = *ccp++;
|
|
block_size += *ccp++ << 8;
|
|
block_size += *ccp++ << 16;
|
|
block_size += *ccp++ << 24;
|
|
corr_bytes -= 4;
|
|
}
|
|
else
|
|
block_size = corr_bytes;
|
|
|
|
if (block_size > corr_bytes) {
|
|
if (error) strcpy (error, "correction block overran available data!");
|
|
raw_close_stream (raw_wv);
|
|
raw_close_stream (raw_wvc);
|
|
return NULL;
|
|
}
|
|
else {
|
|
WavpackHeader *wphdr = malloc (sizeof (WavpackHeader));
|
|
memset (wphdr, 0, sizeof (WavpackHeader));
|
|
memcpy (wphdr->ckID, "wvpk", 4);
|
|
wphdr->ckSize = sizeof (WavpackHeader) - 8 + block_size;
|
|
SET_TOTAL_SAMPLES (*wphdr, block_samples);
|
|
wphdr->block_samples = block_samples;
|
|
wphdr->version = version;
|
|
wphdr->flags = wphdr_flags;
|
|
wphdr->crc = crc;
|
|
WavpackLittleEndianToNative (wphdr, WavpackHeaderFormat);
|
|
|
|
raw_wvc->num_segments += 2;
|
|
raw_wvc->segments = realloc (raw_wvc->segments, sizeof (RawSegment) * raw_wvc->num_segments);
|
|
raw_wvc->segments [csi].dptr = raw_wvc->segments [csi].sptr = (unsigned char *) wphdr;
|
|
raw_wvc->segments [csi].eptr = raw_wvc->segments [csi].dptr + sizeof (WavpackHeader);
|
|
raw_wvc->segments [csi++].free_required = 1;
|
|
raw_wvc->segments [csi].dptr = raw_wvc->segments [csi].sptr = ccp;
|
|
raw_wvc->segments [csi].eptr = raw_wvc->segments [csi].dptr + block_size;
|
|
raw_wvc->segments [csi++].free_required = 0;
|
|
corr_bytes -= block_size;
|
|
ccp += block_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (main_bytes || (corr_data && corr_bytes)) {
|
|
if (error) strcpy (error, "leftover multiblock data!");
|
|
raw_close_stream (raw_wv);
|
|
raw_close_stream (raw_wvc);
|
|
return NULL;
|
|
}
|
|
}
|
|
else { // the case of WavPack blocks with headers is much easier...
|
|
if (main_data) {
|
|
raw_wv = malloc (sizeof (WavpackRawContext));
|
|
memset (raw_wv, 0, sizeof (WavpackRawContext));
|
|
raw_wv->num_segments = 1;
|
|
raw_wv->segments = malloc (sizeof (RawSegment) * raw_wv->num_segments);
|
|
raw_wv->segments [0].dptr = raw_wv->segments [0].sptr = main_data;
|
|
raw_wv->segments [0].eptr = raw_wv->segments [0].dptr + main_size;
|
|
raw_wv->segments [0].free_required = 0;
|
|
}
|
|
|
|
if (corr_data && corr_size) {
|
|
raw_wvc = malloc (sizeof (WavpackRawContext));
|
|
memset (raw_wvc, 0, sizeof (WavpackRawContext));
|
|
raw_wvc->num_segments = 1;
|
|
raw_wvc->segments = malloc (sizeof (RawSegment) * raw_wvc->num_segments);
|
|
raw_wvc->segments [0].dptr = raw_wvc->segments [0].sptr = corr_data;
|
|
raw_wvc->segments [0].eptr = raw_wvc->segments [0].dptr + corr_size;
|
|
raw_wvc->segments [0].free_required = 0;
|
|
}
|
|
}
|
|
|
|
return WavpackOpenFileInputEx64 (&raw_reader, raw_wv, raw_wvc, error, flags | OPEN_STREAMING | OPEN_NO_CHECKSUM, norm_offset);
|
|
}
|
|
|
|
// Return the number of samples represented by the current (and in the raw case, only) frame.
|
|
|
|
uint32_t WavpackGetNumSamplesInFrame (WavpackContext *wpc)
|
|
{
|
|
if (wpc && wpc->streams && wpc->streams [0])
|
|
return wpc->streams [0]->wphdr.block_samples;
|
|
else
|
|
return -1;
|
|
}
|
|
|