Merge pull request #162 from bnnm/schl-1snh

SCHl, 1SNh
This commit is contained in:
Christopher Snowhill 2017-12-23 17:15:52 -08:00 committed by GitHub
commit a818f7adfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 410 additions and 235 deletions

View File

@ -9,7 +9,7 @@
**MSVC / Visual Studio**: Microsoft's Visual C++ and MSBuild, bundled in either: **MSVC / Visual Studio**: Microsoft's Visual C++ and MSBuild, bundled in either:
- Visual Studio: https://www.visualstudio.com/downloads/ - Visual Studio: https://www.visualstudio.com/downloads/
- Visual Studio Community 2015 should work (free, but must register after trial period) - Visual Studio Community should work (free, but must register after trial period)
- Visual C++ Build Tools (no IDE): http://landinghub.visualstudio.com/visual-cpp-build-tools - Visual C++ Build Tools (no IDE): http://landinghub.visualstudio.com/visual-cpp-build-tools
**Git**: optional, to generate version numbers: **Git**: optional, to generate version numbers:
@ -22,7 +22,7 @@
**With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder. **With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder.
You need to manually rebuild if you change a *.h* file (use *make clean*). You need to manually rebuild if you change a *.h* file (use *make clean*).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but they aren't well prepared to generate native code at the moment. It should be fixable with some effort. In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort.
Windows CMD example for test.exe: Windows CMD example for test.exe:
``` ```
@ -39,7 +39,6 @@ mingw32-make.exe mingw_test -f Makefile ^
**With MSVC**: open *./vgmstream_full.sln* and compile in Visual Studio, or use MSBuild in the command line. See the foobar2000 section for dependencies and CMD examples. **With MSVC**: open *./vgmstream_full.sln* and compile in Visual Studio, or use MSBuild in the command line. See the foobar2000 section for dependencies and CMD examples.
### foobar2000 plugin (foo\_input\_vgmstream) ### foobar2000 plugin (foo\_input\_vgmstream)
Requires MSVC (foobar/SDK only links to MSVC C++ DLLs) and these dependencies: Requires MSVC (foobar/SDK only links to MSVC C++ DLLs) and these dependencies:
- foobar2000 SDK, in *(vgmstream)/../foobar/*: http://www.foobar2000.org/SDK - foobar2000 SDK, in *(vgmstream)/../foobar/*: http://www.foobar2000.org/SDK
@ -81,9 +80,9 @@ msbuild fb2k/foo_input_vgmstream.vcxproj ^
``` ```
### Audacious plugin ### Audacious plugin
Requires the dev version of Audacious (and dependencies), automake/autoconf, and gcc/make (C++11). Requires the dev version of Audacious (and dependencies), automake/autoconf, and gcc/make (C++11). It must be compiled and installed into Audacious, where it should appear in the plugin list as "vgmstream".
The plugin itself works with Audacious 3.5 or higher. New Audacious releases can break plugin compatibility so it may not work with the latest version unless adapted first. The plugin needs Audacious 3.5 or higher. New Audacious releases can break plugin compatibility so it may not work with the latest version unless adapted first.
FFmpeg and other external libraries aren't enabled, thus some formats are not supported. libvorbis and libmpg123 can be disabled with -DVGM_DISABLE_VORBIS and -DVGM_DISABLE_MPEG. FFmpeg and other external libraries aren't enabled, thus some formats are not supported. libvorbis and libmpg123 can be disabled with -DVGM_DISABLE_VORBIS and -DVGM_DISABLE_MPEG.
@ -128,10 +127,14 @@ git clean -fd
## Development ## Development
### Structure ### Code
vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins. vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins.
C is restricted to features VS2010 can understand. This mainly means means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in that block) and avoiding C99 like variable-length arrays (but others like // comments are fine). C should be restricted to features VS2010 understands. This mainly means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in the same block) and avoiding C99 features like variable-length arrays (but certain others like // comments are fine).
There are no hard coding rules but for consistency should follow general C conventions and the style used in most files: 4 spaces instead of tabs, underscore_and_lowercase_names, brackets starting in the same line (`if (..) { CRLF ... }`), etc. Some of the code may be a bit inefficient or duplicated at places, but it isn't much of a problem if gives clarity.
### Structure
``` ```
./ docs, scripts ./ docs, scripts
@ -149,37 +152,81 @@ C is restricted to features VS2010 can understand. This mainly means means decla
``` ```
### Overview ### Overview
vgmstream works by parsing a music stream header (*meta/*), preparing/demuxing data (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*). vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
Very simplified it goes like this: Very simplified it goes like this:
- player (test.exe, plugin, etc) opens a file stream *[plugin's main/decode]* - player (test.exe, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
- init tries all parsers (metas) until one works *[init_vgmstream]* - init tries all parsers (metas) until one works *[init_vgmstream]*
- parser reads header (channels, sample rate, loop points) and set ups a VGMSTREAM struct + layout/coding, if the format is correct *[init_vgmstream_(format-name)]* - parser reads header (channels, sample rate, loop points) and set ups the VGMSTREAM struct, if the format is correct *[init_vgmstream_(format-name)]*
- player gets total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]* - player finds total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]*
- player asks to fill a small sample buffer *[render_vgmstream]* - player asks to fill a small sample buffer *[render_vgmstream]*
- layout prepares byte offsets to read from the stream *[render_vgmstream_(layout)]* - layout prepares samples and offsets to read from the stream *[render_vgmstream_(layout)]*
- decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]* - decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]*
- player plays those samples, asks to fill sample buffer again, repeats (until total_samples) - player plays those samples, asks to fill sample buffer again, repeats (until total_samples)
- layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]* - layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]*
- close the VGMSTREAM once the stream is finished - player closes the VGMSTREAM once the stream is finished
The VGMSTREAM struct created during holds the stream's parameters and decoder state (such as file streams, or offsets per channel). ### Components
### Adding new formats #### STREAMFILEs
For new simple formats, assuming existing layout/coding: Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, and reads all needed info from the stream header and inits the VGMSTREAM
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases (ex. if data needs decryption, or a file is composed of multiple sub-files).
#### VGMSTREAM
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
#### metas
Metadata (header) parsers that identify and handle formats.
To add a new one:
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
- *src/meta/meta.h*: define parser's init - *src/meta/meta.h*: define parser's init
- *src/vgmstream.h*: define meta description in the meta_t list - *src/vgmstream.h*: define meta type in the meta_t list
- *src/vgmstream.c*: add parser init to the init list - *src/vgmstream.c*: add parser init to the init list
- *src/formats.c*: add new extension to the format list, add meta description - *src/formats.c*: add new extension to the format list, add meta type description
- *fb2k/foo_filetypes.h*: add new extension to the file register list (optional)
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS - *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif* - if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
The new meta is usually named after the format's header id or main extension, possibly with prepended platform. Each file should parse one format (regardless of accepted extensions or decoders used) for consistency, but variations can be found as code evolved. Differents formats can use the same extension, this is not a problem as long as the header id or some other validation tells them apart. If the format is headerless and the extension isn't unique enough it may need a generic GENH/TXTH header instead of direct support. Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
A STREAMFILE is passed to init_vgmstream_(format-name) function, and I/O must be done using its functions and not STDIO/FILEs, as this lets plugins do their own I/O. This includes reading data from the header or opening other STREAMFILEs (if the header has companion files that need to be parsed). It also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopening the base one, but could be any other file) to do I/O during decode, as well as setting the starting offsets of each channel and other values; this gives metas full flexibility at the cost of some repetition. The STREAMFILE passed to the meta will be discarded and its pointer must not be reused.
When a parser is successful (allocates VGMSTREAM and sets values) it also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopens the one passed, but could be any other file) to do I/O during decode. The STREAMFILE passed to the meta will be discarded and must not be reused. The .c file is usually named after the format's main extension or header id, optionally with affixes. Each file should parse one format and/or its variations (regardless of accepted extensions or decoders used) for consistency, but deviations may be found in the codebase. Sometimes a format is already parsed but not accepted due to bugs though.
If it supports subsongs it should read and handle the stream index (subsong number) in the passed STREAMFILE, and report the number of subsongs in the VGMSTREAM, to signal the plugins this feature. The index is 1-based (first subsong is 1, not 0). Different formats may use the same extension but this isn't a problem as long as the header id or some other validation tells them apart, and should be implemented in separate .c files. If the format is headerless and the extension isn't unique enough it probably needs a generic GENH/TXTH header instead of direct support.
If the format supports subsongs it should read the stream index (subsong number) in the passed STREAMFILE, and use it to parse a section of the file. Then it must report the number of subsongs in the VGMSTREAM, to signal this feature is enabled. The index is 1-based (first subsong is 1, 0 is default/first).
#### layouts
Layouts control most of the main logic:
- receive external buffer to fill with PCM samples
- detect when looping must be done
- find max number of samples to do next decoder call (usually one frame, less if loop starts/ends)
- call decoder
- do post-process if necessary (move offsets, check stuff, etc)
- repeat until buffer is filled
Available layouts, depending on how codec data is laid out:
- none/flat: straight data. Decoder should handle channel offsets and other details normally.
- interleave: one data block per channel, mixed in configurable sizes. Once one channel block is fully decoded this layout skips the other channels, so the decoder only handles one at a time.
- blocked: data is divided into blocks, often with a header. Layout detects when a block is done and asks a helper function to fix offsets (skipping the header and pointing to data per channel), depending on the block format.
- others: uncommon cases may need its own custom layout (ex.- multistream/subfiles)
The layout is used mainly depends on the decoder. MP3 data (that may have 1 or 2 channels per frame) uses the flat layout, while DSP ADPCM (that only decodes one channel at a time) is interleaved. In case of mono files either could be used as there won't be any actual difference.
Layouts expect the VGMSTREAM to be properly initialized during the meta processing (channel offsets must point to each channel start offset).
#### decoders
Decoders take a sample buffer, convert data to PCM samples and fill one or multiple channels at a time, depending on the decoder itself. Usually its data is divided into frames with a number of samples, and should only need to do one frame at a time (when size is fixed/informed; vgmstream gives flexibility to the decoder), but must take into account that the sample buffer may be smaller than the frame samples, and that may start some samples into the frame.
Every call the decoder will need to find out the current frame offset (usually per channel). This is usually done with a base channel offset (from the VGMSTREAM) plus deriving the frame number (thus sub-offset, but only if frames are fixed) through the current sample, or manually updating the channel offsets every frame. This second method is not suitable to use with the interleave layout as it advances the offsets assuming they didn't change (this is a limitation/bug at the moment). Similarly, the blocked layout cannot contain interleaved data, and must use alt decoders with internal interleave (also a current limitation). Thus, some decoders and layouts don't mix.
If the decoder needs to keep state between calls it may use the VGMSTREAM for common values (like ADPCM history), or alloc a custom data struct. In that case the decoder should provide init/free functions so the meta or vgmstream may use. This is the case with decoders implemented using external libraries (*ext_libs*), as seen in *#ifdef VGM_USE_X ... #endif* sections.
#### core
The vgmstream core simply consists of functions gluing the above together and some helpers (ex.- extension list, loop adjust, etc).
The *Overview* section should give an idea about how it's used.

View File

@ -1,22 +1,26 @@
# vgmstream # vgmstream
This is vgmstream, a library for playing streamed audio from video games. This is vgmstream, a library for playing streamed (pre-recorded) audio
from video games.
There are multiple end-user bits: There are multiple end-user bits:
- a command line decoder called "test.exe" - a command line decoder called "test.exe"
- a Winamp plugin called "in_vgmstream" - a Winamp plugin called "in_vgmstream"
- a foobar2000 component called "foo_input_vgmstream" - a foobar2000 component called "foo_input_vgmstream"
- an XMPlay plugin called "xmp-vgmstream" - an XMPlay plugin called "xmp-vgmstream"
- an Audacious plugin called "libvgmstream" - an Audacious plugin called "libvgmstream"
## Needed files (for Windows) Help and newest builds can be found here: https://www.hcs64.com/
Since Ogg Vorbis, MPEG audio, and other external formats are supported, you
will need to have certain DLL files. Latest development is usually here: https://github.com/kode54/vgmstream/
## Needed extra files (for Windows)
Support for some codecs (Ogg Vorbis, MPEG audio, etc) is done with external
libraries, so you will need to have certain DLL files.
In the case of the foobar2000 component they are all bundled for convenience, In the case of the foobar2000 component they are all bundled for convenience,
or you can get them from here: https://github.com/kode54/vgmstream or you can get them here: https://github.com/kode54/vgmstream/tree/master/ext_libs
(also here: https://f.losno.co/vgmstream-win32-deps.zip, may not be latest). (bundled here: https://f.losno.co/vgmstream-win32-deps.zip, may not be latest).
Put ```libvorbis.dll```, ```libmpg123-0.dll```, ```libg7221_decode.dll```, ```libg719_decode.dll```, Put ```libvorbis.dll```, ```libmpg123-0.dll```, ```libg7221_decode.dll```, ```libg719_decode.dll```,
```at3plusdecoder.dll```, ```avcodec-vgmstream-58.dll```, ```avformat-vgmstream-58.dll```, ```at3plusdecoder.dll```, ```avcodec-vgmstream-58.dll```, ```avformat-vgmstream-58.dll```,
@ -111,20 +115,25 @@ are used in few games.
- Electronic Arts MicroTalk a.k.a. UTK or UMT - Electronic Arts MicroTalk a.k.a. UTK or UMT
- Xiph Vorbis (Ogg, FSB5, Wwise, OGL, Silicon Knights) - Xiph Vorbis (Ogg, FSB5, Wwise, OGL, Silicon Knights)
- MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, etc) - MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, etc)
- Electronic Arts EALayer3
- ITU-T G.722.1 (Polycom Siren 7) - ITU-T G.722.1 (Polycom Siren 7)
- ITU-T G.722.1 annex C (Polycom Siren 14) - ITU-T G.722.1 annex C (Polycom Siren 14)
- ITU G.719 annex B (Polycom Siren 22) - ITU G.719 annex B (Polycom Siren 22)
- FFmpeg codecs: - Electronic Arts EALayer3
- ATRAC3, ATRAC3plus - Electronic Arts EA-XMA
- XMA - Sony ATRAC3, ATRAC3plus
- WMA v1, WMA v2, WMAPro - Microsoft XMA1/2
- AAC - Microsoft WMA v1, WMA v2, WMAPro
- Bink - AAC
- AC3/SPDIF - Bink
- Opus (Ogg, Switch) - AC3/SPDIF
- FLAC - Xiph Opus (Ogg, Switch)
- Others - FLAC
- Others
Note that vgmstream doesn't (can't) reproduce in-game music 1:1, as internal
resampling, filters, volume, etc, are not replicated. Some codecs are not
fully accurate compared to the games due to minor bugs, but in most cases
it isn't audible.
## Supported file types ## Supported file types
@ -292,8 +301,6 @@ This list is not complete and many other files are supported.
- .brstm (GC DSP ADPCM, 8/16 bit PCM) - .brstm (GC DSP ADPCM, 8/16 bit PCM)
- .emff (PSX APDCM, GC DSP ADPCM) - .emff (PSX APDCM, GC DSP ADPCM)
- .fsb/wii (PSX ADPCM, GC DSP ADPCM, Xbox IMA ADPCM, MPEG audio, FSB Vorbis, MS XMA) - .fsb/wii (PSX ADPCM, GC DSP ADPCM, Xbox IMA ADPCM, MPEG audio, FSB Vorbis, MS XMA)
- .genh (lots)
- .txth (lots)
- .msf (PCM, PSX ADPCM, ATRAC3, MP3) - .msf (PCM, PSX ADPCM, ATRAC3, MP3)
- .musx (PSX ADPCM, Xbox IMA ADPCM, DAT4 IMA ADPCM) - .musx (PSX ADPCM, Xbox IMA ADPCM, DAT4 IMA ADPCM)
- .nwa (16 bit PCM, NWA DPCM) - .nwa (16 bit PCM, NWA DPCM)
@ -357,6 +364,9 @@ This list is not complete and many other files are supported.
- .um3 (Ogg Vorbis) - .um3 (Ogg Vorbis)
- .xa (CD-ROM XA audio) - .xa (CD-ROM XA audio)
- .xma (MS XMA/XMA2) - .xma (MS XMA/XMA2)
- artificial/generic headers:
- .genh (lots)
- .txth (lots)
- loop assists: - loop assists:
- .mus (playlist for .acm) - .mus (playlist for .acm)
- .pos (loop info for .wav: 32 bit LE loop start sample + loop end sample) - .pos (loop info for .wav: 32 bit LE loop start sample + loop end sample)

View File

@ -65,6 +65,7 @@ void decode_pcm8_sb_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channels
void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian);
size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample); size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample);

View File

@ -119,8 +119,8 @@ int ffmpeg_custom_read_eaxma(ffmpeg_codec_data *data, uint8_t *buf, int buf_size
virtual_base += data_size; virtual_base += data_size;
} }
/* exit on last block just in case, though should reach file size */ /* exit on last block just in case, though should reach real_size */
if (block_size & 0x80000000) if ((block_size & 0x80000000) || (block_size & 0x45000000))
break; break;
} }
@ -199,10 +199,9 @@ size_t ffmpeg_get_eaxma_virtual_size(int channels, off_t real_offset, size_t rea
/* 0x04(4): decoded samples */ /* 0x04(4): decoded samples */
off_t packets_offset = real_offset + 0x08; off_t packets_offset = real_offset + 0x08;
if ((block_size & 0xFF000000) && !(block_size & 0x80000000)) { /* At 0x00(1): block flag
VGM_LOG("EA-XMA: unknown flag found at %lx\n", (off_t)real_offset); * - in SNS: 0x00=normal block, 0x80=last block (not mandatory)
goto fail; * - in SPS: 0x48=header, 0x44=normal block, 0x45=last block (empty) */
}
max_packets = get_block_max_packets(num_streams, packets_offset, streamFile); max_packets = get_block_max_packets(num_streams, packets_offset, streamFile);
if (max_packets == 0) goto fail; if (max_packets == 0) goto fail;
@ -213,7 +212,7 @@ size_t ffmpeg_get_eaxma_virtual_size(int channels, off_t real_offset, size_t rea
real_offset += (block_size & 0x00FFFFFF); real_offset += (block_size & 0x00FFFFFF);
/* exit on last block just in case, though should reach real_size */ /* exit on last block just in case, though should reach real_size */
if (block_size & 0x80000000) if ((block_size & 0x80000000) || (block_size & 0x45000000))
break; break;
} }

View File

@ -792,7 +792,7 @@ size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) {
size_t ima_bytes_to_samples(size_t bytes, int channels) { size_t ima_bytes_to_samples(size_t bytes, int channels) {
/* 2 samples per byte (2 nibbles) in stereo or mono config */ /* 2 samples per byte (2 nibbles) in stereo or mono config */
return bytes / channels * 2; return bytes * 2 / channels;
} }
size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset) { size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset) {

View File

@ -87,69 +87,83 @@ void decode_pcm16LE_XOR_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int chan
} }
} }
/* decodes u-law (ITU G.711 non-linear PCM), from g711.c */ static int expand_ulaw(uint8_t ulawbyte) {
void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int sign, segment, quantization, new_sample;
int i;
int32_t sample_count;
int sign, segment, quantization, sample;
const int bias = 0x84; const int bias = 0x84;
ulawbyte = ~ulawbyte; /* stored in complement */
sign = (ulawbyte & 0x80);
segment = (ulawbyte & 0x70) >> 4; /* exponent */
quantization = ulawbyte & 0x0F; /* mantissa */
new_sample = (quantization << 3) + bias; /* add bias */
new_sample <<= segment;
new_sample = (sign) ? (bias - new_sample) : (new_sample - bias); /* remove bias */
#if 0 // the above follows Sun's implementation, but this works too
{
static int exp_lut[8] = {0,132,396,924,1980,4092,8316,16764}; /* precalcs from bias */
new_sample = exp_lut[segment] + (quantization << (segment + 3));
if (sign != 0) new_sample = -new_sample;
}
#endif
return new_sample;
}
/* decodes u-law (ITU G.711 non-linear PCM), from g711.c */
void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) { for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
uint8_t ulawbyte = read_8bit(stream->offset+i,stream->streamfile); uint8_t ulawbyte = read_8bit(stream->offset+i,stream->streamfile);
outbuf[sample_count] = expand_ulaw(ulawbyte);
ulawbyte = ~ulawbyte; /* stored in complement */
sign = (ulawbyte & 0x80);
segment = (ulawbyte & 0x70) >> 4; /* exponent */
quantization = ulawbyte & 0x0F; /* mantissa */
sample = (quantization << 3) + bias; /* add bias */
sample <<= segment;
sample = (sign) ? (bias - sample) : (sample - bias); /* remove bias */
#if 0 // the above follows Sun's implementation, but this works too
{
static int exp_lut[8] = {0,132,396,924,1980,4092,8316,16764}; /* precalcs from bias */
sample = exp_lut[segment] + (quantization << (segment + 3));
if (sign != 0) sample = -sample;
}
#endif
outbuf[sample_count] = sample;
} }
} }
void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
uint8_t ulawbyte = read_8bit(stream->offset+i*channelspacing,stream->streamfile);
outbuf[sample_count] = expand_ulaw(ulawbyte);
}
}
static int expand_alaw(uint8_t alawbyte) {
int sign, segment, quantization, new_sample;
alawbyte ^= 0x55;
sign = (alawbyte & 0x80);
segment = (alawbyte & 0x70) >> 4; /* exponent */
quantization = alawbyte & 0x0F; /* mantissa */
new_sample = (quantization << 4);
switch (segment) {
case 0:
new_sample += 8;
break;
case 1:
new_sample += 0x108;
break;
default:
new_sample += 0x108;
new_sample <<= segment - 1;
break;
}
new_sample = (sign) ? new_sample : -new_sample;
return new_sample;
}
/* decodes a-law (ITU G.711 non-linear PCM), from g711.c */ /* decodes a-law (ITU G.711 non-linear PCM), from g711.c */
void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i; int i, sample_count;
int32_t sample_count;
int sign, segment, quantization, sample;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) { for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
uint8_t alawbyte = read_8bit(stream->offset+i,stream->streamfile); uint8_t alawbyte = read_8bit(stream->offset+i,stream->streamfile);
outbuf[sample_count] = expand_alaw(alawbyte);;
alawbyte ^= 0x55;
sign = (alawbyte & 0x80);
segment = (alawbyte & 0x70) >> 4; /* exponent */
quantization = alawbyte & 0x0F; /* mantissa */
sample = (quantization << 4);
switch (segment) {
case 0:
sample += 8;
break;
case 1:
sample += 0x108;
break;
default:
sample += 0x108;
sample <<= segment - 1;
break;
}
sample = (sign) ? sample : -sample;
outbuf[sample_count] = sample;
} }
} }

View File

@ -423,6 +423,7 @@ static const coding_info coding_info_list[] = {
{coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"}, {coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"},
{coding_PCM8_SB_int, "8-bit PCM with sign bit, 1 byte interleave (block)"}, {coding_PCM8_SB_int, "8-bit PCM with sign bit, 1 byte interleave (block)"},
{coding_ULAW, "8-bit u-Law"}, {coding_ULAW, "8-bit u-Law"},
{coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"},
{coding_ALAW, "8-bit a-Law"}, {coding_ALAW, "8-bit a-Law"},
{coding_PCMFLOAT, "32-bit float PCM"}, {coding_PCMFLOAT, "32-bit float PCM"},

View File

@ -4,36 +4,41 @@
/* set up for the block at the given offset */ /* set up for the block at the given offset */
void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) { void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) {
int i;
STREAMFILE* streamFile = vgmstream->ch[0].streamfile; STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
uint32_t id; int i;
size_t file_size, block_size = 0, block_header = 0; size_t block_size = 0, block_header = 0;
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE; int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
size_t file_size = get_streamfile_size(streamFile);
/* find target block ID and skip the rest */
file_size = get_streamfile_size(streamFile);
while (block_offset < file_size) { while (block_offset < file_size) {
id = read_32bitBE(block_offset+0x00,streamFile); uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
block_size = read_32bit(block_offset+0x04,streamFile); /* includes id/size */
block_header = 0x0;
if (id == 0x31534E68) { /* "1SNh" header block found */ block_size = read_32bitLE(block_offset+0x04,streamFile);
block_header = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353 ? 0x28 : 0x2c; /* "EACS" */ if (block_size > 0x00F00000) /* BE in SAT, but one file may have both BE and LE chunks */
if (block_header < block_size) /* sometimes has data */ block_size = read_32bitBE(block_offset+0x04,streamFile);
break;
block_header = 0;
if (id == 0x31534E68 || id == 0x53454144) { /* "1SNh" "SEAD" audio header */
int is_sead = (id == 0x53454144);
int is_eacs = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353;
block_header = is_eacs ? 0x28 : (is_sead ? 0x14 : 0x2c);
if (block_header >= block_size) /* sometimes has audio data after header */
block_header = 0;
} }
else if (id == 0x31534E64 || id == 0x534E4443) { /* "1SNd" "SNDC" audio data */
if (id == 0x31534E64) { /* "1SNd" data block found */
block_header = 0x08; block_header = 0x08;
}
else if (id == 0x00000000) {
break; break;
} }
if (id == 0x00000000 || id == 0xFFFFFFFF) { /* EOF: possible? */ if (block_header) {
break; break;
} }
/* any other blocks "1SNl" "1SNe" etc */ //todo parse movie blocks
block_offset += block_size; block_offset += block_size;
} }
@ -45,6 +50,7 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) {
/* set new channel offsets and block sizes */ /* set new channel offsets and block sizes */
switch(vgmstream->coding_type) { switch(vgmstream->coding_type) {
case coding_PCM8_int: case coding_PCM8_int:
case coding_ULAW_int:
vgmstream->current_block_size /= vgmstream->channels; vgmstream->current_block_size /= vgmstream->channels;
for (i=0;i<vgmstream->channels;i++) { for (i=0;i<vgmstream->channels;i++) {
vgmstream->ch[i].offset = block_offset + block_header + i; vgmstream->ch[i].offset = block_offset + block_header + i;
@ -66,13 +72,24 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) {
break; break;
case coding_DVI_IMA: case coding_DVI_IMA:
vgmstream->current_block_size -= 0x14; if (vgmstream->codec_version == 1) { /* ADPCM hist */
for(i = 0; i < vgmstream->channels; i++) { vgmstream->current_block_samples = read_32bit(block_offset + block_header, streamFile);
off_t adpcm_offset = block_offset + block_header + 0x04; vgmstream->current_block_size = 0; // - (0x04 + 0x08*vgmstream->channels); /* should be equivalent */
vgmstream->ch[i].adpcm_step_index = read_32bit(adpcm_offset + i*0x04, streamFile);
vgmstream->ch[i].adpcm_history1_32 = read_32bit(adpcm_offset + 0x04*vgmstream->channels + i*0x04, streamFile); for(i = 0; i < vgmstream->channels; i++) {
// todo some demuxed vids don't have ADPCM hist? not sure how to correctly detect off_t adpcm_offset = block_offset + block_header + 0x04;
vgmstream->ch[i].offset = block_offset + block_header + 0x14; vgmstream->ch[i].adpcm_step_index = read_32bit(adpcm_offset + i*0x04 + 0x00*vgmstream->channels, streamFile);
vgmstream->ch[i].adpcm_history1_32 = read_32bit(adpcm_offset + i*0x04 + 0x04*vgmstream->channels, streamFile);
vgmstream->ch[i].offset = adpcm_offset + 0x08*vgmstream->channels;
}
VGM_ASSERT(vgmstream->current_block_samples != (block_size - block_header - 0x04 - 0x08*vgmstream->channels) * 2 / vgmstream->channels,
"EA 1SHN blocked: different expected vs block num samples at %lx\n", block_offset);
}
else {
for(i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = block_offset + block_header;
}
} }
break; break;

View File

@ -4,66 +4,58 @@
/* set up for the block at the given offset */ /* set up for the block at the given offset */
void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) {
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
int i; int i;
int new_schl = 0; int new_schl = 0;
STREAMFILE* streamFile = vgmstream->ch[0].streamfile; size_t block_size = 0, block_samples = 0;
uint32_t id;
size_t file_size, block_size = 0, block_samples;
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE; int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
//int16_t (*read_16bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_16bitBE : read_16bitLE; size_t file_size = get_streamfile_size(streamFile);
/* find target block ID and skip the rest */
file_size = get_streamfile_size(streamFile);
while (block_offset < file_size) { while (block_offset < file_size) {
id = read_32bitBE(block_offset+0x00,streamFile); uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
block_size = read_32bitLE(block_offset+0x04,streamFile); block_size = read_32bitLE(block_offset+0x04,streamFile);
if (block_size > 0x00F00000) /* size is always LE, except in early SS/MAC */ if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */
block_size = read_32bitBE(block_offset+0x04,streamFile); block_size = read_32bitBE(block_offset+0x04,streamFile);
/* SCxx blocks have size in the header, but others may not. To simplify we just try to find block_samples = 0;
* a SCDl (main data) every 0x04. EA sometimes concats many small files, so after a SCEl (end block)
* there may be a new SCHl + SCDl too, so this pretends they are a single stream. */
if (id == 0x5343446C) { /* "SCDl" data block found */
/* use num_samples from header if possible; don't calc as data may have padding (ex. PCM8) or not possible (ex. MP3) */ if (id == 0x5343446C || id == 0x5344454E) { /* "SCDl" "SDEN" audio data */
switch(vgmstream->coding_type) { switch(vgmstream->coding_type) {
case coding_PSX: case coding_PSX:
block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels); block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels);
break; break;
default: default:
block_samples = read_32bit(block_offset+0x08,streamFile); block_samples = read_32bit(block_offset+0x08,streamFile);
break; break;
} }
/* guard against false positives (happens in "pIQT" blocks) */
if (block_size > 0xFFFF || block_samples > 0xFFFF) { /* observed max is ~0xf00 but who knows */
block_offset += 0x04;
continue;
}
break;
} }
else { else { /* any other chunk, audio ("SCHl" "SCCl" "SCLl" "SCEl" etc), or video ("pQGT" "pIQT "MADk" etc) */
/* movie "pIQT" may be bigger than what block_size says, but seems to help */ /* padding between "SCEl" and next "SCHl" (when subfiles exist) */
if (id == 0x5343486C || id == 0x5343436C || id == 0x53434C6C || id == 0x70495154) { /* "SCHl" "SCCl" "SCLl" "SCEl" "pIQT" */ if (id == 0x00000000) {
block_offset += block_size; block_size = 0x04;
} else {
block_offset += 0x04;
} }
if (id == 0x5343456C) { /* "SCEl" end block found */ if (id == 0x5343486C || id == 0x5348454E) { /* "SCHl" "SHEN" end block */
block_offset += (block_offset % 0x04) == 0 ? 0 : 0x04 - (block_offset % 0x04); /* 32b-aligned, important */
/* Usually there is padding between SCEl and SCHl too (aligned to 0x80) */
}
if (id == 0x5343486C) { /* "SCHl", new subfile */
new_schl = 1; new_schl = 1;
} }
}
continue; /* guard against errors (happens in bad rips/endianness, observed max is vid ~0x20000) */
if (block_size == 0x00 || block_size > 0xFFFFF || block_samples > 0xFFFF) {
block_size = 0x04;
block_samples = 0;
}
if (block_samples) /* audio found */
break;
block_offset += block_size;
/* "SCEl" are aligned to 0x80 usually, but causes problems if not 32b-aligned (ex. Need for Speed 2 PC) */
if ((id == 0x5343456C || id == 0x5345454E) && block_offset % 0x04) {
block_offset += 0x04 - (block_offset % 0x04);
} }
} }

View File

@ -3,7 +3,7 @@
#include "../layout/layout.h" #include "../layout/layout.h"
#define EA_CODEC_PCM 0x00 #define EA_CODEC_PCM 0x00
//#define EA_CODEC_??? 0x01 //used in SAT videos #define EA_CODEC_ULAW 0x01
#define EA_CODEC_IMA 0x02 #define EA_CODEC_IMA 0x02
#define EA_CODEC_PSX 0xFF //fake value #define EA_CODEC_PSX 0xFF //fake value
@ -20,10 +20,13 @@ typedef struct {
int big_endian; int big_endian;
int loop_flag; int loop_flag;
int is_sead;
int codec_version;
} ea_header; } ea_header;
static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset); static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset);
static void set_ea_1snh_psx_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea); static void set_ea_1snh_num_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea);
static int get_ea_1snh_ima_version(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea);
/* EA 1SNh - from early EA games (~1996, ex. Need for Speed) */ /* EA 1SNh - from early EA games (~1996, ex. Need for Speed) */
VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
@ -37,9 +40,15 @@ VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
goto fail; goto fail;
/* check header (first block) */ /* check header (first block) */
if (read_32bitBE(0,streamFile)!=0x31534E68) /* "1SNh" */ if (read_32bitBE(0x00,streamFile) != 0x31534E68 && /* "1SNh" */
read_32bitBE(0x00,streamFile) != 0x53454144) /* "SEAD" */
goto fail; goto fail;
/* stream is divided into blocks/chunks: 1SNh=audio header, 1SNd=data xN, 1SNl=loop end, 1SNe=end.
* Video uses various blocks (kVGT/fVGT/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */
ea.is_sead = read_32bitBE(0x00,streamFile) == 0x53454144;
/* use block size as endian marker (Saturn = BE) */ /* use block size as endian marker (Saturn = BE) */
ea.big_endian = !(read_32bitLE(0x04,streamFile) < 0x0000FFFF); ea.big_endian = !(read_32bitLE(0x04,streamFile) < 0x0000FFFF);
@ -63,16 +72,22 @@ VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_EA_1SNH; vgmstream->meta_type = meta_EA_1SNH;
switch (ea.codec) { switch (ea.codec) {
case EA_CODEC_PCM: case EA_CODEC_PCM: /* Need for Speed (PC) */
vgmstream->coding_type = ea.bits==1 ? coding_PCM8_int : coding_PCM16_int; vgmstream->coding_type = ea.bits==1 ? coding_PCM8_int : coding_PCM16_int;
break; break;
case EA_CODEC_IMA: case EA_CODEC_ULAW: /* Crusader: No Remorse movies (SAT), FIFA 96 movies (SAT) */
if (ea.bits!=2) goto fail; if (ea.bits && ea.bits!=2) goto fail; /* only set in EACS */
vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */ vgmstream->coding_type = coding_ULAW_int;
break; break;
case EA_CODEC_PSX: case EA_CODEC_IMA: /* Need for Speed II (PC) */
if (ea.bits && ea.bits!=2) goto fail; /* only in EACS */
vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */
vgmstream->codec_version = ea.codec_version;
break;
case EA_CODEC_PSX: /* Need for Speed (PS) */
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
break; break;
@ -98,7 +113,7 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
if (read_32bitBE(offset+0x00, streamFile) == 0x45414353) { /* "EACS" */ if (read_32bitBE(offset+0x00, streamFile) == 0x45414353) { /* "EACS" */
/* PC/SAT EACS subheader */ /* EACS subheader (PC, SAT) */
ea->sample_rate = read_32bit(offset+0x04, streamFile); ea->sample_rate = read_32bit(offset+0x04, streamFile);
ea->bits = read_8bit(offset+0x08, streamFile); ea->bits = read_8bit(offset+0x08, streamFile);
ea->channels = read_8bit(offset+0x09, streamFile); ea->channels = read_8bit(offset+0x09, streamFile);
@ -109,15 +124,32 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
ea->loop_end = read_32bit(offset+0x14, streamFile) + ea->loop_start; /* loop length */ ea->loop_end = read_32bit(offset+0x14, streamFile) + ea->loop_start; /* loop length */
/* 0x18: data start? (0x00), 0x1c: pan/volume/etc? (0x7F), rest can be padding/garbage */ /* 0x18: data start? (0x00), 0x1c: pan/volume/etc? (0x7F), rest can be padding/garbage */
VGM_ASSERT(ea->type != 0, "EA EACS: unknown type\n"); /* block type? */ VGM_ASSERT(ea->type != 0, "EA EACS: unknown type\n"); /* block type? */
if (ea->codec == EA_CODEC_IMA)
ea->codec_version = get_ea_1snh_ima_version(streamFile, 0x00, ea);
}
else if (ea->is_sead) {
/* alt subheader (found in some PC videos) */
ea->sample_rate = read_32bit(offset+0x00, streamFile);
ea->channels = read_32bit(offset+0x04, streamFile);
ea->codec = read_32bit(offset+0x08, streamFile);
if (ea->codec == EA_CODEC_IMA)
ea->codec_version = get_ea_1snh_ima_version(streamFile, 0x00, ea);
set_ea_1snh_num_samples(streamFile, 0x00, ea);
if (ea->loop_start_offset) /* offset found, now find actual start sample */
set_ea_1snh_num_samples(streamFile, 0x00, ea);
} }
else { else {
/* PS subheader */ /* alt subheader (PS) */
ea->sample_rate = read_32bit(offset+0x00, streamFile); ea->sample_rate = read_32bit(offset+0x00, streamFile);
ea->channels = read_8bit(offset+0x18, streamFile); ea->channels = read_8bit(offset+0x18, streamFile);
ea->codec = EA_CODEC_PSX; ea->codec = EA_CODEC_PSX;
set_ea_1snh_psx_samples(streamFile, 0x00, ea);
if (ea->loop_start_offset)/* found offset, now find sample start */ set_ea_1snh_num_samples(streamFile, 0x00, ea);
set_ea_1snh_psx_samples(streamFile, 0x00, ea); if (ea->loop_start_offset) /* offset found, now find actual start sample */
set_ea_1snh_num_samples(streamFile, 0x00, ea);
} }
ea->loop_flag = (ea->loop_end > 0); ea->loop_flag = (ea->loop_end > 0);
@ -126,42 +158,61 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
} }
/* get total samples by parsing block headers, needed when EACS isn't present */ /* get total samples by parsing block headers, needed when EACS isn't present */
static void set_ea_1snh_psx_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea) { static void set_ea_1snh_num_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea) {
int num_samples = 0, loop_start = 0, loop_end = 0, loop_start_offset = 0; int num_samples = 0, loop_start = 0, loop_end = 0, loop_start_offset = 0;
off_t block_offset = start_offset; off_t block_offset = start_offset;
size_t file_size = get_streamfile_size(streamFile); size_t block_size, block_header, block_samples;
int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
size_t file_size = get_streamfile_size(streamFile);
while (block_offset < file_size) { while (block_offset < file_size) {
uint32_t id = read_32bitBE(block_offset+0x00,streamFile); uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
size_t block_size = read_32bit(block_offset+0x04,streamFile); /* includes id/size */ block_size = read_32bit(block_offset+0x04,streamFile);
block_header = 0;
block_samples = 0;
if (id == 0x31534E68) { /* "1SNh" header block found */ if (id == 0x31534E68 || id == 0x53454144) { /* "1SNh" "SEAD" audio header */
size_t block_header = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353 ? 0x28 : 0x2c; /* "EACS" */ int is_sead = (id == 0x53454144);
if (block_header < block_size) /* sometimes has data */ int is_eacs = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353;
num_samples += ps_bytes_to_samples(block_size - block_header, ea->channels);
block_header = is_eacs ? 0x28 : (is_sead ? 0x14 : 0x2c);
if (block_header >= block_size) /* sometimes has audio data after header */
block_header = 0;
} }
else if (id == 0x31534E64 || id == 0x534E4443) { /* "1SNd" "SNDC" audio data */
if (id == 0x31534E64) { /* "1SNd" data block found */ block_header = 0x08;
num_samples += ps_bytes_to_samples(block_size - 0x08, ea->channels);
} }
else if (id == 0x00000000) {
if (id == 0x31534E6C) { /* "1SNl" loop point found */ break;
}
else if (id == 0x31534E6C) { /* "1SNl" loop point found */
loop_start_offset = read_32bit(block_offset+0x08,streamFile); loop_start_offset = read_32bit(block_offset+0x08,streamFile);
loop_end = num_samples; loop_end = num_samples;
} }
if (id == 0x00000000 || id == 0xFFFFFFFF) { /* EOF: possible? */ if (block_header) {
break; switch(ea->codec) {
case EA_CODEC_PSX:
block_samples = ps_bytes_to_samples(block_size - block_header, ea->channels);
break;
case EA_CODEC_IMA:
if (ea->codec_version == 1)
block_samples = read_32bit(block_offset + block_header, streamFile);
else
block_samples = ima_bytes_to_samples(block_size - block_header, ea->channels);
break;
}
} }
/* if there is a loop start offset this was called again just to find it */
/* if there is a loop start offset set, this was called again just to find it */
if (ea->loop_start_offset && ea->loop_start_offset == block_offset) { if (ea->loop_start_offset && ea->loop_start_offset == block_offset) {
ea->loop_start = num_samples; ea->loop_start = num_samples;
return; return;
} }
/* any other blocks "1SNl" "1SNe" etc */ //todo parse movie blocks num_samples += block_samples;
block_offset += block_size; block_offset += block_size;
} }
@ -171,3 +222,31 @@ static void set_ea_1snh_psx_samples(STREAMFILE* streamFile, off_t start_offset,
ea->loop_end = loop_end; ea->loop_end = loop_end;
ea->loop_start_offset = loop_start_offset; ea->loop_start_offset = loop_start_offset;
} }
/* find codec version used, with or without ADPCM hist per block */
static int get_ea_1snh_ima_version(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) {
off_t block_offset = start_offset;
size_t file_size = get_streamfile_size(streamFile);
int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
while (block_offset < file_size) {
uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
size_t block_size = read_32bitLE(block_offset+0x04,streamFile);
if (block_size > 0x00F00000) /* BE in SAT, but one file may have both BE and LE chunks */
block_size = read_32bitBE(block_offset+0x04,streamFile);
if (id == 0x31534E64 || id == 0x534E4443) { /* "1SNd" "SNDC" audio data */
size_t ima_samples = read_32bit(block_offset + 0x08, streamFile);
size_t expected_samples = (block_size - 0x08 - 0x04 - 0x08*ea->channels) * 2 / ea->channels;
if (ima_samples == expected_samples) {
return 1; /* has ADPCM hist (hopefully) */
}
}
block_offset += block_size;
}
return 0; /* no ADPCM hist */
}

View File

@ -28,7 +28,7 @@
#define EA_CODEC1_NONE -1 #define EA_CODEC1_NONE -1
#define EA_CODEC1_PCM 0x00 #define EA_CODEC1_PCM 0x00
#define EA_CODEC1_VAG 0x01 // unsure #define EA_CODEC1_VAG 0x01 // unsure
#define EA_CODEC1_EAXA 0x07 // Need for Speed 2 PC, Fifa 98 SAT #define EA_CODEC1_EAXA 0x07 // Need for Speed 2 PC, FIFA 98 SAT
#define EA_CODEC1_MT10 0x09 #define EA_CODEC1_MT10 0x09
//#define EA_CODEC1_N64 ? //#define EA_CODEC1_N64 ?
@ -91,12 +91,14 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) {
goto fail; goto fail;
/* check header */ /* check header */
/* EA's stream files are made of blocks called "chunks" (SCxx, presumably Sound Chunk xx) if (read_32bitBE(0x00,streamFile) != 0x5343486C && /* "SCHl" */
* typically: SCHl=header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=stream end. read_32bitBE(0x00,streamFile) != 0x5348454E) /* "SHEN" */
* The number/size of blocks is affected by: block rate setting, sample rate, channels, CPU location (SPU/main/DSP/others), etc */
if (read_32bitBE(0x00,streamFile) != 0x5343486C) /* "SCHl" */
goto fail; goto fail;
/* stream is divided into blocks/chunks: SCHl=audio header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=end.
* Video uses various blocks (MVhd/MV0K/etc) and sometimes alt audio blocks (SHEN/SDEN/SEEN).
* The number/size is affected by: block rate setting, sample rate, channels, CPU location (SPU/main/DSP/others), etc */
header_size = read_32bitLE(0x04,streamFile); header_size = read_32bitLE(0x04,streamFile);
if (header_size > 0x00F00000) /* size is always LE, except in early SS/MAC */ if (header_size > 0x00F00000) /* size is always LE, except in early SS/MAC */
header_size = read_32bitBE(0x04,streamFile); header_size = read_32bitBE(0x04,streamFile);
@ -451,7 +453,7 @@ static int parse_variable_header(STREAMFILE* streamFile, ea_header* ea, off_t be
memset(ea,0,sizeof(ea_header)); memset(ea,0,sizeof(ea_header));
/* null defaults as 0 can be valid */ /* null defaults as 0 can be valid */
ea->version = EA_VERSION_NONE; ea->version = EA_VERSION_NONE;
ea->codec1 = EA_CODEC1_NONE; ea->codec1 = EA_CODEC1_NONE;
ea->codec2 = EA_CODEC2_NONE; ea->codec2 = EA_CODEC2_NONE;
@ -747,70 +749,73 @@ fail:
return 0; return 0;
} }
/* get total samples by parsing block headers, needed when multiple files are stitched together */ /* Get total samples by parsing block headers, needed when multiple files are stitched together.
/* Some EA files (.mus, .eam, .sng, etc) concat many small subfiles, used as mapped * Some EA files (.mus/eam/sng/etc) concat many small subfiles, used for interactive/mapped
* music (.map/lin). We get total possible samples (counting all subfiles) and pretend * music (.map/lin). Subfiles always share header, except num_samples. */
* they are a single stream. Subfiles always share header, except num_samples. */
static int get_ea_stream_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) { static int get_ea_stream_total_samples(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea) {
int num_samples = 0; int num_samples = 0;
size_t file_size = get_streamfile_size(streamFile); int new_schl = 0;
off_t block_offset = start_offset; off_t block_offset = start_offset;
size_t block_size, block_samples;
int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
size_t file_size = get_streamfile_size(streamFile);
while (block_offset < file_size) { while (block_offset < file_size) {
uint32_t id, block_size, block_samples; uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
id = read_32bitBE(block_offset+0x00,streamFile);
block_size = read_32bitLE(block_offset+0x04,streamFile); block_size = read_32bitLE(block_offset+0x04,streamFile);
if (block_size > 0x00F00000) /* size is always LE, except in early SS/MAC */ if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */
block_size = read_32bitBE(block_offset+0x04,streamFile); block_size = read_32bitBE(block_offset+0x04,streamFile);
/* SCxx blocks have size in the header, but others may not. To simplify we just try to block_samples = 0;
* find a SCDl (main data) every 0x04. EA sometimes concats many small files, so after a SCEl (end block)
* there may be a new SCHl + SCDl too, so this pretends they are a single stream. */
if (id == 0x5343446C) { /* "SCDl" data block found */
/* use num_samples from header if possible */ if (id == 0x5343446C || id == 0x5344454E) { /* "SCDl" "SDEN" audio data */
switch (ea->codec2) { switch (ea->codec2) {
case EA_CODEC2_VAG: /* PS-ADPCM */ case EA_CODEC2_VAG:
block_samples = ps_bytes_to_samples(block_size-0x10, ea->channels); block_samples = ps_bytes_to_samples(block_size-0x10, ea->channels);
break; break;
default: default:
block_samples = read_32bit(block_offset+0x08,streamFile); block_samples = read_32bit(block_offset+0x08,streamFile);
break; break;
} }
/* guard against false positives (happens in "pIQT" blocks) */
if (block_size > 0xFFFF || block_samples > 0xFFFF) { /* observed max is ~0xf00 but who knows */
block_offset += 0x04;
continue;
}
num_samples += block_samples;
block_offset += block_size; /* size includes header */
} }
else { else { /* any other chunk, audio ("SCHl" "SCCl" "SCLl" "SCEl" etc), or video ("pQGT" "pIQT "MADk" "MPCh" etc) */
/* movie "pIQT" may be bigger than what block_size says, but seems to help */ /* padding between "SCEl" and next "SCHl" (when subfiles exist) */
if (id == 0x5343486C || id == 0x5343436C || id == 0x53434C6C || id == 0x70495154) { /* "SCHl" "SCCl" "SCLl" "SCEl" "pIQT" */ if (id == 0x00000000) {
block_offset += block_size; block_size = 0x04;
} else {
block_offset += 0x04;
} }
if (id == 0x5343456C) { /* "SCEl" end block found */ if (id == 0x5343486C || id == 0x5348454E) { /* "SCHl" "SHEN" end block */
/* Usually there is padding between SCEl and SCHl (aligned to 0x80) */ new_schl = 1;
block_offset += (block_offset % 0x04) == 0 ? 0 : 0x04 - (block_offset % 0x04); /* also 32b-aligned, important */
} }
}
block_offset += 0x04; /* guard against errors (happens in bad rips/endianness, observed max is vid ~0x20000) */
continue; if (block_size == 0x00 || block_size > 0xFFFFF || block_samples > 0xFFFF) {
VGM_LOG("EA SCHl: bad block size %x at %lx\n", block_size, block_offset);
block_size = 0x04;
block_samples = 0;
}
num_samples += block_samples;
block_offset += block_size;
/* "SCEl" are aligned to 0x80 usually, but causes problems if not 32b-aligned (ex. Need for Speed 2 PC) */
if ((id == 0x5343456C || id == 0x5345454E) && block_offset % 0x04) {
VGM_LOG_ONCE("EA SCHl: mis-aligned end offset found\n");
block_offset += 0x04 - (block_offset % 0x04);
} }
} }
return num_samples; /* only use calculated samples with multiple subfiles (rarely header samples may be less due to padding) */
if (new_schl) {
;VGM_LOG("EA SCHl: multiple SCHl found\n");
return num_samples;
}
else {
return 0;
}
} }
/* find data start offset inside the first SCDl; not very elegant but oh well */ /* find data start offset inside the first SCDl; not very elegant but oh well */
@ -825,7 +830,7 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start
id = read_32bitBE(block_offset+0x00,streamFile); id = read_32bitBE(block_offset+0x00,streamFile);
block_size = read_32bitLE(block_offset+0x04,streamFile); block_size = read_32bitLE(block_offset+0x04,streamFile);
if (block_size > 0x00F00000) /* size is always LE, except in early SS/MAC */ if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */
block_size = read_32bitBE(block_offset+0x04,streamFile); block_size = read_32bitBE(block_offset+0x04,streamFile);
if (id == 0x5343446C) { /* "SCDl" data block found */ if (id == 0x5343446C) { /* "SCDl" data block found */

View File

@ -1002,6 +1002,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
case coding_PCM8_SB_int: case coding_PCM8_SB_int:
case coding_PCM8_U_int: case coding_PCM8_U_int:
case coding_ULAW: case coding_ULAW:
case coding_ULAW_int:
case coding_ALAW: case coding_ALAW:
case coding_PCMFLOAT: case coding_PCMFLOAT:
return 1; return 1;
@ -1157,6 +1158,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
case coding_PCM8_SB_int: case coding_PCM8_SB_int:
case coding_PCM8_U_int: case coding_PCM8_U_int:
case coding_ULAW: case coding_ULAW:
case coding_ULAW_int:
case coding_ALAW: case coding_ALAW:
return 1; return 1;
case coding_PCMFLOAT: case coding_PCMFLOAT:
@ -1405,6 +1407,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
samples_to_do); samples_to_do);
} }
break; break;
case coding_ULAW_int:
for (chan=0;chan<vgmstream->channels;chan++) {
decode_ulaw_int(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
vgmstream->channels,vgmstream->samples_into_block,
samples_to_do);
}
break;
case coding_ALAW: case coding_ALAW:
for (chan=0;chan<vgmstream->channels;chan++) { for (chan=0;chan<vgmstream->channels;chan++) {
decode_alaw(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan, decode_alaw(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,

View File

@ -80,12 +80,13 @@ typedef enum {
coding_PCM16_int, /* 16-bit PCM with sample-level interleave (for blocks) */ coding_PCM16_int, /* 16-bit PCM with sample-level interleave (for blocks) */
coding_PCM8, /* 8-bit PCM */ coding_PCM8, /* 8-bit PCM */
coding_PCM8_int, /* 8-Bit PCM with sample-level interleave (for blocks) */ coding_PCM8_int, /* 8-bit PCM with sample-level interleave (for blocks) */
coding_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */ coding_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */
coding_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */ coding_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */
coding_PCM8_SB_int, /* 8-bit PCM, sign bit (others are 2's complement) with sample-level interleave (for blocks) */ coding_PCM8_SB_int, /* 8-bit PCM, sign bit (others are 2's complement) with sample-level interleave (for blocks) */
coding_ULAW, /* 8-bit u-Law (non-linear PCM) */ coding_ULAW, /* 8-bit u-Law (non-linear PCM) */
coding_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */
coding_ALAW, /* 8-bit a-Law (non-linear PCM) */ coding_ALAW, /* 8-bit a-Law (non-linear PCM) */
coding_PCMFLOAT, /* 32 bit float PCM */ coding_PCMFLOAT, /* 32 bit float PCM */