388 lines
14 KiB
C
388 lines
14 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2013 Conifer Software. //
|
|
// All Rights Reserved. //
|
|
// Distributed under the BSD Software License (see license.txt) //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// unpack_seek.c
|
|
|
|
// This module provides the high-level API for unpacking audio data from
|
|
// a specific sample index (i.e., seeking).
|
|
|
|
#ifndef NO_SEEKING
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "wavpack_local.h"
|
|
|
|
///////////////////////////// executable code ////////////////////////////////
|
|
|
|
static int64_t find_sample (WavpackContext *wpc, void *infile, int64_t header_pos, int64_t sample);
|
|
|
|
// Seek to the specified 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. After a
|
|
// FALSE return the file should not be accessed again (other than to close
|
|
// it); this is a fatal error.
|
|
|
|
int WavpackSeekSample (WavpackContext *wpc, uint32_t sample)
|
|
{
|
|
return WavpackSeekSample64 (wpc, sample);
|
|
}
|
|
|
|
int WavpackSeekSample64 (WavpackContext *wpc, int64_t sample)
|
|
{
|
|
WavpackStream *wps = wpc->streams ? wpc->streams [wpc->current_stream = 0] : NULL;
|
|
uint32_t bcount, samples_to_skip, samples_to_decode = 0;
|
|
int32_t *buffer;
|
|
|
|
if (wpc->total_samples == -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 ENABLE_LEGACY
|
|
if (wpc->stream3)
|
|
return seek_sample3 (wpc, (uint32_t) sample);
|
|
#endif
|
|
|
|
#ifdef ENABLE_DSD
|
|
if (wpc->decimation_context) { // the decimation code needs some context to be sample accurate
|
|
if (sample < 16) {
|
|
samples_to_decode = (uint32_t) sample;
|
|
sample = 0;
|
|
}
|
|
else {
|
|
samples_to_decode = 16;
|
|
sample -= 16;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || sample < GET_BLOCK_INDEX (wps->wphdr) ||
|
|
sample >= GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples) {
|
|
|
|
free_streams (wpc);
|
|
wpc->filepos = find_sample (wpc, wpc->wv_in, wpc->filepos, sample);
|
|
|
|
if (wpc->filepos == -1)
|
|
return FALSE;
|
|
|
|
if (wpc->wvc_flag) {
|
|
wpc->file2pos = find_sample (wpc, wpc->wvc_in, 0, sample);
|
|
|
|
if (wpc->file2pos == -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));
|
|
WavpackLittleEndianToNative (&wps->wphdr, WavpackHeaderFormat);
|
|
|
|
if ((wps->wphdr.ckSize & 1) || wps->wphdr.ckSize < 24 || wps->wphdr.ckSize >= 1024 * 1024) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wps->blockbuff = (unsigned char *)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;
|
|
}
|
|
|
|
// render corrupt blocks harmless
|
|
if (!WavpackVerifySingleBlock (wps->blockbuff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
|
|
wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
|
|
wps->wphdr.block_samples = 0;
|
|
memcpy (wps->blockbuff, &wps->wphdr, 32);
|
|
}
|
|
|
|
SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);
|
|
memcpy (wps->blockbuff, &wps->wphdr, sizeof (WavpackHeader));
|
|
wps->init_done = 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));
|
|
WavpackLittleEndianToNative (&wps->wphdr, WavpackHeaderFormat);
|
|
|
|
if ((wps->wphdr.ckSize & 1) || wps->wphdr.ckSize < 24 || wps->wphdr.ckSize >= 1024 * 1024) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wps->block2buff = (unsigned char *)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;
|
|
}
|
|
|
|
// render corrupt blocks harmless
|
|
if (!WavpackVerifySingleBlock (wps->block2buff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
|
|
wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
|
|
wps->wphdr.block_samples = 0;
|
|
memcpy (wps->block2buff, &wps->wphdr, 32);
|
|
}
|
|
|
|
SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);
|
|
memcpy (wps->block2buff, &wps->wphdr, sizeof (WavpackHeader));
|
|
}
|
|
|
|
if (!wps->init_done && !unpack_init (wpc)) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wps->init_done = TRUE;
|
|
}
|
|
|
|
while (!wpc->reduced_channels && !(wps->wphdr.flags & FINAL_BLOCK)) {
|
|
if (++wpc->current_stream == wpc->num_streams) {
|
|
|
|
if (wpc->num_streams == wpc->max_streams) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wpc->streams = (WavpackStream **)realloc (wpc->streams, (wpc->num_streams + 1) * sizeof (wpc->streams [0]));
|
|
wps = wpc->streams [wpc->num_streams++] = (WavpackStream *)malloc (sizeof (WavpackStream));
|
|
CLEAR (*wps);
|
|
bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr);
|
|
|
|
if (bcount == (uint32_t) -1) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wps->blockbuff = (unsigned char *)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) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
// render corrupt blocks harmless
|
|
if (!WavpackVerifySingleBlock (wps->blockbuff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
|
|
wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
|
|
wps->wphdr.block_samples = 0;
|
|
memcpy (wps->blockbuff, &wps->wphdr, 32);
|
|
}
|
|
|
|
wps->init_done = FALSE;
|
|
|
|
if (wpc->wvc_flag && !read_wvc_block (wpc)) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!wps->init_done && !unpack_init (wpc)) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
wps->init_done = TRUE;
|
|
}
|
|
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;
|
|
else
|
|
wpc->streams [wpc->current_stream]->init_done = TRUE;
|
|
}
|
|
|
|
samples_to_skip = (uint32_t) (sample - wps->sample_index);
|
|
|
|
if (samples_to_skip > 131072) {
|
|
free_streams (wpc);
|
|
return FALSE;
|
|
}
|
|
|
|
if (samples_to_skip) {
|
|
buffer = (int32_t *)malloc (samples_to_skip * 8);
|
|
|
|
for (wpc->current_stream = 0; wpc->current_stream < wpc->num_streams; wpc->current_stream++)
|
|
#ifdef ENABLE_DSD
|
|
if (wpc->streams [wpc->current_stream]->wphdr.flags & DSD_FLAG)
|
|
unpack_dsd_samples (wpc, buffer, samples_to_skip);
|
|
else
|
|
#endif
|
|
unpack_samples (wpc, buffer, samples_to_skip);
|
|
|
|
free (buffer);
|
|
}
|
|
|
|
wpc->current_stream = 0;
|
|
|
|
#ifdef ENABLE_DSD
|
|
if (wpc->decimation_context)
|
|
decimate_dsd_reset (wpc->decimation_context);
|
|
|
|
if (samples_to_decode) {
|
|
buffer = (int32_t *)malloc (samples_to_decode * wpc->config.num_channels * 4);
|
|
|
|
if (buffer) {
|
|
WavpackUnpackSamples (wpc, buffer, samples_to_decode);
|
|
free (buffer);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// 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 int64_t find_header (WavpackStreamReader64 *reader, void *id, int64_t filepos, WavpackHeader *wphdr)
|
|
{
|
|
unsigned char *buffer = (unsigned char *)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 = (int)(ep - sp);
|
|
memmove (buffer, sp, bleft);
|
|
ep -= (sp - buffer);
|
|
sp = buffer;
|
|
}
|
|
else {
|
|
if (sp > ep)
|
|
if (reader->set_pos_rel (id, (int32_t)(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 [2] || sp [1] || *sp >= 24) && sp [5] == 4 &&
|
|
sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff) && sp [18] < 3 && !sp [19]) {
|
|
memcpy (wphdr, sp - 4, sizeof (*wphdr));
|
|
WavpackLittleEndianToNative (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 int64_t find_sample (WavpackContext *wpc, void *infile, int64_t header_pos, int64_t sample)
|
|
{
|
|
WavpackStream *wps = wpc->streams [wpc->current_stream];
|
|
int64_t file_pos1 = 0, file_pos2 = wpc->reader->get_length (infile);
|
|
int64_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 (GET_BLOCK_INDEX (wps->wphdr) > sample) {
|
|
sample_pos2 = GET_BLOCK_INDEX (wps->wphdr);
|
|
file_pos2 = header_pos;
|
|
}
|
|
else if (GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples <= sample) {
|
|
sample_pos1 = GET_BLOCK_INDEX (wps->wphdr);
|
|
file_pos1 = header_pos;
|
|
}
|
|
else
|
|
return header_pos;
|
|
}
|
|
|
|
while (1) {
|
|
double bytes_per_sample;
|
|
int64_t seek_pos;
|
|
|
|
bytes_per_sample = (double) file_pos2 - file_pos1;
|
|
bytes_per_sample /= sample_pos2 - sample_pos1;
|
|
seek_pos = file_pos1 + (file_skip ? 32 : 0);
|
|
seek_pos += (int64_t)(bytes_per_sample * (sample - sample_pos1) * ratio);
|
|
seek_pos = find_header (wpc->reader, infile, seek_pos, &wps->wphdr);
|
|
|
|
if (seek_pos != (int64_t) -1)
|
|
SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);
|
|
|
|
if (seek_pos == (int64_t) -1 || seek_pos >= file_pos2) {
|
|
if (ratio > 0.0) {
|
|
if ((ratio -= 0.24) < 0.0)
|
|
ratio = 0.0;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
else if (GET_BLOCK_INDEX (wps->wphdr) > sample) {
|
|
sample_pos2 = GET_BLOCK_INDEX (wps->wphdr);
|
|
file_pos2 = seek_pos;
|
|
}
|
|
else if (GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples <= sample) {
|
|
|
|
if (seek_pos == file_pos1)
|
|
file_skip = 1;
|
|
else {
|
|
sample_pos1 = GET_BLOCK_INDEX (wps->wphdr);
|
|
file_pos1 = seek_pos;
|
|
}
|
|
}
|
|
else
|
|
return seek_pos;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|