diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index 170e47b2..38915e1d 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -612,9 +612,10 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i msd->loop_end_sample = loop_end_frame * samples_per_frame + (msd->loop_end_subframe) * samples_per_subframe; } - //todo apply once FFmpeg decode is ok (must internal skip 64 samples + apply skips + output extra 128 IMDCT samples) and remove skip_samples output + //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; @@ -630,6 +631,14 @@ static void ms_audio_get_samples(ms_sample_data * msd, STREAMFILE *streamFile, i } } #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 + } } static int wma_get_samples_per_frame(int version, int sample_rate, uint32_t decode_flags) { @@ -744,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 } diff --git a/src/formats.c b/src/formats.c index 4d9ced92..7d046c5b 100644 --- a/src/formats.c +++ b/src/formats.c @@ -456,8 +456,8 @@ static const char* extension_list[] = { "xwb", "xmd", "xwc", - "xwm", //FFmpeg, not parsed (XWMA) - "xwma", //FFmpeg, not parsed (XWMA) + "xwm", + "xwma", "xws", "xwv", @@ -1088,6 +1088,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_XWMA, "Microsoft XWMA RIFF header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index e373b11c..426aa3e1 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1609,6 +1609,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index c4d5d4a2..5112db54 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -991,6 +991,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index f8b08446..7efc9635 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -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*/ diff --git a/src/meta/xwma.c b/src/meta/xwma.c new file mode 100644 index 00000000..689fefa0 --- /dev/null +++ b/src/meta/xwma.c @@ -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; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 6c00ac1b..d93d057c 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -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) */ diff --git a/src/vgmstream.h b/src/vgmstream.h index 842715b6..b3346201 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -698,6 +698,7 @@ typedef enum { meta_NXA, meta_ADPCM_CAPCOM, meta_UE4OPUS, + meta_XWMA, } meta_t;