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;