mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 09:40:51 +01:00
Merge pull request #304 from bnnm/xwma-ffmpeg-lse-sps
xwma ffmpeg lse sps
This commit is contained in:
commit
8d72756f45
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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). */
|
||||
|
@ -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 */
|
||||
/* ************************************ */
|
||||
|
@ -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"},
|
||||
|
||||
};
|
||||
|
||||
|
@ -1609,6 +1609,10 @@
|
||||
<File
|
||||
RelativePath=".\meta\xwc.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\xwma.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ydsp.c"
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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*/
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
87
src/meta/xwma.c
Normal 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;
|
||||
}
|
@ -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++) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user