440 lines
14 KiB
ObjectPascal

(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower Abbrevia
*
* The Initial Developer of the Original Code is Craig Peterson
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Craig Peterson <capeterson@users.sourceforge.net>
*
* ***** END LICENSE BLOCK ***** *)
{*********************************************************}
{* ABBREVIA: AbWavPack.pas *}
{*********************************************************}
{* ABBREVIA: WavPack decompression procedures *}
{*********************************************************}
unit AbWavPack;
{$I AbDefine.inc}
interface
uses
Classes;
// Decompress a WavPack compressed stream from aSrc and write to aDes.
// aSrc must not allow reads past the compressed data.
procedure DecompressWavPack(aSrc, aDes: TStream);
implementation
uses
AbCrtl,
Math,
SysUtils;
// Compile using
// bcc32 -DWIN32 -DNO_USE_FSTREAMS -c -w-8004 -w-8012 -w-8017 -w-8057 -w-8065 *.c
//
// In wavpack_local.h remove the line "#define FASTCALL __fastcall"
{ C runtime library ======================================================== }
function fabs(x: Double): Double; cdecl;
begin
if x < 0 then Result := -1
else Result := x
end;
function floor(x: Double): Integer; cdecl;
begin
Result := Floor(x);
end;
function labs(n: Integer): Integer; cdecl;
begin
if n < 0 then Result := -n
else Result := n;
end;
function _stricmp(str1, str2: PAnsiChar): Integer; cdecl;
external 'msvcrt.dll' name '_stricmp';
function strncmp(str1, str2: PAnsiChar; num: Integer): Integer; cdecl;
external 'msvcrt.dll' {$IFDEF BCB}name '_strncmp'{$ENDIF};
{ Forward declarations ===================================================== }
// bits.c
procedure bs_open_read; external;
procedure bs_close_read; external;
procedure bs_open_write; external;
procedure bs_close_write; external;
procedure little_endian_to_native; external;
procedure native_to_little_endian; external;
// extra1.c
procedure execute_mono; external;
// extra2.c
procedure execute_stereo; external;
// float.c
procedure float_values; external;
procedure read_float_info; external;
procedure scan_float_data; external;
procedure send_float_data; external;
procedure WavpackFloatNormalize; external;
procedure write_float_info; external;
// metadata.c
procedure add_to_metadata; external;
procedure copy_metadata; external;
procedure free_metadata; external;
procedure process_metadata; external;
procedure read_metadata_buff; external;
procedure write_metadata_block; external;
// pack.c
procedure pack_block; external;
procedure pack_init; external;
// tags.c
procedure load_tag; external;
procedure valid_tag; external;
// unpack.c
procedure check_crc_error; external;
procedure free_tag; external;
procedure unpack_init; external;
procedure unpack_samples; external;
// unpack3.c
procedure free_stream3; external;
procedure get_version3; external;
procedure get_sample_index3; external;
procedure open_file3; external;
procedure seek_sample3; external;
procedure unpack_samples3; external;
// words.c
procedure exp2s; external;
procedure flush_word; external;
procedure get_word; external;
procedure get_words_lossless; external;
procedure init_words; external;
procedure log2s; external;
procedure log2buffer; external;
procedure nosend_word; external;
procedure read_hybrid_profile; external;
procedure read_entropy_vars; external;
procedure restore_weight; external;
procedure scan_word; external;
procedure send_word; external;
procedure send_words_lossless; external;
procedure store_weight; external;
procedure write_entropy_vars; external;
procedure write_hybrid_profile; external;
{ Linker derectives ======================================================== }
{$IF DEFINED(WIN32)}
{$L Win32\wv_bits.obj}
{$L Win32\wv_extra1.obj}
{$L Win32\wv_extra2.obj}
{$L Win32\wv_float.obj}
{$L Win32\wv_metadata.obj}
{$L Win32\wv_pack.obj}
{$L Win32\wv_tags.obj}
{$L Win32\wv_unpack.obj}
{$L Win32\wv_unpack3.obj}
{$L Win32\wv_words.obj}
{$L Win32\wv_wputils.obj}
{$ELSEIF DEFINED(WIN64)}
{$L Win64\wv_bits.obj}
{$L Win64\wv_extra1.obj}
{$L Win64\wv_extra2.obj}
{$L Win64\wv_float.obj}
{$L Win64\wv_metadata.obj}
{$L Win64\wv_pack.obj}
{$L Win64\wv_tags.obj}
{$L Win64\wv_unpack.obj}
{$L Win64\wv_unpack3.obj}
{$L Win64\wv_words.obj}
{$L Win64\wv_wputils.obj}
{$IFEND}
{ wavpack_local.h ========================================================== }
const
OPEN_WVC = $1; // open/read "correction" file
OPEN_TAGS = $2; // read ID3v1 / APEv2 tags (seekable file)
OPEN_WRAPPER = $4; // make audio wrapper available (i.e. RIFF)
OPEN_2CH_MAX = $8; // open multichannel as stereo (no downmix)
OPEN_NORMALIZE = $10; // normalize floating point data to +/- 1.0
OPEN_STREAMING = $20; // "streaming" mode blindly unpacks blocks
// w/o regard to header file position info
OPEN_EDIT_TAGS = $40; // allow editing of tags
type
int32_t = LongInt;
uint32_t = LongWord;
WavpackStreamReader = record
read_bytes: function(id, data: Pointer; bcount: int32_t): int32_t; cdecl;
get_pos: function(id: Pointer): uint32_t; cdecl;
set_pos_abs: function(id: Pointer; pos: uint32_t): Integer; cdecl;
set_pos_rel: function(id: Pointer; delta: int32_t; mode: Integer): Integer; cdecl;
push_back_byte: function(id: Pointer; c: Integer): Integer; cdecl;
get_length: function(id: Pointer): uint32_t; cdecl;
can_seek: function(id: Pointer): Integer; cdecl;
write_bytes: function(id, data: Pointer; bcount: int32_t): int32_t; cdecl;
end;
WavpackContext = Pointer;
{ wputils.c ================================================================ }
function WavpackOpenFileInputEx(const reader: WavpackStreamReader;
wv_id, wvc_id: Pointer; error: PAnsiChar; flags, norm_offset: Integer): WavpackContext;
cdecl; external;
function WavpackGetWrapperBytes(wpc: WavpackContext): uint32_t; cdecl; external;
function WavpackGetWrapperData(wpc: WavpackContext): PByte; cdecl; external;
procedure WavpackFreeWrapper (wpc: WavpackContext); cdecl; external;
procedure WavpackSeekTrailingWrapper(wpc: WavpackContext); cdecl; external;
function WavpackGetNumSamples(wpc: WavpackContext): uint32_t; cdecl; external;
function WavpackGetNumChannels(wpc: WavpackContext): Integer; cdecl; external;
function WavpackGetBytesPerSample (wpc: WavpackContext): Integer; cdecl; external;
function WavpackUnpackSamples(wpc: WavpackContext; buffer: Pointer;
samples: uint32_t): uint32_t; cdecl; external;
function WavpackCloseFile(wpc: WavpackContext): WavpackContext; cdecl; external;
{ TWavPackStream implementation ============================================ }
type
PWavPackStream = ^TWavPackStream;
TWavPackStream = record
HasPushedByte: Boolean;
PushedByte: Byte;
Stream: TStream;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_read_bytes(id, data: Pointer; bcount: int32_t): int32_t; cdecl;
begin
if PWavPackStream(id).HasPushedByte then begin
PByte(data)^ := PWavPackStream(id).PushedByte;
PWavPackStream(id).HasPushedByte := False;
Inc(PByte(data));
Dec(bcount);
if bcount = 0 then
Result := 1
else
Result := PWavPackStream(id).Stream.Read(data^, bcount) + 1;
end
else
Result := PWavPackStream(id).Stream.Read(data^, bcount);
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_get_pos(id: Pointer): uint32_t; cdecl;
begin
Result := PWavPackStream(id).Stream.Position;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_set_pos_abs(id: Pointer; pos: uint32_t): Integer; cdecl;
begin
PWavPackStream(id).Stream.Position := pos;
Result := 0;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_set_pos_rel(id: Pointer; delta: int32_t;
mode: Integer): Integer; cdecl;
begin
PWavPackStream(id).Stream.Seek(delta, mode);
Result := 1;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_push_back_byte(id: Pointer; c: Integer): Integer; cdecl;
begin
Assert(not PWavPackStream(id).HasPushedByte);
PWavPackStream(id).HasPushedByte := True;
PWavPackStream(id).PushedByte := Byte(c);
Result := 1;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_get_length(id: Pointer): uint32_t; cdecl;
begin
Result := PWavPackStream(id).Stream.Size;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_can_seek(id: Pointer): Integer; cdecl;
begin
Result := 1;
end;
{ -------------------------------------------------------------------------- }
function TWavPackStream_write_bytes(id, data: Pointer;
bcount: int32_t): int32_t; cdecl;
begin
Result := PWavPackStream(id).Stream.Write(data^, bcount);
end;
{ Decompression routines =================================================== }
{ -------------------------------------------------------------------------- }
// Reformat samples from longs in processor's native endian mode to
// little-endian data with (possibly) less than 4 bytes / sample.
//
// Based on wvunpack.c::format_samples.
// Conversions simplified since we only support little-endian processors
function FormatSamples(bps: Integer; dst, src: PByte; samcnt: uint32_t): PByte;
var
sample: LongWord;
begin
while samcnt > 0 do begin
Dec(samcnt);
// Get next sample
sample := PLongWord(src)^;
// Convert and write to output
case bps of
1: begin
dst^ := sample + 128;
end;
2: begin
PWord(dst)^ := sample;
end;
3: begin
PByteArray(dst)[0] := sample;
PByteArray(dst)[1] := sample shr 8;
PByteArray(dst)[2] := sample shr 16;
end;
4: begin
PLongWord(dst)^ := sample;
end;
end;
Inc(src, SizeOf(LongWord));
Inc(dst, bps);
end;
Result := dst;
end;
{ -------------------------------------------------------------------------- }
// Decompress a WavPack compressed stream from aSrc and write to aDes.
// aSrc must not allow reads past the compressed data.
//
// Based on wvunpack.c::unpack_file()
procedure DecompressWavPack(aSrc, aDes: TStream);
type
PtrInt = {$IF DEFINED(CPUX64)}Int64{$ELSE}LongInt{$IFEND};
const
OutputBufSize = 256 * 1024;
var
StreamReader: WavpackStreamReader;
Context: WavpackContext;
Src: TWavpackStream;
Error: array[0..79] of AnsiChar;
SamplesToUnpack, SamplesUnpacked: uint32_t;
NumChannels, bps, BytesPerSample: Integer;
OutputBuf, OutputPtr: PByte;
DecodeBuf: Pointer;
begin
OutputBuf := nil;
DecodeBuf := nil;
StreamReader.read_bytes := TWavPackStream_read_bytes;
StreamReader.get_pos := TWavPackStream_get_pos;
StreamReader.set_pos_abs := TWavPackStream_set_pos_abs;
StreamReader.set_pos_rel := TWavPackStream_set_pos_rel;
StreamReader.push_back_byte := TWavPackStream_push_back_byte;
StreamReader.get_length := TWavPackStream_get_length;
StreamReader.can_seek := TWavPackStream_can_seek;
StreamReader.write_bytes := TWavPackStream_write_bytes;
FillChar(Src, SizeOf(Src), 0);
Src.Stream := aSrc;
Context := WavpackOpenFileInputEx(StreamReader, @Src, nil, Error, OPEN_WRAPPER, 0);
if Context = nil then
raise Exception.Create('WavPack decompression failed: ' + Error);
try
// Write .wav header
if WavpackGetWrapperBytes(Context) > 0 then begin
aDes.WriteBuffer(WavpackGetWrapperData(Context)^, WavpackGetWrapperBytes(Context));
WavpackFreeWrapper(Context);
end;
NumChannels := WavpackGetNumChannels(Context);
bps := WavpackGetBytesPerSample(Context);
BytesPerSample := NumChannels * bps;
GetMem(OutputBuf, OutputBufSize);
OutputPtr := OutputBuf;
GetMem(DecodeBuf, 4096 * NumChannels * SizeOf(Integer));
repeat
// Unpack samples
SamplesToUnpack := (OutputBufSize - (PtrInt(OutputPtr) - PtrInt(OutputBuf))) div BytesPerSample;
if (SamplesToUnpack > 4096) then
SamplesToUnpack := 4096;
SamplesUnpacked := WavpackUnpackSamples(Context, DecodeBuf, SamplesToUnpack);
// Convert from 32-bit integers down to appriopriate bit depth
// and copy to output buffer.
if (SamplesUnpacked > 0) then
OutputPtr := FormatSamples(bps, OutputPtr, DecodeBuf,
SamplesUnpacked * uint32_t(NumChannels));
// Write output when it's full or when we're done
if (SamplesUnpacked = 0) or
((OutputBufSize - (PtrInt(OutputPtr) - PtrInt(OutputBuf))) < BytesPerSample) then begin
aDes.WriteBuffer(OutputBuf^, PtrInt(OutputPtr) - PtrInt(OutputBuf));
OutputPtr := OutputBuf;
end;
until (SamplesUnpacked = 0);
// Write .wav footer
while WavpackGetWrapperBytes(Context) > 0 do begin
try
aDes.WriteBuffer(WavpackGetWrapperData(Context)^,
WavpackGetWrapperBytes(Context));
finally
WavpackFreeWrapper(Context);
end;
// Check for more RIFF data
WavpackUnpackSamples (Context, DecodeBuf, 1);
end;
finally
if DecodeBuf <> nil then
FreeMemory(DecodeBuf);
if OutputBuf <> nil then
FreeMemory(OutputBuf);
WavpackCloseFile(Context);
end;
end;
end.