Merge pull request #304 from bnnm/xwma-ffmpeg-lse-sps

xwma ffmpeg lse sps
This commit is contained in:
Christopher Snowhill 2018-10-07 15:44:35 -07:00 committed by GitHub
commit 8d72756f45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 718 additions and 387 deletions

View File

@ -173,10 +173,11 @@ Must use autotools (sh configure, make, make install), though some scripts simpl
### libg7221_decode
Adds support for ITU-T G.722.1 annex C (standardization of Polycom Siren 14).
- Source: https://github.com/kode54/libg7221_decode
- Source: https://github.com/bnnm/vgmstream-g7221
- Alt lib (has volume problems): https://github.com/kode54/libg7221_decode
- DLL: `libg7221_decode.dll`
Requires MSVC (use `g719.sln`).
Use make `libg7221_decode.dll`.
### libg719_decode
Adds support for ITU-T G.719 (standardization of Polycom Siren 22).
@ -191,12 +192,14 @@ Adds support for multiple codecs: ATRAC3, ATRAC3plus, XMA1/2, WMA v1, WMA v2, WM
- Source: https://github.com/FFmpeg/FFmpeg/
- DLLs: `avcodec-vgmstream-58.dll`, `avformat-vgmstream-58.dll`, `avutil-vgmstream-56.dll`, `swresample-vgmstream-3.dll`
vgmstream's FFmpeg builds remove many unnecessary parts of FFmpeg to trim down its gigantic size, and are also built with the "vgmstream-" preffix. Current options can be seen in `ffmpeg_options.txt`.
vgmstream's FFmpeg builds remove many unnecessary parts of FFmpeg to trim down its gigantic size, and are also built with the "vgmstream-" preffix (to avoid clashing with other plugins). Current options can be seen in `ffmpeg_options.txt`.
For GCC simply use autotools (configure, make, make install), passing to `configure` the above options.
For MSCV it can be done through a helper: https://github.com/jb-alvarado/media-autobuild_suite
Both may need yasm somewhere in PATH to properly compile: https://yasm.tortall.net
### LibAtrac9
Adds support for ATRAC9.

View File

@ -21,6 +21,7 @@ void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac
void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
@ -267,6 +268,7 @@ void free_celt_fsb(celt_codec_data *data);
/* ffmpeg_decoder */
ffmpeg_codec_data *init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
ffmpeg_codec_data *init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size);
ffmpeg_codec_data *init_ffmpeg_header_offset_subsong(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int target_subsong);
void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels);
void reset_ffmpeg(VGMSTREAM *vgmstream);

View File

@ -328,6 +328,35 @@ int ffmpeg_make_riff_xwma(uint8_t * buf, size_t buf_size, int codec, size_t data
if (buf_size < riff_size)
return -1;
/* XWMA encoder only allows a few channel/sample rate/bitrate combinations,
* but some create identical files with fake bitrate (1ch 22050hz at
* 20/48/192kbps are all 20kbps, with the exact same codec data).
* Decoder needs correct bitrate to work, so it's normalized here. */
/* (may be removed once FFmpeg fixes this) */
if (codec == 0x161) { /* WMAv2 only */
int ch = channels;
int sr = sample_rate;
int br = avg_bps * 8;
/* Must be a bug in MS's encoder, as later versions of xWMAEncode remove these bitrates */
if (ch == 1) {
if (sr == 22050 && (br==48000 || br==192000))
br = 20000;
else if (sr == 32000 && (br==48000 || br==192000))
br = 20000;
else if (sr == 44100 && (br==96000 || br==192000))
br = 48000;
}
else if (ch == 2) {
if (sr == 22050 && (br==48000 || br==192000))
br = 32000;
else if (sr == 32000 && (br==192000))
br = 48000;
}
avg_bps = br / 8;
}
memcpy(buf+0x00, "RIFF", 4);
put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */
memcpy(buf+0x08, "XWMA", 4);
@ -341,7 +370,7 @@ int ffmpeg_make_riff_xwma(uint8_t * buf, size_t buf_size, int codec, size_t data
put_16bitLE(buf+0x20, block_align); /* block align */
put_16bitLE(buf+0x22, 16); /* bits per sample */
put_16bitLE(buf+0x24, 0); /* extra size */
/* here goes the "dpds" table, but it's optional and not needed by FFmpeg */
/* here goes the "dpds" seek table, but it's optional and not needed by FFmpeg */
memcpy(buf+0x26, "data", 4);
put_32bitLE(buf+0x2a, data_size); /* data size */
@ -488,7 +517,7 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i
while (packet_offset_b < packet_size_b) {
frame_offset_b = offset_b + packet_offset_b; /* in bits for aligment stuff */
/* loops, later adjusted with subframe (seems correct vs tests) */
/* frame loops, later adjusted with subframes (seems correct vs tests) */
if (msd->loop_flag && (offset_b + packet_offset_b) - stream_offset_b == msd->loop_start_b)
loop_start_frame = frames;
if (msd->loop_flag && (offset_b + packet_offset_b) - stream_offset_b == msd->loop_end_b)
@ -542,7 +571,7 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i
if (new_skip > samples_per_frame) /* from xmaencode */
new_skip = samples_per_frame;
if (start_skip==0)
if (start_skip==0) /* only use first skip */
start_skip = new_skip;
}
@ -558,7 +587,7 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i
if (new_skip > samples_per_frame) /* from xmaencode */
new_skip = samples_per_frame;
end_skip = new_skip;
end_skip = new_skip; /* always use last skip */
}
}
}
@ -576,18 +605,39 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i
}
//todo FFmpeg seems to decode 1 subframe late vs xmaencode, and doesn't write 128 init samples, so skips are not useful ATM
//samples = samples + 128 - start_skip - end_skip; /* 128 init samples added by xmaencode */
msd->num_samples = samples;
msd->skip_samples = start_skip;
msd->skip_samples = start_skip; //todo remove once FFmpeg decodes correctly
if (msd->loop_flag && loop_end_frame > loop_start_frame) {
msd->loop_start_sample = loop_start_frame * samples_per_frame + msd->loop_start_subframe * samples_per_subframe;
msd->loop_end_sample = loop_end_frame * samples_per_frame + msd->loop_end_subframe * samples_per_subframe;
msd->loop_end_sample = loop_end_frame * samples_per_frame + (msd->loop_end_subframe) * samples_per_subframe;
}
//todo maybe this is needed
//msd->loop_start_sample -= start_skip;
//msd->loop_end_sample -= start_skip;
//todo apply once FFmpeg decode is ok
// for XMA must internal skip 64 samples + apply skips + output extra 128 IMDCT samples) and remove skip_samples output
#if 0
if (msd->xma_version == 1 || msd->xma_version == 2) {
msd->num_samples += 128; /* final extra IMDCT samples */
msd->num_samples -= start_skip; /* can be less but fixed to 512 in practice */
msd->num_samples -= end_skip;
/* from xma2encode tests this looks correct (probably XMA loops considering IMDCT delay)
* a full loop wav > xma makes start=384, then xma > wav writes "smpl" with start=0 */
if (msd->loop_flag) {
msd->loop_start_sample += 128;
msd->loop_start_sample -= start_skip;
msd->loop_end_sample += 128;
msd->loop_end_sample -= start_skip;
}
}
#endif
/* the above can't properly read skips for WMAPro ATM, but should fixed to 1 frame anyway */
if (msd->xma_version == 0) {
msd->num_samples -= samples_per_frame; /* FFmpeg does skip this */
#if 0
msd->num_samples += (samples_per_frame / 2); /* but doesn't add extra samples */
#endif
}
}
@ -663,7 +713,7 @@ void wmapro_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, int block_
}
samples_per_frame = wma_get_samples_per_frame(version, sample_rate, decode_flags);
bits_frame_size = (int)floor(log(block_align) / log(2)) + 4; /* max bits needed to represent this block_align */
samples_per_subframe = 0; /* not really needed WMAPro can't use loop subframes (complex subframe lengths) */
samples_per_subframe = 0; /* not needed as WMAPro can't use loop subframes (complex subframe lengths) */
msd->xma_version = 0; /* signal it's not XMA */
ms_audio_get_samples(msd, streamFile, start_packet, channels_per_stream, bytes_per_packet, samples_per_frame, samples_per_subframe, bits_frame_size);
@ -703,6 +753,11 @@ void wma_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, int block_ali
}
msd->num_samples = num_frames * samples_per_frame;
#if 0 //todo apply once FFmpeg decode is ok
msd->num_samples += (samples_per_frame / 2); /* last IMDCT samples */
msd->num_samples -= (samples_per_frame * 2); /* WMA default encoder delay */
#endif
}
@ -718,7 +773,8 @@ void xma1_parse_fmt_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * chan
int32_t (*read_32bit)(off_t,STREAMFILE*) = be ? read_32bitBE : read_32bitLE;
int i, num_streams, total_channels = 0;
if (read_16bit(chunk_offset+0x00,streamFile) != 0x165) return;
if (read_16bit(chunk_offset+0x00,streamFile) != 0x165)
return;
num_streams = read_16bit(chunk_offset+0x08,streamFile);
if(loop_flag) *loop_flag = (uint8_t)read_8bit(chunk_offset+0xA,streamFile) > 0;
@ -737,71 +793,112 @@ void xma1_parse_fmt_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * chan
}
/* Read values from a 'new' XMA2 RIFF "fmt" chunk (XMA2WAVEFORMATEX), starting from an offset *after* chunk type+size.
* Useful as custom X360 headers commonly have it lurking inside. Only the extra data, the first part is a normal WAVEFORMATEX. */
* Useful as custom X360 headers commonly have it lurking inside. Only parses the extra data (before is a normal WAVEFORMATEX). */
void xma2_parse_fmt_chunk_extra(STREAMFILE *streamFile, off_t chunk_offset, int * out_loop_flag, int32_t * out_num_samples, int32_t * out_loop_start_sample, int32_t * out_loop_end_sample, int be) {
int16_t (*read_16bit)(off_t,STREAMFILE*) = be ? read_16bitBE : read_16bitLE;
int32_t (*read_32bit)(off_t,STREAMFILE*) = be ? read_32bitBE : read_32bitLE;
int num_samples, loop_start_sample, loop_end_sample, loop_flag;
if (read_16bit(chunk_offset+0x00,streamFile) != 0x166) return;
/* up to extra data is a WAVEFORMATEX */
if (read_16bit(chunk_offset+0x10,streamFile) < 0x22) return; /* expected extra data size */
if (read_16bit(chunk_offset+0x00,streamFile) != 0x166)
return;
if (read_16bit(chunk_offset+0x10,streamFile) < 0x22)
return; /* expected extra data size */
num_samples = read_32bit(chunk_offset+0x18,streamFile); /* max samples from all frames */
num_samples = read_32bit(chunk_offset+0x18,streamFile);
loop_start_sample = read_32bit(chunk_offset+0x28,streamFile);
loop_end_sample = loop_start_sample + read_32bit(chunk_offset+0x2C,streamFile);
loop_flag = (uint8_t)read_8bit(chunk_offset+0x30,streamFile) != 0;
/* num_samples isn't used by xmaencode, so final_num_samples = num_samples + setup_samples (128) - start_skip (~512) - end_skip (0..512) */
/* loop values seem to be after applying skips, so loop_end wouldn't be higher than final_num_samples */
/* flag rarely set, use loop_end as marker */
if (!loop_flag) {
loop_flag = loop_end_sample > 0;
/* loop_end_sample - 128 + start_skip + end_skip = num_samples, use approx */
if (loop_start_sample == 384 && loop_end_sample - 128 + 512 + 512 >= num_samples) {
/* some XMA incorrectly do full loops for every song/jingle [Shadows of the Damned (X360)] */
if ((loop_start_sample + 128 - 512) == 0 && (loop_end_sample + 128 - 512) + 256 >= (num_samples + 128 - 512)) {
VGM_LOG("XMA2 PARSE: disabling full loop\n");
loop_flag = 0; /* some XMA have full loop set without loop flag and shouldn't loop */
loop_flag = 0;
}
}
#if 0 //todo apply once FFmpeg decode is ok
/* apply extra output + skips (see ms_audio_get_samples, approximate as find out with first and last frames) */
{
int start_skip = 512;
int end_skip = 0;
num_samples += 128;
num_samples -= start_skip;
num_samples -= end_skip;
if (loop_flag) {
loop_start_sample += 128;
loop_start_sample -= start_skip;
loop_end_sample += 128;
loop_end_sample -= start_skip;
}
}
#endif
if(out_num_samples) *out_num_samples = num_samples;
if(out_loop_start_sample) *out_loop_start_sample = loop_start_sample;
if(out_loop_end_sample) *out_loop_end_sample = loop_end_sample;
if(out_loop_flag) *out_loop_flag = loop_flag;
/* play_begin+end = pcm_samples in original sample rate (not usable when resampled = looped) */
/* play_begin+end = pcm_samples in original sample rate (not usable as file may be resampled) */
/* int32_t play_begin_sample = read_32bit(xma->chunk_offset+0x20,streamFile); */
/* int32_t play_end_sample = play_begin_sample + read_32bit(xma->chunk_offset+0x24,streamFile); */
}
/* Read values from an 'old' XMA2 RIFF "XMA2" chunk (XMA2WAVEFORMAT), starting from an offset *after* chunk type+size.
* Useful as custom X360 headers commonly have it lurking inside. */
void xma2_parse_xma2_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * channels, int * sample_rate, int * loop_flag, int32_t * num_samples, int32_t * loop_start_sample, int32_t * loop_end_sample) {
void xma2_parse_xma2_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * out_channels, int * out_sample_rate, int * out_loop_flag, int32_t * out_num_samples, int32_t * out_loop_start_sample, int32_t * out_loop_end_sample) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = read_32bitBE; /* XMA2WAVEFORMAT is always big endian */
int i, xma2_chunk_version, num_streams, total_channels = 0;
off_t off;
int i, xma2_chunk_version, num_streams;
int channels, sample_rate, loop_flag, num_samples, loop_start_sample, loop_end_sample;
off_t offset;
xma2_chunk_version = read_8bit(chunk_offset+0x00,streamFile);
num_streams = read_8bit(chunk_offset+0x01,streamFile);
if(loop_start_sample) *loop_start_sample = read_32bit(chunk_offset+0x04,streamFile);
if(loop_end_sample) *loop_end_sample = read_32bit(chunk_offset+0x08,streamFile);
if(loop_flag) *loop_flag = (uint8_t)read_8bit(chunk_offset+0x03,streamFile) > 0 /* rarely not set, encoder default */
|| read_32bit(chunk_offset+0x08,streamFile); /* loop_end_sample */
if(sample_rate) *sample_rate = read_32bit(chunk_offset+0x0c,streamFile);;
loop_start_sample = read_32bit(chunk_offset+0x04,streamFile);
loop_end_sample = read_32bit(chunk_offset+0x08,streamFile);
loop_flag = (uint8_t)read_8bit(chunk_offset+0x03,streamFile) > 0 || loop_end_sample; /* rarely not set, encoder default */
sample_rate = read_32bit(chunk_offset+0x0c,streamFile);
off = xma2_chunk_version == 3 ? 0x14 : 0x1C;
if(num_samples) *num_samples = read_32bit(chunk_offset+off,streamFile);
/*xma->pcm_samples = read_32bitBE(xma->chunk_offset+off+0x04,streamFile)*/
/* num_samples is the max samples in the file (apparently not including encoder delay) */
/* pcm_samples are original WAV's; not current since samples and sample rate may be adjusted for looping purposes */
offset = xma2_chunk_version == 3 ? 0x14 : 0x1C;
num_samples = read_32bit(chunk_offset+offset+0x00,streamFile);
/* pcm_samples in original sample rate (not usable as file may be resampled) */
/* pcm_samples = read_32bitBE(chunk_offset+offset+0x04,streamFile)*/
off = xma2_chunk_version == 3 ? 0x20 : 0x28;
/* channels is the sum of all streams */
offset = xma2_chunk_version == 3 ? 0x20 : 0x28;
channels = 0; /* channels is the sum of all streams */
for (i = 0; i < num_streams; i++) {
total_channels += read_8bit(chunk_offset+off+i*0x04,streamFile);
channels += read_8bit(chunk_offset+offset+i*0x04,streamFile);
}
if (channels) *channels = total_channels;
#if 0 //todo apply once FFmpeg decode is ok
/* apply extra output + skips (see ms_audio_get_samples, approximate as find out with first and last frames) */
{
int start_skip = 512;
int end_skip = 0;
num_samples += 128;
num_samples -= start_skip;
num_samples -= end_skip;
if (loop_flag) {
loop_start_sample += 128;
loop_start_sample -= start_skip;
loop_end_sample += 128;
loop_end_sample -= start_skip;
}
}
#endif
if(out_channels) *out_channels = channels;
if(out_sample_rate) *out_sample_rate = sample_rate;
if(out_num_samples) *out_num_samples = num_samples;
if(out_loop_start_sample) *out_loop_start_sample = loop_start_sample;
if(out_loop_end_sample) *out_loop_end_sample = loop_end_sample;
if(out_loop_flag) *out_loop_flag = loop_flag;
}
/* manually read from "fact" chunk */

View File

@ -29,35 +29,32 @@ static void g_init_ffmpeg() {
}
/* converts codec's samples (can be in any format, ex. Ogg's float32) to PCM16 */
static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount, int bitsPerSample, int floatingPoint) {
static void convert_audio_pcm16(sample *outbuf, const uint8_t *inbuf, int fullSampleCount, int bitsPerSample, int floatingPoint) {
int s;
switch (bitsPerSample) {
case 8:
{
for (s = 0; s < sampleCount; ++s) {
case 8: {
for (s = 0; s < fullSampleCount; s++) {
*outbuf++ = ((int)(*(inbuf++))-0x80) << 8;
}
}
break;
case 16:
{
}
case 16: {
int16_t *s16 = (int16_t *)inbuf;
for (s = 0; s < sampleCount; ++s) {
for (s = 0; s < fullSampleCount; s++) {
*outbuf++ = *(s16++);
}
}
break;
case 32:
{
}
case 32: {
if (!floatingPoint) {
int32_t *s32 = (int32_t *)inbuf;
for (s = 0; s < sampleCount; ++s) {
for (s = 0; s < fullSampleCount; s++) {
*outbuf++ = (*(s32++)) >> 16;
}
}
else {
float *s32 = (float *)inbuf;
for (s = 0; s < sampleCount; ++s) {
for (s = 0; s < fullSampleCount; s++) {
float sample = *s32++;
int s16 = (int)(sample * 32768.0f);
if ((unsigned)(s16 + 0x8000) & 0xFFFF0000) {
@ -66,13 +63,12 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
*outbuf++ = s16;
}
}
}
break;
case 64:
{
}
case 64: {
if (floatingPoint) {
double *s64 = (double *)inbuf;
for (s = 0; s < sampleCount; ++s) {
for (s = 0; s < fullSampleCount; s++) {
double sample = *s64++;
int s16 = (int)(sample * 32768.0f);
if ((unsigned)(s16 + 0x8000) & 0xFFFF0000) {
@ -81,8 +77,8 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
*outbuf++ = s16;
}
}
}
break;
}
}
}
@ -97,30 +93,31 @@ static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount,
* Fortunately seek_frame_generic can use an index to find the correct position. This function reads the
* first frame/packet and sets up index to timestamp 0. This ensures faulty demuxers will seek to 0 correctly.
* Some formats may not seek to 0 even with this, though.
*
* todo: some formats don't work with the current index values
*/
static int init_seek(ffmpeg_codec_data * data) {
int ret, ts_index, found_first = 0;
int64_t ts = 0;
int64_t pos = 0; /* offset */
int size = 0; /* coded size */
int distance = 0; /* always? */
int64_t ts = 0; /* seek timestamp */
int64_t pos = 0; /* data offset */
int size = 0; /* data size (block align) */
int distance = 0; /* always 0 ("duration") */
AVStream * stream;
AVPacket * pkt;
AVStream * stream = data->formatCtx->streams[data->streamIndex];
AVPacket * pkt = data->lastReadPacket;
stream = data->formatCtx->streams[data->streamIndex];
pkt = data->lastReadPacket;
/* read_seek shouldn't need this index, but direct access to FFmpeg's internals is no good */
/* if (data->formatCtx->iformat->read_seek || data->formatCtx->iformat->read_seek2)
return 0; */
/* some formats already have a proper index (e.g. M4A) */
ts_index = av_index_search_timestamp(stream, ts, AVSEEK_FLAG_ANY);
if (ts_index>=0)
/* A few formats may have a proper index (e.g. CAF/MP4/MPC/ASF/WAV/XWMA/FLAC/MP3), but some don't
* work with our custom index (CAF/MPC/MP4) and must skip it. Most formats need flag AVSEEK_FLAG_ANY,
* while XWMA (with index 0 not pointing to ts 0) needs AVSEEK_FLAG_BACKWARD to seek properly, but it
* makes OGG use the index and seek wrong instead. So for XWMA we forcefully remove the index on it's own meta. */
ts_index = av_index_search_timestamp(stream, 0, /*AVSEEK_FLAG_BACKWARD |*/ AVSEEK_FLAG_ANY);
if (ts_index >= 0) {
VGM_LOG("FFMPEG: index found for init_seek\n");
goto test_seek;
}
/* find the first + second packets to get pos/size */
@ -132,7 +129,7 @@ static int init_seek(ffmpeg_codec_data * data) {
if (pkt->stream_index != data->streamIndex)
continue; /* ignore non-selected streams */
if (!found_first) { /* first found */
if (!found_first) {
found_first = 1;
pos = pkt->pos;
ts = pkt->dts;
@ -144,28 +141,28 @@ static int init_seek(ffmpeg_codec_data * data) {
}
if (!found_first)
goto fail;
/* in rare cases there is only one packet */
/* if (size == 0) { size = data_end - pos; } */ /* no easy way to know, ignore (most formats don's need size) */
//if (size == 0) size = data_end - pos; /* no easy way to know, ignore (most formats don's need size) */
/* some formats (XMA1) don't seem to have packet.dts, pretend it's 0 */
/* some formats don't seem to have packet.dts, pretend it's 0 */
if (ts == INT64_MIN)
ts = 0;
/* Some streams start with negative DTS (observed in Ogg). For Ogg seeking to negative or 0 doesn't alter the output.
* It does seem seeking before decoding alters a bunch of (inaudible) +-1 lower bytes though. */
/* Some streams start with negative DTS (OGG/OPUS). For Ogg seeking to negative or 0 doesn't seem different.
* It does seem seeking before decoding alters a bunch of (inaudible) +-1 lower bytes though.
* Output looks correct (encoder delay, num_samples, etc) compared to libvorbis's output. */
VGM_ASSERT(ts != 0, "FFMPEG: negative start_ts (%li)\n", (long)ts);
if (ts != 0)
ts = 0;
/* add index 0 */
ret = av_add_index_entry(stream, pos, ts, size, distance, AVINDEX_KEYFRAME);
if ( ret < 0 )
return ret;
test_seek:
/* seek to 0 test / move back to beginning, since we just consumed packets */
/* seek to 0 test + move back to beginning, since we just consumed packets */
ret = avformat_seek_file(data->formatCtx, data->streamIndex, ts, ts, ts, AVSEEK_FLAG_ANY);
if ( ret < 0 )
return ret; /* we can't even reset_vgmstream the file */
@ -275,6 +272,10 @@ ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, u
return init_ffmpeg_header_offset(streamFile, NULL,0, start,size);
}
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
return init_ffmpeg_header_offset_subsong(streamFile, header, header_size, start, size, 0);
}
/**
* Manually init FFmpeg, from a fake header / offset.
*
@ -282,13 +283,12 @@ ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, u
* This header will be seamlessly inserted before 'start' offset, and total filesize will be 'header_size' + 'size'.
* The header buffer will be copied and memory-managed internally.
* NULL header can used given if the stream has internal data recognized by FFmpeg at offset.
* Stream index can be passed to FFmpeg in the streamFile, if the format has multiple streams (1=first).
* Stream index can be passed if the file has multiple audio streams that FFmpeg can demux (1=first).
*/
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
ffmpeg_codec_data * init_ffmpeg_header_offset_subsong(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int target_subsong) {
char filename[PATH_LIMIT];
ffmpeg_codec_data * data;
int errcode, i;
int targetSubsong = streamFile->stream_index;
int streamIndex, streamCount;
AVStream *stream;
@ -355,7 +355,7 @@ ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t *
streamCount++;
/* select Nth audio stream if specified, or first one */
if (streamIndex < 0 || (targetSubsong > 0 && streamCount == targetSubsong)) {
if (streamIndex < 0 || (target_subsong > 0 && streamCount == target_subsong)) {
codecPar = stream->codecpar;
streamIndex = i;
}
@ -364,7 +364,7 @@ ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t *
if (i != streamIndex)
stream->discard = AVDISCARD_ALL; /* disable demuxing for other streams */
}
if (streamCount < targetSubsong) goto fail;
if (streamCount < target_subsong) goto fail;
if (streamIndex < 0 || !codecPar) goto fail;
data->streamIndex = streamIndex;
@ -459,7 +459,25 @@ ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t *
/* setup decent seeking for faulty formats */
errcode = init_seek(data);
if (errcode < 0) goto fail;
if (errcode < 0) {
VGM_LOG("FFMPEG: can't init_seek\n");
goto fail;
}
/* check ways to skip encoder delay/padding, for debugging purposes (some may be old/unused/encoder only/etc) */
VGM_ASSERT(data->codecCtx->delay > 0, "FFMPEG: delay %i\n", (int)data->codecCtx->delay);//delay: OPUS
//VGM_ASSERT(data->codecCtx->internal->skip_samples > 0, ...); /* for codec use, not accessible */
VGM_ASSERT(stream->codecpar->initial_padding > 0, "FFMPEG: initial_padding %i\n", (int)stream->codecpar->initial_padding);//delay: OPUS
VGM_ASSERT(stream->codecpar->trailing_padding > 0, "FFMPEG: trailing_padding %i\n", (int)stream->codecpar->trailing_padding);
VGM_ASSERT(stream->codecpar->seek_preroll > 0, "FFMPEG: seek_preroll %i\n", (int)stream->codecpar->seek_preroll);//seek delay: OPUS
VGM_ASSERT(stream->skip_samples > 0, "FFMPEG: skip_samples %i\n", (int)stream->skip_samples); //delay: MP4
VGM_ASSERT(stream->start_skip_samples > 0, "FFMPEG: start_skip_samples %i\n", (int)stream->start_skip_samples); //delay: MP3
VGM_ASSERT(stream->first_discard_sample > 0, "FFMPEG: first_discard_sample %i\n", (int)stream->first_discard_sample); //padding: MP3
VGM_ASSERT(stream->last_discard_sample > 0, "FFMPEG: last_discard_sample %i\n", (int)stream->last_discard_sample); //padding: MP3
/* also negative timestamp for formats like OGG/OPUS */
/* not using it: BINK, FLAC, ATRAC3, XMA, MPC, WMA (may use internal skip samples) */
//todo: double check Opus behavior
/* expose start samples to be skipped (encoder delay, usually added by MDCT-based encoders like AAC/MP3/ATRAC3/XMA/etc)
* get after init_seek because some demuxers like AAC only fill skip_samples for the first packet */
@ -478,173 +496,178 @@ fail:
/* decode samples of any kind of FFmpeg format */
void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
int bytesPerSample, bytesPerFrame, frameSize;
int bytesToRead, bytesRead;
uint8_t *targetBuf;
AVFormatContext *formatCtx;
AVCodecContext *codecCtx;
AVPacket *lastReadPacket;
AVFrame *lastDecodedFrame;
int bytesConsumedFromDecodedFrame;
int readNextPacket, endOfStream, endOfAudio;
int framesReadNow;
ffmpeg_codec_data *data = vgmstream->codec_data;
int samplesReadNow;
//todo use either channels / data->channels / codecCtx->channels
/* ignore decode attempts at EOF */
if (data->endOfStream || data->endOfAudio) {
AVFormatContext *formatCtx = data->formatCtx;
AVCodecContext *codecCtx = data->codecCtx;
AVPacket *packet = data->lastReadPacket;
AVFrame *frame = data->lastDecodedFrame;
int planar = av_sample_fmt_is_planar(data->codecCtx->sample_fmt);
int readNextPacket = data->readNextPacket;
int endOfStream = data->endOfStream;
int endOfAudio = data->endOfAudio;
int bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame;
int bytesPerSample = data->bitsPerSample / 8;
int bytesRead, bytesToRead;
/* ignore once file is done (but not at endOfStream as FFmpeg can still output samples until endOfAudio) */
if (/*endOfStream ||*/ endOfAudio) {
VGM_LOG("FFMPEG: decode after end of audio\n");
memset(outbuf, 0, samples_to_do * channels * sizeof(sample));
return;
}
bytesPerSample = data->bitsPerSample / 8;
bytesPerFrame = channels * bytesPerSample;
frameSize = data->channels * bytesPerSample;
bytesToRead = samples_to_do * frameSize;
bytesRead = 0;
targetBuf = data->sampleBuffer;
memset(targetBuf, 0, bytesToRead);
formatCtx = data->formatCtx;
codecCtx = data->codecCtx;
lastReadPacket = data->lastReadPacket;
lastDecodedFrame = data->lastDecodedFrame;
bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame;
readNextPacket = data->readNextPacket;
endOfStream = data->endOfStream;
endOfAudio = data->endOfAudio;
bytesToRead = samples_to_do * (bytesPerSample * codecCtx->channels);
/* keep reading and decoding packets until the requested number of samples (in bytes) */
/* keep reading and decoding packets until the requested number of samples (in bytes for FFmpeg calcs) */
while (bytesRead < bytesToRead) {
int planeSize, planar, dataSize, toConsume, errcode;
int dataSize, toConsume, errcode;
/* size of previous frame */
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1);
/* get sample data size from current frame (dataSize will be < 0 when nb_samples = 0) */
dataSize = av_samples_get_buffer_size(NULL, codecCtx->channels, frame->nb_samples, codecCtx->sample_fmt, 1);
if (dataSize < 0)
dataSize = 0;
/* read new frame + packets when requested */
/* read new data packet when requested */
while (readNextPacket && !endOfAudio) {
if (!endOfStream) {
av_packet_unref(lastReadPacket);
if ((errcode = av_read_frame(formatCtx, lastReadPacket)) < 0) {
/* reset old packet */
av_packet_unref(packet);
/* get compressed data from demuxer into packet */
errcode = av_read_frame(formatCtx, packet);
if (errcode < 0) {
if (errcode == AVERROR_EOF) {
endOfStream = 1;
endOfStream = 1; /* no more data, but may still output samples */
}
if (formatCtx->pb && formatCtx->pb->error)
else {
VGM_LOG("FFMPEG: av_read_frame errcode %i\n", errcode);
}
if (formatCtx->pb && formatCtx->pb->error) {
break;
}
}
if (lastReadPacket->stream_index != data->streamIndex)
if (packet->stream_index != data->streamIndex)
continue; /* ignore non-selected streams */
}
/* send compressed packet to decoder (NULL at EOF to "drain") */
if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) {
/* send compressed data to decoder in packet (NULL at EOF to "drain") */
errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : packet);
if (errcode < 0) {
if (errcode != AVERROR(EAGAIN)) {
VGM_LOG("FFMPEG: avcodec_send_packet errcode %i\n", errcode);
goto end;
}
}
readNextPacket = 0;
readNextPacket = 0; /* got compressed data */
}
/* decode packets into frame (checking if we have bytes to consume from previous frame) */
/* decode packet into frame's sample data (if we don't have bytes to consume from previous frame) */
if (dataSize <= bytesConsumedFromDecodedFrame) {
if (endOfStream && endOfAudio)
if (endOfAudio) {
break;
}
bytesConsumedFromDecodedFrame = 0;
/* receive uncompressed data from decoder */
if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) {
/* receive uncompressed sample data from decoder in frame */
errcode = avcodec_receive_frame(codecCtx, frame);
if (errcode < 0) {
if (errcode == AVERROR_EOF) {
endOfAudio = 1;
endOfAudio = 1; /* no more samples, file is fully decoded */
break;
}
else if (errcode == AVERROR(EAGAIN)) {
readNextPacket = 1;
readNextPacket = 1; /* request more compressed data */
continue;
}
else {
VGM_LOG("FFMPEG: avcodec_receive_frame errcode %i\n", errcode);
goto end;
}
}
/* size of current frame */
dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1);
/* get sample data size of current frame */
dataSize = av_samples_get_buffer_size(NULL, codecCtx->channels, frame->nb_samples, codecCtx->sample_fmt, 1);
if (dataSize < 0)
dataSize = 0;
}
toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead));
/* discard decoded frame if needed (fully or partially) */
if (data->samplesToDiscard) {
int samplesDataSize = dataSize / bytesPerFrame;
int samplesDataSize = dataSize / (bytesPerSample * channels);
if (data->samplesToDiscard >= samplesDataSize) {
/* discard all of the frame's samples and continue to the next */
bytesConsumedFromDecodedFrame = dataSize;
data->samplesToDiscard -= samplesDataSize;
continue;
}
else {
/* discard part of the frame and copy the rest below */
int bytesToDiscard = data->samplesToDiscard * bytesPerFrame;
int bytesToDiscard = data->samplesToDiscard * (bytesPerSample * channels);
int dataSizeLeft = dataSize - bytesToDiscard;
bytesConsumedFromDecodedFrame += bytesToDiscard;
data->samplesToDiscard = 0;
if (toConsume > dataSizeLeft)
toConsume = dataSizeLeft; /* consume at most dataSize left */
toConsume = dataSizeLeft;
}
}
/* copy decoded frame to buffer (mux channels if needed) */
planar = av_sample_fmt_is_planar(codecCtx->sample_fmt);
if (!planar || channels == 1) {
memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume);
/* copy decoded sample data to buffer */
if (!planar || channels == 1) { /* 1 sample per channel, already mixed */
memmove(data->sampleBuffer + bytesRead, (frame->data[0] + bytesConsumedFromDecodedFrame), toConsume);
}
else {
uint8_t * out = (uint8_t *) targetBuf + bytesRead;
else { /* N samples per channel, mix to 1 sample per channel */
uint8_t * out = (uint8_t *) data->sampleBuffer + bytesRead;
int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels;
int toConsumePerPlane = toConsume / channels;
int s, ch;
for (s = 0; s < toConsumePerPlane; s += bytesPerSample) {
for (ch = 0; ch < channels; ++ch) {
memcpy(out, lastDecodedFrame->extended_data[ch] + bytesConsumedPerPlane + s, bytesPerSample);
memcpy(out, frame->extended_data[ch] + bytesConsumedPerPlane + s, bytesPerSample);
out += bytesPerSample;
}
}
}
/* consume */
bytesConsumedFromDecodedFrame += toConsume;
bytesRead += toConsume;
}
end:
framesReadNow = bytesRead / frameSize;
/* Convert the audio */
convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint);
/* Output the state back to the structure */
data->bytesConsumedFromDecodedFrame = bytesConsumedFromDecodedFrame;
/* convert native sample format into PCM16 outbuf */
samplesReadNow = bytesRead / (bytesPerSample * channels);
convert_audio_pcm16(outbuf, data->sampleBuffer, samplesReadNow * channels, data->bitsPerSample, data->floatingPoint);
/* clean buffer when requested more samples than possible */
if (endOfAudio && samplesReadNow < samples_to_do) {
VGM_LOG("FFMPEG: decode after end of audio %i samples\n", (samples_to_do - samplesReadNow));
memset(outbuf + (samplesReadNow * channels), 0, (samples_to_do - samplesReadNow) * channels * sizeof(sample));
}
/* copy state back */
data->readNextPacket = readNextPacket;
data->endOfStream = endOfStream;
data->endOfAudio = endOfAudio;
data->bytesConsumedFromDecodedFrame = bytesConsumedFromDecodedFrame;
}
@ -682,7 +705,8 @@ void reset_ffmpeg(VGMSTREAM *vgmstream) {
void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data;
int64_t ts;
if (!data) return;
if (!data)
return;
/* Start from 0 and discard samples until loop_start (slower but not too noticeable).
* Due to various FFmpeg quirks seeking to a sample is erratic in many formats (would need extra steps). */

View File

@ -207,6 +207,35 @@ static void alp_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
if (*step_index > 88) *step_index=88;
}
/* FFTA2 IMA, different hist and sample rounding, reverse engineered from the ROM */
static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index, int16_t *out_sample) {
int sample_nibble, sample_decoded, step, delta;
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
sample_decoded = *hist1; /* predictor value */
step = ADPCMTable[*step_index] * 0x100; /* current step (table in ROM is pre-multiplied though) */
delta = step >> 3;
if (sample_nibble & 1) delta += step >> 2;
if (sample_nibble & 2) delta += step >> 1;
if (sample_nibble & 4) delta += step;
if (sample_nibble & 8) delta = -delta;
sample_decoded += delta;
/* custom clamp16 */
if (sample_decoded > 0x7FFF00)
sample_decoded = 0x7FFF00;
else if (sample_decoded < -0x800000)
sample_decoded = -0x800000;
*hist1 = sample_decoded;
*out_sample = (short)((sample_decoded + 128) / 256); /* int16 sample rounding, hist is kept as int32 */
*step_index += IMA_IndexTable[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
}
/* ************************************ */
/* DVI/IMA */
/* ************************************ */
@ -351,6 +380,29 @@ void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci
stream->adpcm_step_index = step_index;
}
/* FFTA2 IMA, DVI IMA with custom nibble expand/rounding */
void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i, sample_count;
int32_t hist1 = stream->adpcm_history1_32;
int step_index = stream->adpcm_step_index;
int16_t out_sample;
//external interleave
//no header
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
off_t byte_offset = stream->offset + i/2;
int nibble_shift = (i&1?0:4); //high nibble first
ffta2_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index, &out_sample);
outbuf[sample_count] = out_sample;
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_step_index = step_index;
}
/* ************************************ */
/* MS-IMA */
/* ************************************ */

View File

@ -458,8 +458,8 @@ static const char* extension_list[] = {
"xwb",
"xmd",
"xwc",
"xwm", //FFmpeg, not parsed (XWMA)
"xwma", //FFmpeg, not parsed (XWMA)
"xwm",
"xwma",
"xws",
"xwv",
@ -547,6 +547,7 @@ static const coding_info coding_info_list[] = {
{coding_OTNS_IMA, "Omikron: The Nomad Soul 4-bit IMA ADPCM"},
{coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"},
{coding_ALP_IMA, "High Voltage ALP 4-bit IMA ADPCM"},
{coding_FFTA2_IMA, "Final Fantasy Tactics A2 4-bit IMA ADPCM"},
{coding_MS_IMA, "Microsoft 4-bit IMA ADPCM"},
{coding_XBOX_IMA, "XBOX 4-bit IMA ADPCM"},
@ -1032,16 +1033,12 @@ static const meta_info meta_info_list[] = {
{meta_OGG_SLI, "Ogg Vorbis with .sli (start,length) for looping"},
{meta_OGG_SLI2, "Ogg Vorbis with .sli (from,to) for looping"},
{meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"},
{meta_OGG_UM3, "Ogg Vorbis (Ultramarine3)"},
{meta_OGG_KOVS, "Ogg Vorbis (KOVS header)"},
{meta_OGG_PSYCHIC, "Ogg Vorbis (Psychic Software)"},
{meta_OGG_SNGW, "Ogg Vorbis (Capcom)"},
{meta_OGG_ISD, "Ogg Vorbis (ISD)"},
{meta_OGG_encrypted, "Ogg Vorbis (encrypted)"},
{meta_KMA9, "Koei Tecmo KMA9 header"},
{meta_XWC, "Starbreeze XWC header"},
{meta_SQEX_SAB, "Square-Enix SAB header"},
{meta_SQEX_MAB, "Square-Enix MAB header"},
{meta_OGG_L2SD, "Ogg Vorbis (L2SD)"},
{meta_WAF, "KID WAF header"},
{meta_WAVE, "EngineBlack .WAVE header"},
{meta_WAVE_segmented, "EngineBlack .WAVE header (segmented)"},
@ -1055,20 +1052,15 @@ static const meta_info meta_info_list[] = {
{meta_DSP_MCADPCM, "Bethesda .mcadpcm header"},
{meta_UBI_LYN, "Ubisoft LyN RIFF header"},
{meta_MSB_MSH, "Sony MultiStream MSH+MSB header"},
{meta_OGG_RPGMV, "Ogg Vorbis (RPGMV header)"},
{meta_OGG_ENO, "Ogg Vorbis (ENO header)"},
{meta_TXTP, "TXTP generic header"},
{meta_SMC_SMH, "Genki SMC+SMH header"},
{meta_OGG_YS8, "Ogg Vorbis (Ys VIII header)"},
{meta_PPST, "Parappa PPST header"},
{meta_OPUS_PPP, "AT9 OPUS header"},
{meta_UBI_BAO, "Ubisoft BAO header"},
{meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"},
{meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"},
{meta_OGG_GWM, "Ogg Vorbis (GWM header)"},
{meta_DSP_SADF, "Procyon Studio SADF header"},
{meta_H4M, "Hudson HVQM4 header"},
{meta_OGG_MUS, "Ogg Vorbis (MUS header)"},
{meta_ASF, "Argonaut ASF header"},
{meta_XMD, "Konami XMD header"},
{meta_CKS, "Cricket Audio CKS header"},
@ -1098,7 +1090,7 @@ static const meta_info meta_info_list[] = {
{meta_NXA, "Entergram NXA header"},
{meta_ADPCM_CAPCOM, "Capcom .ADPCM header"},
{meta_UE4OPUS, "Epic Games UE4OPUS header"},
{meta_OGG_LSE, "Ogg Vorbis (LSE header)"},
{meta_XWMA, "Microsoft XWMA RIFF header"},
};

View File

@ -1609,6 +1609,10 @@
<File
RelativePath=".\meta\xwc.c"
>
</File>
<File
RelativePath=".\meta\xwma.c"
>
</File>
<File
RelativePath=".\meta\ydsp.c"

View File

@ -474,6 +474,7 @@
<ClCompile Include="meta\xmd.c" />
<ClCompile Include="meta\xwb.c" />
<ClCompile Include="meta\xwc.c" />
<ClCompile Include="meta\xwma.c" />
<ClCompile Include="meta\ydsp.c" />
<ClCompile Include="meta\zsd.c" />
<ClCompile Include="meta\zwdsp.c" />

View File

@ -991,6 +991,9 @@
<ClCompile Include="meta\xwc.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\xwma.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ydsp.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -38,9 +38,9 @@ VGMSTREAM * init_vgmstream_bik(STREAMFILE *streamFile) {
#ifdef VGM_USE_FFMPEG
{
/* target_subsong should be passed with the streamFile */
/* target_subsong should be passed manually */
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, NULL,0, 0x0,get_streamfile_size(streamFile));
vgmstream->codec_data = init_ffmpeg_header_offset_subsong(streamFile, NULL,0, 0x0,get_streamfile_size(streamFile), target_subsong);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
}

View File

@ -526,7 +526,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
/* get loops (fairly involved due to the multiple layouts and mutant streamfiles)
* full loops aren't too uncommon [Dead Space (PC) stream sfx/ambiance, FIFA 98 (PS3) RAM sfx],
* while actual looping is very rare [Need for Speed: World (PC)] */
* while actual looping is very rare [Need for Speed: World (PC)-EAL3, The Simpsons Game (X360)-EAXMA] */
if (eaac.flags & EAAC_FLAG_LOOPED) {
eaac.loop_flag = 1;
eaac.loop_start = read_32bitBE(header_offset+0x08, streamHead);
@ -545,9 +545,12 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
}
//todo need more cases to test how layout/streamfiles react
if (eaac.loop_start > 0 && !(eaac.codec == EAAC_CODEC_EALAYER3_V1 ||
eaac.codec == EAAC_CODEC_EALAYER3_V2_PCM || eaac.codec == EAAC_CODEC_EALAYER3_V2_SPIKE)) {
VGM_LOG("EA EAAC: unknown actual looping for non-EALayer3\n");
if (eaac.loop_start > 0 && !(
eaac.codec == EAAC_CODEC_EALAYER3_V1 ||
eaac.codec == EAAC_CODEC_EALAYER3_V2_PCM ||
eaac.codec == EAAC_CODEC_EALAYER3_V2_SPIKE ||
eaac.codec == EAAC_CODEC_EAXMA)) {
VGM_LOG("EA EAAC: unknown actual looping for codec %x\n", eaac.codec);
goto fail;
}
}
@ -586,10 +589,22 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
#ifdef VGM_USE_FFMPEG
case EAAC_CODEC_EAXMA: { /* "EXm0": EA-XMA [Dante's Inferno (X360)] */
vgmstream->layout_data = build_layered_eaaudiocore_eaxma(streamData, &eaac);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_layered;
/* special (if hacky) loop handling, see comments */
if (eaac.streamed && eaac.loop_start > 0) {
segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac);
if (!data) goto fail;
vgmstream->layout_data = data;
vgmstream->coding_type = data->segments[0]->coding_type;
vgmstream->layout_type = layout_segmented;
}
else {
vgmstream->layout_data = build_layered_eaaudiocore_eaxma(streamData, &eaac);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_layered;
}
break;
}
#endif
@ -612,7 +627,8 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
start_offset = 0x00; /* must point to the custom streamfile's beginning */
if (eaac.streamed && eaac.loop_start > 0) { /* special (if hacky) loop handling, see comments */
/* special (if hacky) loop handling, see comments */
if (eaac.streamed && eaac.loop_start > 0) {
segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac);
if (!data) goto fail;
vgmstream->layout_data = data;
@ -714,17 +730,13 @@ static size_t get_snr_size(STREAMFILE *streamFile, off_t offset) {
/* Actual looping uses 2 block sections, separated by a block end flag *and* padded.
*
* We use the segmented layout, since the eaac_streamfile doesn't handle padding properly ATM
* (getting EALayer3 frame sizes + skip sizes can be fairly involved), plus seems likely
* that after a block end the decoder needs to be reset (not possible from a streamfile).
*
* Or could fix the blocked_layout+L32P bug, though that involves a lot of rewrites.
* So this is the simplest, surest way ATM (if very ugly). */
* We use the segmented layout, since the eaac_streamfile doesn't handle padding,
* and the segments seem fully separate (so even skipping would probably decode wrong). */
// todo consider better ways to handle this once more looped files for other codecs are found
static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac) {
segmented_layout_data *data = NULL;
STREAMFILE* temp_streamFile[2] = {0};
off_t offsets[2] = { eaac->stream_offset, eaac->loop_offset };
off_t offsets[2] = { eaac->stream_offset, eaac->stream_offset + eaac->loop_offset };
int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start};
int segment_count = 2; /* intro/loop */
int i;
@ -735,9 +747,6 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
if (!data) goto fail;
for (i = 0; i < segment_count; i++) {
temp_streamFile[i] = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]);
if (!temp_streamFile[i]) goto fail;
data->segments[i] = allocate_vgmstream(eaac->channels, 0);
if (!data->segments[i]) goto fail;
data->segments[i]->sample_rate = eaac->sample_rate;
@ -745,6 +754,22 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
//data->segments[i]->meta_type = eaac->meta_type; /* bleh */
switch(eaac->codec) {
#ifdef VGM_USE_FFMPEG
case EAAC_CODEC_EAXMA: {
eaac_header temp_eaac = *eaac; /* equivalent to memcpy... I think */
temp_eaac.loop_flag = 0;
temp_eaac.num_samples = num_samples[i];
temp_eaac.stream_offset = offsets[i];
/* layers inside segments, how trippy */
data->segments[i]->layout_data = build_layered_eaaudiocore_eaxma(streamData, &temp_eaac);
if (!data->segments[i]->layout_data) goto fail;
data->segments[i]->coding_type = coding_FFmpeg;
data->segments[i]->layout_type = layout_layered;
break;
}
#endif
#ifdef VGM_USE_MPEG
case EAAC_CODEC_EALAYER3_V1:
case EAAC_CODEC_EALAYER3_V2_PCM:
@ -752,6 +777,9 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
mpeg_custom_config cfg = {0};
mpeg_custom_t type = (eaac->codec == 0x05 ? MPEG_EAL31b : (eaac->codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S);
temp_streamFile[i] = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]);
if (!temp_streamFile[i]) goto fail;
data->segments[i]->codec_data = init_mpeg_custom(temp_streamFile[i], 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg);
if (!data->segments[i]->codec_data) goto fail;
data->segments[i]->layout_type = layout_none;
@ -766,7 +794,6 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
goto fail;
}
/* setup segmented VGMSTREAMs */
if (!setup_layout_segmented(data))
goto fail;
return data;
@ -832,7 +859,6 @@ static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamDa
}
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
close_streamfile(temp_streamFile);

View File

@ -174,6 +174,9 @@ static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) {
block_flag = (uint8_t)read_8bit(physical_offset+0x00,streamfile);
block_size = read_32bitBE(physical_offset+0x00,streamfile) & 0x00FFFFFF;
if (block_size == 0)
break; /* bad data */
if (data->version == 0 && block_flag != 0x00 && block_flag != 0x80)
break; /* unknown block */

View File

@ -46,7 +46,7 @@ VGMSTREAM * init_vgmstream_dsp_dspw(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ngc_dsp_iadp(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_dsp_mcadpcm(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_vag(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile);
@ -659,7 +659,7 @@ VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_nlsd(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_nxa(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_pc_al2(STREAMFILE * streamFile);
@ -733,7 +733,7 @@ VGMSTREAM * init_vgmstream_ea_sps_fb(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_opus_sps_n1_segmented(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile);
@ -793,4 +793,6 @@ VGMSTREAM * init_vgmstream_adpcm_capcom(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ue4opus(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_xwma(STREAMFILE * streamFile);
#endif /*_META_H*/

View File

@ -6,8 +6,11 @@ VGMSTREAM * init_vgmstream_nds_strm_ffta2(STREAMFILE *streamFile) {
off_t start_offset;
int loop_flag, channel_count;
/* check extension, case insensitive (the ROM seems to use simply .bin though) */
if (!check_extensions(streamFile,"strm"))
/* checks*/
/* .bin: actual extension
* .strm: header id */
if (!check_extensions(streamFile,"bin,strm"))
goto fail;
/* check header */
@ -31,13 +34,12 @@ VGMSTREAM * init_vgmstream_nds_strm_ffta2(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_NDS_STRM_FFTA2;
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_FFTA2_IMA;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x80;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:

View File

@ -1114,16 +1114,18 @@ fail:
return NULL;
}
/* .vag - from Penny-Punching Princess (Switch) sfx */
VGMSTREAM * init_vgmstream_dsp_vag(STREAMFILE *streamFile) {
/* .vag - Nippon Ichi SPS wrapper [Penny-Punching Princess (Switch), Ys VIII (Switch)] */
VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile) {//todo rename
dsp_meta dspm = {0};
/* checks */
if (!check_extensions(streamFile, "vag"))
/* .vag: Penny-Punching Princess (Switch)
* .nlsd: Ys VIII (Switch) */
if (!check_extensions(streamFile, "vag,nlsd"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type? OPUSs had 09 */
if (read_32bitBE(0x00,streamFile) != 0x08000000) /* file type (see other N1 SPS) */
goto fail;
if (read_32bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */
if ((uint16_t)read_16bitLE(0x08,streamFile) != read_32bitLE(0x24,streamFile)) /* header has various repeated values */
goto fail;
dspm.channel_count = 1;

View File

@ -17,7 +17,8 @@ VGMSTREAM * init_vgmstream_ngc_pdt(STREAMFILE *streamFile) {
if (read_16bitBE(0x00,streamFile) != 0x01) /* version? */
goto fail;
if (read_32bitBE(0x04,streamFile) != 0x04) /* entry size? */
if (read_32bitBE(0x04,streamFile) != 0x02 && /* Mario Party 4 (GC) */
read_32bitBE(0x04,streamFile) != 0x04) /* Cubic Lode Runner (GC) */
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x7d00) /* not-sample rate? */
goto fail;

View File

@ -78,7 +78,7 @@ static void um3_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* first 0x800 bytes are xor'd with 0xff */
/* first 0x800 bytes are xor'd */
if (ov_streamfile->offset < 0x800) {
int num_crypt = 0x800 - ov_streamfile->offset;
if (num_crypt > bytes_read)
@ -94,7 +94,7 @@ static void kovs_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, v
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* first 0x100 bytes are xor'd with offset */
/* first 0x100 bytes are xor'd */
if (ov_streamfile->offset < 0x100) {
int max_offset = ov_streamfile->offset + bytes_read;
if (max_offset > 0x100)
@ -110,9 +110,9 @@ static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb
size_t bytes_read = size*nmemb;
int i;
/* add 0x23 ('#') */
/* bytes add 0x23 ('#') */
for (i = 0; i < bytes_read; i++) {
((uint8_t*)ptr)[i] += 0x23;
((uint8_t*)ptr)[i] += 0x23;
}
}
@ -125,7 +125,7 @@ static void sngw_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, v
put_32bitBE(key, ov_streamfile->xor_value);
/* bytes are xor'd with key and nibble-swapped, first "OggS" is changed */
/* first "OggS" is changed and bytes are xor'd and nibble-swapped */
for (i = 0; i < bytes_read; i++) {
if (ov_streamfile->offset+i < 0x04) {
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
@ -145,7 +145,7 @@ static void isd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* bytes are xor'd with key */
/* bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % 16];
}
@ -160,7 +160,6 @@ static void l2sd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, v
/* first "OggS" is changed */
for (i = 0; i < bytes_read; i++) {
if (ov_streamfile->offset+i < 0x04) {
/* replace key in the first 4 bytes with "OggS" */
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
}
else {
@ -177,7 +176,7 @@ static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb,
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* first 0x10 are xor'd with a key, but the header can be easily reconstructed
/* first 0x10 are xor'd, but header can be easily reconstructed
* (key is also in (game)/www/data/System.json "encryptionKey") */
for (i = 0; i < bytes_read; i++) {
if (ov_streamfile->offset+i < 0x10) {
@ -197,7 +196,7 @@ static void eno_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* bytes are xor'd with key */
/* bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value;
}
@ -208,7 +207,7 @@ static void ys8_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* bytes are xor'd with key and nibble-swapped */
/* bytes are xor'd and nibble-swapped */
for (i = 0; i < bytes_read; i++) {
uint8_t val = ((uint8_t*)ptr)[i] ^ ov_streamfile->xor_value;
((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f);
@ -220,7 +219,7 @@ static void gwm_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* bytes are xor'd with key */
/* bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value;
}
@ -236,7 +235,7 @@ static void mus_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
int i;
char *header_id = "OggS";
/* bytes are xor'd with key, first "OggS" is changed */
/* first "OggS" is changed and bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
if (ov_streamfile->offset+i < 0x04) { /* if decrypted gives "Mus " */
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
@ -247,18 +246,35 @@ static void mus_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, vo
}
}
static void lse_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
static void lse_add_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* bytes are xor'd with variable key */
/* bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
int key = 0xC2 + (ov_streamfile->offset + i) % 256;
int key = (uint8_t)ov_streamfile->xor_value + ((ov_streamfile->offset + i) % 256);
((uint8_t*)ptr)[i] ^= key;
}
}
static void lse_ff_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
char *header_id = "OggS";
/* first "OggS" is changed and bytes are xor'd */
for (i = 0; i < bytes_read; i++) {
if (ov_streamfile->offset+i < 0x04) {
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
}
else {
((uint8_t*)ptr)[i] ^= 0xFF;
}
}
}
/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
@ -307,22 +323,21 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
goto fail;
}
/* check standard Ogg Vorbis and variations */
if (is_ogg) {
if (read_32bitBE(0x00,streamFile) == 0x2c444430) { /* Psychic Software [Darkwind: War on Wheels (PC)] */
ovmi.decryption_callback = psychic_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_PSYCHIC;
ovmi.meta_type = meta_OGG_encrypted;
}
else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" [Lineage II Chronicle 4 (PC)] */
ovmi.decryption_callback = l2sd_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_L2SD;
ovmi.meta_type = meta_OGG_encrypted;
}
else if (read_32bitBE(0x00,streamFile) == 0x048686C5) { /* XOR'ed + bitswapped "OggS" [Ys VIII (PC)] */
else if (read_32bitBE(0x00,streamFile) == 0x048686C5) { /* "OggS" XOR'ed + bitswapped [Ys VIII (PC)] */
ovmi.xor_value = 0xF0;
ovmi.decryption_callback = ys8_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_YS8;
ovmi.meta_type = meta_OGG_encrypted;
}
else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" */
else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */
ovmi.meta_type = meta_OGG_VORBIS;
}
else {
@ -330,16 +345,14 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
}
}
/* check "Ultramarine3" (???), may be encrypted */
if (is_um3) {
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
if (is_um3) { /* ["Ultramarine3" (???)] */
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" (optionally encrypted) */
ovmi.decryption_callback = um3_ogg_decryption_callback;
}
ovmi.meta_type = meta_OGG_UM3;
ovmi.meta_type = meta_OGG_encrypted;
}
/* check KOVS (Koei Tecmo games), header + encrypted */
if (is_kovs) {
if (is_kovs) { /* Koei Tecmo PC games] */
if (read_32bitBE(0x00,streamFile) != 0x4b4f5653) { /* "KOVS" */
goto fail;
}
@ -351,19 +364,17 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
start_offset = 0x20;
}
/* check SNGW (Capcom's MT Framework PC games), may be encrypted */
if (is_sngw) {
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
if (is_sngw) { /* [Capcom's MT Framework PC games] */
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" (optionally encrypted) */
ovmi.xor_value = read_32bitBE(0x00,streamFile);
ovmi.decryption_callback = sngw_ogg_decryption_callback;
}
ovmi.meta_type = meta_OGG_SNGW;
ovmi.meta_type = meta_OGG_encrypted;
}
/* check ISD [Gunvolt (PC)], encrypted */
if (is_isd) {
if (is_isd) { /* [Gunvolt (PC)] */
ovmi.decryption_callback = isd_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_ISD;
ovmi.meta_type = meta_OGG_encrypted;
//todo looping unknown, not in Ogg comments
// game has sound/GV_steam.* files with info about sound/stream/*.isd
@ -374,45 +385,48 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
// 0x0c(2): PCM block size, 0x0e(2): PCM bps, 0x10: null, 0x18: samples (in PCM bytes)
}
/* check RPGMKVO [RPG Maker MV (PC)], header + partially encrypted */
if (is_rpgmvo) {
if (is_rpgmvo) { /* [RPG Maker MV (PC)] */
if (read_32bitBE(0x00,streamFile) != 0x5250474D && /* "RPGM" */
read_32bitBE(0x00,streamFile) != 0x56000000) { /* "V\0\0\0" */
goto fail;
}
ovmi.decryption_callback = rpgmvo_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_RPGMV;
ovmi.meta_type = meta_OGG_encrypted;
start_offset = 0x10;
}
/* check ENO [Metronomicon (PC)], key + encrypted */
if (is_eno) {
if (is_eno) { /* [Metronomicon (PC)] */
/* first byte probably derives into xor key, but this works too */
ovmi.xor_value = read_8bit(0x05,streamFile); /* always zero = easy key */
ovmi.decryption_callback = eno_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_ENO;
ovmi.meta_type = meta_OGG_encrypted;
start_offset = 0x01;
}
/* check GWM [Adagio: Cloudburst (PC)], encrypted */
if (is_gwm) {
if (is_gwm) { /* [Adagio: Cloudburst (PC)] */
ovmi.xor_value = 0x5D;
ovmi.decryption_callback = gwm_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_GWM;
ovmi.meta_type = meta_OGG_encrypted;
}
/* check .mus [Redux - Dark Matters (PC)], encrypted */
if (is_mus) {
if (is_mus) { /* [Redux - Dark Matters (PC)] */
ovmi.decryption_callback = mus_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_MUS;
ovmi.meta_type = meta_OGG_encrypted;
}
/* check .lse [Labyrinth of Refrain: Coven of Dusk (PC)], encrypted */
if (is_lse) {
ovmi.decryption_callback = lse_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_LSE;
if (is_lse) { /* [Nippon Ichi PC games] */
if (read_32bitBE(0x00,streamFile) == 0xFFFFFFFF) { /* [Operation Abyss: New Tokyo Legacy (PC)] */
ovmi.decryption_callback = lse_ff_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_encrypted;
}
else { /* [Operation Babel: New Tokyo Legacy (PC), Labyrinth of Refrain: Coven of Dusk (PC)] */
ovmi.decryption_callback = lse_add_ogg_decryption_callback;
ovmi.xor_value = (uint8_t)read_8bit(0x04,streamFile) - 0x04;
ovmi.meta_type = meta_OGG_encrypted;
/* key is found at file_size-1 but this works too (same key for most files but can vary) */
}
}
@ -587,7 +601,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb
}
}
//;VGM_LOG("OGG: user_comment=%s\n", user_comment);
;VGM_LOG("OGG: user_comment=%s\n", user_comment);
}
}

View File

@ -71,8 +71,8 @@ fail:
/* standard Switch Opus, Nintendo header + raw data (generated by opus_test.c?) [Lego City Undercover (Switch)] */
VGMSTREAM * init_vgmstream_opus_std(STREAMFILE *streamFile) {
STREAMFILE * PSIFile = NULL;
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0;
off_t offset;
int num_samples, loop_start, loop_end;
/* checks */
if (!check_extensions(streamFile,"opus,lopus"))
@ -102,13 +102,12 @@ fail:
/* Nippon1 variation [Disgaea 5 (Switch)] */
VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE *streamFile) {
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0;
off_t offset;
int num_samples, loop_start, loop_end;
/* checks */
if ( !check_extensions(streamFile,"opus,lopus"))
goto fail;
if (!((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) ||
(read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF)))
goto fail;
@ -126,8 +125,8 @@ fail:
/* Capcom variation [Ultra Street Fighter II (Switch), Resident Evil: Revelations (Switch)] */
VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE *streamFile) {
VGMSTREAM *vgmstream = NULL;
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0;
off_t offset;
int num_samples, loop_start, loop_end;
int channel_count;
/* checks */
@ -205,8 +204,8 @@ fail:
/* Procyon Studio variation [Xenoblade Chronicles 2 (Switch)] */
VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) {
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
off_t offset;
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
/* checks */
if (!check_extensions(streamFile,"nop"))
@ -231,12 +230,11 @@ fail:
/* Shin'en variation [Fast RMX (Switch)] */
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) {
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0;
int num_samples, loop_start, loop_end;
/* checks */
if ( !check_extensions(streamFile,"opus,lopus"))
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x01000080)
goto fail;
@ -259,6 +257,7 @@ VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE *streamFile) {
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
/* checks */
/* .opus: header ID (they only exist inside .nus3bank) */
if (!check_extensions(streamFile, "opus,lopus"))
goto fail;
if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */
@ -281,32 +280,28 @@ fail:
return NULL;
}
/* Nihon Falcom NLSD Opus [Ys VIII: Lacrimosa of Dana (Switch)]
Similar to Nippon Ichi Software "AT9" Opus (Penny Punching Princess (Switch)
Unlike Penny Punching Princess, this is not segmented */
VGMSTREAM * init_vgmstream_opus_nlsd(STREAMFILE *streamFile) {
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
/* Nippon Ichi SPS wrapper (non-segmented) [Ys VIII: Lacrimosa of Dana (Switch)] */
VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) {
off_t offset;
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
/* checks */
if (!check_extensions(streamFile, "nlsd"))
/* .sps: Labyrinth of Refrain - Coven of Dusk (Switch)
* .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) */
if (!check_extensions(streamFile, "sps,nlsd"))
goto fail;
/* File type check - DSP is 0x8, Opus is 0x9 */
if (read_32bitBE(0x00, streamFile) != 0x09000000)
if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */
goto fail;
offset = 0x1C;
num_samples = read_32bitLE(0x0C, streamFile);
/* Check if there's a loop_end "adjuster" value to determine loop_flag
Setting loop_flag to loop_start would work too but it may cause conflicts
With files that may have a 0 sample loop_start. */
// Not sure why this is a thing but let's roll with it.
loop_flag = read_32bitLE(0x18, streamFile);
/* sections num_samples (remnant of segmented opus_sps_n1):
* 0x10: intro, 0x14: loop, 0x18: end (all must add up to num_samples) */
loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop section has samples */
if (loop_flag) {
loop_start = read_32bitLE(0x10, streamFile);
/* They were being really creative here */
loop_end = (num_samples - read_32bitLE(0x18, streamFile));
loop_end = loop_start + read_32bitLE(0x14, streamFile);
}
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);

View File

@ -4,8 +4,8 @@
static STREAMFILE* setup_opus_ppp_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, const char* fake_ext);
/* .AT9 Opus - from Penny-Punching Princess (Switch) */
VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile) {
/* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch)] */
VGMSTREAM * init_vgmstream_opus_sps_n1_segmented(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t segment_offset;
int loop_flag, channel_count;
@ -19,11 +19,11 @@ VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile) {
/* checks */
if (!check_extensions(streamFile, "at9"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x09000000) /* file type? DSPs had 08 */
if (read_32bitBE(0x00,streamFile) != 0x09000000) /* file type (see other N1 SPS) */
goto fail;
if (read_32bitLE(0x04,streamFile) + 0x1c != get_streamfile_size(streamFile))
goto fail;
/* 0x08(2): sample rate, 0x0a(2): loop flag?, 0x0c: num_samples (slightly smaller than added samples) */
/* 0x08(2): sample rate, 0x0a(2): flag?, 0x0c: num_samples (slightly smaller than added samples) */
segment_count = 3; /* intro/loop/end */
loop_start_segment = 1;

View File

@ -371,16 +371,16 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
break;
case 0x736D706C: /* "smpl" (RIFFMIDISample + MIDILoop chunk) */
/* check loop count/loop info (most common) *///todo double check values
/* check loop count/loop info (most common) */
/* 0x00: manufacturer id, 0x04: product id, 0x08: sample period, 0x0c: unity node,
* 0x10: pitch fraction, 0x14: SMPTE format, 0x18: SMPTE offset, 0x1c: loop count, 0x20: sampler data */
if (read_32bitLE(current_chunk+0x08+0x1c, streamFile)==1) {
if (read_32bitLE(current_chunk+0x08+0x1c, streamFile) == 1) { /* handle only one loop (could contain N MIDILoop) */
/* 0x24: cue point id, 0x28: type (0=forward, 1=alternating, 2=backward)
* 0x2c: start, 0x30: end, 0x34: fraction, 0x38: play count */
if (read_32bitLE(current_chunk+0x08+0x28, streamFile)==0) {
if (read_32bitLE(current_chunk+0x08+0x28, streamFile) == 0) { /* loop forward */
loop_flag = 1;
loop_start_smpl = read_32bitLE(current_chunk+0x08+0x2c, streamFile);
loop_end_smpl = read_32bitLE(current_chunk+0x08+0x30, streamFile);
loop_end_smpl = read_32bitLE(current_chunk+0x08+0x30, streamFile) + 1; /* must add 1 as per spec (ok for standard WAV/AT3/AT9) */
}
}
break;
@ -393,10 +393,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
&& read_32bitLE(current_chunk+0x08+0x10, streamFile) > 0
&& read_32bitLE(current_chunk+0x08+0x14, streamFile) == 0x10) {
/* 0x14: size, 0x18: loop type (0=forward, 1=release), 0x1c: loop start, 0x20: loop length */
if (read_32bitLE(current_chunk+0x08+0x18, streamFile)==0) {
if (read_32bitLE(current_chunk+0x08+0x18, streamFile) == 0) { /* loop forward */
loop_flag = 1;
loop_start_wsmp = read_32bitLE(current_chunk+0x08+0x1c, streamFile);
loop_end_wsmp = read_32bitLE(current_chunk+0x08+0x20, streamFile);
loop_end_wsmp = read_32bitLE(current_chunk+0x08+0x20, streamFile); /* must not add 1 as per spec */
loop_end_wsmp += loop_start_wsmp;
}
}
@ -655,6 +655,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
else if (loop_start_smpl >= 0) {
vgmstream->loop_start_sample = loop_start_smpl;
vgmstream->loop_end_sample = loop_end_smpl;
/* end must add +1, but check in case of faulty tools */
if (vgmstream->loop_end_sample - 1 == vgmstream->num_samples)
vgmstream->loop_end_sample--;
vgmstream->meta_type = meta_RIFF_WAVE_smpl;
}
else if (loop_start_wsmp >= 0) {
@ -754,7 +758,7 @@ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) {
if (read_32bitBE(current_chunk+0x2c+4, streamFile)==0) {
loop_flag = 1;
loop_start_offset = read_32bitBE(current_chunk+0x2c+8, streamFile);
loop_end_offset = read_32bitBE(current_chunk+0x2c+0xc,streamFile);
loop_end_offset = read_32bitBE(current_chunk+0x2c+0xc,streamFile) + 1;
}
}
break;
@ -803,6 +807,10 @@ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) {
if (loop_start_offset >= 0) {
vgmstream->loop_start_sample = loop_start_offset;
vgmstream->loop_end_sample = loop_end_offset;
/* end must add +1, but check in case of faulty tools */
if (vgmstream->loop_end_sample - 1 == vgmstream->num_samples)
vgmstream->loop_end_sample--;
vgmstream->meta_type = meta_RIFX_WAVE_smpl;
}
}

View File

@ -3,7 +3,7 @@
static STREAMFILE* setup_sps_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size, char* extension);
/* .SPS - Nippon Ichi's RIFF AT3 wrapper [ClaDun (PSP)] */
/* .SPS - Nippon Ichi wrapper [ClaDun (PSP)] */
VGMSTREAM * init_vgmstream_sps_n1(STREAMFILE *streamFile) {
VGMSTREAM *vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
@ -16,10 +16,10 @@ VGMSTREAM * init_vgmstream_sps_n1(STREAMFILE *streamFile) {
goto fail;
/* mini header */
type = read_32bitLE(0x00,streamFile); //todo channels? all known VAG are mono and AT3 stereo
type = read_32bitLE(0x00,streamFile);
subfile_size = read_32bitLE(0x04,streamFile);
sample_rate = (uint16_t)read_16bitLE(0x08,streamFile);
/* 0x0a/0b: stereo+loop flags? */
/* 0x0a: flag? */
//num_samples = read_32bitLE(0x0c,streamFile);
subfile_offset = 0x10;

View File

@ -11,8 +11,11 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) {
int fmt_be = 0;
/* check extension, case insensitive */
/* .xma2: Skullgirls, .nps: Beautiful Katamari (renamed .xma), .str: Sonic & Sega All Stars Racing */
/* checks */
/* .xma: standard
* .xma2: Skullgirls (X360)
* .nps: Beautiful Katamari (X360)
* .str: Sonic & Sega All Stars Racing (X360) */
if ( !check_extensions(streamFile, "xma,xma2,nps,str") )
goto fail;
@ -86,7 +89,6 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) {
num_samples = msd.num_samples;
loop_start_sample = msd.loop_start_sample;
loop_end_sample = msd.loop_end_sample;
/* XMA2 loop/num_samples don't seem to use msd.skip_samples */
}
@ -111,7 +113,6 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) {
} else {
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, fmt_be);
}
if (bytes <= 0) goto fail;
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size);
if ( !vgmstream->codec_data ) goto fail;
@ -122,15 +123,7 @@ VGMSTREAM * init_vgmstream_xma(STREAMFILE *streamFile) {
goto fail;
#endif
#if 0
//not active due to a FFmpeg bug that misses some of the last packet samples and decodes
// garbage if asked for more samples (always happens but more apparent with skip_samples active)
/* fix encoder delay */
if (data->skipSamples==0)
ffmpeg_set_skip_samples(data, xma.skip_samples);
#endif
/* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
return vgmstream;

View File

@ -67,6 +67,8 @@ typedef struct {
uint32_t loop_end;
uint32_t loop_start_sample;
uint32_t loop_end_sample;
int is_crackdown;
} xwb_header;
static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile);
@ -102,9 +104,11 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
/* read main header (WAVEBANKHEADER) */
xwb.version = read_32bit(0x04, streamFile); /* XACT3: 0x04=tool version, 0x08=header version */
/* Crackdown 1 X360, essentially XACT2 but may have split header in some cases */
if (xwb.version == XACT_CRACKDOWN)
/* Crackdown 1 (X360), essentially XACT2 but may have split header in some cases */
if (xwb.version == XACT_CRACKDOWN) {
xwb.version = XACT2_2_MAX;
xwb.is_crackdown = 1;
}
/* read segment offsets (SEGIDX) */
if (xwb.version <= XACT1_0_MAX) {
@ -222,7 +226,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(off+0x08, streamFile);
xwb.stream_size = (uint32_t)read_32bit(off+0x0c, streamFile);
if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */
if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */
xwb.loop_start = (uint32_t)read_32bit(off+0x10, streamFile);
xwb.loop_end = (uint32_t)read_32bit(off+0x14, streamFile);//length (LoopRegion) or offset (XMALoopRegion in late XACT2)
} else { /* LoopRegion (samples) */
@ -364,8 +368,8 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
xwb.loop_end_sample = msadpcm_bytes_to_samples(xwb.loop_start + xwb.loop_end, block_size, xwb.channels);
}
else if (xwb.version <= XACT2_1_MAX && (xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) {
/* v38: byte offset, v40+: sample offset, v39: ? */
/* need to manually find sample offsets, thanks to Microsoft dumb headers */
/* v38: byte offset, v40+: sample offset, v39: ? */
/* need to manually find sample offsets, thanks to Microsoft's dumb headers */
ms_sample_data msd = {0};
msd.xma_version = xwb.codec == XMA1 ? 1 : 2;
@ -387,19 +391,38 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
if (xwb.num_samples == 0)
xwb.num_samples = msd.num_samples;
// todo fix properly (XWB loop_start/end seem to count padding samples while XMA1 RIFF doesn't)
//this doesn't seem ok because can fall within 0 to 512 (ie.- first frame, 384)
//if (xwb.loop_start_sample) xwb.loop_start_sample -= 512;
//if (xwb.loop_end_sample) xwb.loop_end_sample -= 512;
/* if provided, xwb.num_samples is equal to msd.num_samples after proper adjustments (+ 128 - start_skip - end_skip) */
//add padding back until it's fixed (affects looping)
// (in rare cases this causes a glitch in FFmpeg since it has a bug where it's missing some samples)
#if 1
//todo add padding back until FFmpeg decoding + msd.loops are fixed (affects edge loops)
// (in rare cases this causes a glitch in FFmpeg due to missing samples)
xwb.num_samples += 64 + 512;
#endif
}
else if ((xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) {
/* seems to be needed by some edge cases, ex. Crackdown */
//add padding, see above
xwb.num_samples += 64 + 512;
/* unlike prev versions, xwb.num_samples is the full size without adjustments */
#if 0 //todo apply once FFmpeg decode is ok
/* apply extra output + skips (see ms_audio_get_samples, approximate as find out with first and last frames) */
int start_skip = 512;
int end_skip = 0;
xwb.num_samples += 128;
xwb.num_samples -= start_skip;
xwb.num_samples -= end_skip;
if (xwb.loop_flag) {
xwb.loop_start_sample += 128;
xwb.loop_start_sample -= start_skip;
xwb.loop_end_sample += 128;
xwb.loop_end_sample -= start_skip;
}
#endif
/* Crackdown does use xwb.num_samples after adjustments (but not loops), fix it back */
if (xwb.is_crackdown) {
xwb.num_samples += 512 - 128;
}
}
@ -437,12 +460,10 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
#ifdef VGM_USE_FFMPEG
case XMA1: { /* Kameo (X360) */
uint8_t buf[100];
uint8_t buf[0x100];
int bytes;
bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
if (bytes <= 0) goto fail;
bytes = ffmpeg_make_riff_xma1(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, xwb.stream_offset,xwb.stream_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
@ -451,15 +472,13 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
}
case XMA2: { /* Blue Dragon (X360) */
uint8_t buf[100];
uint8_t buf[0x100];
int bytes, block_size, block_count;
block_size = 0x10000; /* XACT default */
block_count = xwb.stream_size / block_size + (xwb.stream_size % block_size ? 1 : 0);
bytes = ffmpeg_make_riff_xma2(buf, 100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
if (bytes <= 0) goto fail;
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, xwb.stream_offset,xwb.stream_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
@ -467,7 +486,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
break;
}
case WMA: { /* WMAudio1 (WMA v1): Prince of Persia 2 port (Xbox) */
case WMA: { /* WMAudio1 (WMA v2): Prince of Persia 2 port (Xbox) */
ffmpeg_codec_data *ffmpeg_data = NULL;
ffmpeg_data = init_ffmpeg_offset(streamFile, xwb.stream_offset,xwb.stream_size);
@ -483,10 +502,10 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
}
case XWMA: { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): Bullet Witch (PC) voices */
uint8_t buf[100];
uint8_t buf[0x100];
int bytes, bps_index, block_align, block_index, avg_bps, wma_codec;
bps_index = (xwb.block_align >> 5); /* upper 3b bytes-per-second index */ //docs say 2b+6b but are wrong
bps_index = (xwb.block_align >> 5); /* upper 3b bytes-per-second index (docs say 2b+6b but are wrong) */
block_index = (xwb.block_align) & 0x1F; /*lower 5b block alignment index */
if (bps_index >= 7) goto fail;
if (block_index >= 17) goto fail;
@ -495,9 +514,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
block_align = wma_block_align_index[block_index];
wma_codec = xwb.bits_per_sample ? 0x162 : 0x161; /* 0=WMAudio2, 1=WMAudio3 */
bytes = ffmpeg_make_riff_xwma(buf, 100, wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
if (bytes <= 0) goto fail;
bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, xwb.stream_offset,xwb.stream_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
@ -506,16 +523,14 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
}
case ATRAC3: { /* Techland PS3 extension [Sniper Ghost Warrior (PS3)] */
uint8_t buf[200];
uint8_t buf[0x100];
int bytes;
int block_size = xwb.block_align * vgmstream->channels;
int joint_stereo = xwb.block_align == 0x60; /* untested, ATRAC3 default */
int skip_samples = 0; /* unknown */
bytes = ffmpeg_make_riff_atrac3(buf, 200, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, skip_samples);
if (bytes <= 0) goto fail;
bytes = ffmpeg_make_riff_atrac3(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_size, joint_stereo, skip_samples);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, xwb.stream_offset,xwb.stream_size);
if ( !vgmstream->codec_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;

87
src/meta/xwma.c Normal file
View File

@ -0,0 +1,87 @@
#include "meta.h"
#include "../coding/coding.h"
/* XWMA - Microsoft WMA container [The Elder Scrolls: Skyrim (PC/X360), Hydrophobia (PC)] */
VGMSTREAM * init_vgmstream_xwma(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t fmt_offset, data_offset, first_offset = 0xc;
size_t fmt_size, data_size;
int loop_flag, channel_count;
/* checks */
/* .xwma: standard
* .xwm: The Elder Scrolls: Skyrim (PC), Blade Arcus from Shining (PC) */
if (!check_extensions(streamFile, "xwma,xwm"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x58574D41) /* "XWMA" */
goto fail;
if ( !find_chunk_le(streamFile, 0x666d7420,first_offset,0, &fmt_offset,&fmt_size) ) /* "fmt "*/
goto fail;
if ( !find_chunk_le(streamFile, 0x64617461,first_offset,0, &data_offset,&data_size) ) /* "data"*/
goto fail;
channel_count = read_16bitLE(fmt_offset+0x02,streamFile);
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitLE(fmt_offset+0x04, streamFile);
vgmstream->meta_type = meta_XWMA;
/* the main purpose of this meta is redoing the XWMA header to:
* - redo header to fix XWMA with buggy bit rates so FFmpeg can play them ok
* - skip seek table to fix FFmpeg buggy XWMA seeking (see init_seek)
* - read num_samples correctly
*/
#ifdef VGM_USE_FFMPEG
{
uint8_t buf[0x100];
int bytes, avg_bps, block_align, wma_codec;
avg_bps = read_32bitLE(fmt_offset+0x08, streamFile);
block_align = (uint16_t)read_16bitLE(fmt_offset+0x0c, streamFile);
wma_codec = (uint16_t)read_16bitLE(fmt_offset+0x00, streamFile);
bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, data_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, data_offset,data_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
/* manually find total samples, why don't they put this in the header is beyond me */
{
ms_sample_data msd = {0};
msd.channels = vgmstream->channels;
msd.data_offset = data_offset;
msd.data_size = data_size;
if (wma_codec == 0x0162)
wmapro_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x00E0);
else
wma_get_samples(&msd, streamFile, block_align, vgmstream->sample_rate,0x001F);
vgmstream->num_samples = msd.num_samples;
if (vgmstream->num_samples == 0)
vgmstream->num_samples = (int32_t)((ffmpeg_codec_data*)vgmstream->codec_data)->totalSamples; /* from avg-br */
//num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2
}
}
#else
goto fail;
#endif
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -364,7 +364,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_opus_nop,
init_vgmstream_opus_shinen,
init_vgmstream_opus_nus3,
init_vgmstream_opus_nlsd,
init_vgmstream_opus_sps_n1,
init_vgmstream_opus_nxa,
init_vgmstream_pc_al2,
init_vgmstream_pc_ast,
@ -405,7 +405,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_smc_smh,
init_vgmstream_ea_sps_fb,
init_vgmstream_ppst,
init_vgmstream_opus_ppp,
init_vgmstream_opus_sps_n1_segmented,
init_vgmstream_ubi_bao_pk,
init_vgmstream_dsp_switch_audio,
init_vgmstream_sadf,
@ -422,7 +422,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_bnk_sony,
init_vgmstream_nus3bank,
init_vgmstream_scd_sscf,
init_vgmstream_dsp_vag,
init_vgmstream_dsp_sps_n1,
init_vgmstream_dsp_itl_ch,
init_vgmstream_a2m,
init_vgmstream_ahv,
@ -441,6 +441,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_utk,
init_vgmstream_adpcm_capcom,
init_vgmstream_ue4opus,
init_vgmstream_xwma,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
@ -1129,6 +1130,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
case coding_3DS_IMA:
case coding_WV6_IMA:
case coding_ALP_IMA:
case coding_FFTA2_IMA:
return 2;
case coding_XBOX_IMA:
case coding_XBOX_IMA_mch:
@ -1299,6 +1301,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
case coding_3DS_IMA:
case coding_WV6_IMA:
case coding_ALP_IMA:
case coding_FFTA2_IMA:
return 0x01;
case coding_MS_IMA:
case coding_RAD_IMA:
@ -1766,6 +1769,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
vgmstream->channels,vgmstream->samples_into_block,samples_to_do);
}
break;
case coding_FFTA2_IMA:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_ffta2_ima(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do);
}
break;
case coding_APPLE_IMA4:
for (ch = 0; ch < vgmstream->channels; ch++) {

View File

@ -115,6 +115,7 @@ typedef enum {
coding_OTNS_IMA, /* Omikron The Nomad Soul IMA ADPCM */
coding_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */
coding_ALP_IMA, /* High Voltage ALP 4-bit IMA ADPCM */
coding_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */
coding_MS_IMA, /* Microsoft IMA ADPCM */
coding_XBOX_IMA, /* XBOX IMA ADPCM */
@ -641,16 +642,12 @@ typedef enum {
meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */
meta_OGG_SLI2, /* Ogg Vorbis file w/ different styled .sli for looping */
meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */
meta_OGG_UM3, /* Ogg Vorbis with optional encryption */
meta_OGG_KOVS, /* Ogg Vorbis with encryption (Koei Tecmo Games) */
meta_OGG_PSYCHIC, /* Ogg Vorbis with encryption */
meta_OGG_SNGW, /* Ogg Vorbis with optional encryption (Capcom PC games) */
meta_OGG_ISD, /* Ogg Vorbis with encryption (Azure Striker Gunvolt PC) */
meta_OGG_KOVS, /* Ogg Vorbis with header and encryption (Koei Tecmo Games) */
meta_OGG_encrypted, /* Ogg Vorbis with encryption */
meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */
meta_XWC, /* Starbreeze games */
meta_SQEX_SAB, /* Square-Enix newest middleware (sound) */
meta_SQEX_MAB, /* Square-Enix newest middleware (music) */
meta_OGG_L2SD, /* Ogg Vorbis with obfuscation [Lineage II Chronicle 4 (PC)] */
meta_WAF, /* KID WAF [Ever 17 (PC)] */
meta_WAVE, /* EngineBlack games [Mighty Switch Force! (3DS)] */
meta_WAVE_segmented, /* EngineBlack games, segmented [Shantae and the Pirate's Curse (PC)] */
@ -664,19 +661,14 @@ typedef enum {
meta_DSP_MCADPCM, /* Skyrim (Switch) */
meta_UBI_LYN, /* Ubisoft LyN engine [The Adventures of Tintin (multi)] */
meta_MSB_MSH, /* sfx companion of MIH+MIB */
meta_OGG_RPGMV, /* Ogg Vorbis with encryption [RPG Maker MV games (PC)] */
meta_OGG_ENO, /* Ogg Vorbis with encryption [Metronomicon (PC)] */
meta_TXTP, /* generic text playlist */
meta_SMC_SMH, /* Wangan Midnight (System 246) */
meta_OGG_YS8, /* Ogg Vorbis with encryption (Ys VIII PC) */
meta_PPST, /* PPST [Parappa the Rapper (PSP)] */
meta_OPUS_PPP, /* .at9 Opus [Penny-Punching Princess (Switch)] */
meta_UBI_BAO, /* Ubisoft BAO */
meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */
meta_TA_AAC_VITA, /* tri-Ace AAC (Judas Code) */
meta_OGG_GWM, /* Ogg Vorbis with encryption [Metronomicon (PC)] */
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
meta_OGG_MUS, /* Ogg Vorbis with encryption [Redux - Dark Matters (PC)] */
meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */
meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */
meta_CKS, /* Cricket Audio stream [Part Time UFO (Android), Mega Man 1-6 (Android)] */
@ -706,7 +698,7 @@ typedef enum {
meta_NXA,
meta_ADPCM_CAPCOM,
meta_UE4OPUS,
meta_OGG_LSE,
meta_XWMA,
} meta_t;

View File

@ -329,19 +329,23 @@ char * WINAPI xmplay_GetTags() {
/* main panel info text (short file info) */
void WINAPI xmplay_GetInfoText(char* format, char* length) {
int rate, samples, bps;
const char* fmt;
int t, tmin, tsec;
if (!format)
return;
if (!vgmstream)
return;
int rate = vgmstream->sample_rate;
int samples = vgmstream->num_samples;
int bps = get_vgmstream_average_bitrate(vgmstream) / 1000;
const char* fmt = get_vgmstream_coding_description(vgmstream->coding_type);
rate = vgmstream->sample_rate;
samples = vgmstream->num_samples;
bps = get_vgmstream_average_bitrate(vgmstream) / 1000;
fmt = get_vgmstream_coding_description(vgmstream->coding_type);
int t = samples / rate;
int tmin = t / 60;
int tsec = t % 60;
t = samples / rate;
tmin = t / 60;
tsec = t % 60;
sprintf(format, "%s", fmt);
sprintf(length, "%d:%02d - %dKb/s - %dHz", tmin, tsec, bps, rate);