From 58b6b16e3a6a54b64f168538cb4ba981854cc1ff Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 11 Sep 2021 13:11:29 +0200 Subject: [PATCH] Tweak some XWMA total samples --- src/coding/coding.h | 4 +- src/coding/coding_utils.c | 28 +++++++ src/coding/ffmpeg_decoder_utils.c | 35 +++++++++ src/meta/fsb5.c | 14 ++-- src/meta/wwise.c | 66 ++++++----------- src/meta/xwma.c | 118 ++++++++++++++++++------------ 6 files changed, 164 insertions(+), 101 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index d003d748..f3f7ffd8 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -592,7 +592,7 @@ STREAMFILE* ffmpeg_get_streamfile(ffmpeg_codec_data* data); ffmpeg_codec_data* init_ffmpeg_atrac3_raw(STREAMFILE* sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay); ffmpeg_codec_data* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* out_samples); ffmpeg_codec_data* init_ffmpeg_aac(STREAMFILE* sf, off_t offset, size_t size, int skip_samples); - +ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size); /* ffmpeg_decoder_custom_opus.c (helper-things) */ typedef struct { @@ -663,6 +663,8 @@ typedef struct { void xma_get_samples(ms_sample_data* msd, STREAMFILE* sf); void wmapro_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags); void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int sample_rate, uint32_t decode_flags); +int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size); +int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be); void xma1_parse_fmt_chunk(STREAMFILE* sf, off_t chunk_offset, int* channels, int* sample_rate, int* loop_flag, int32_t* loop_start_b, int32_t* loop_end_b, int32_t* loop_subframe, int be); void xma2_parse_fmt_chunk_extra(STREAMFILE* sf, off_t chunk_offset, int* loop_flag, int32_t* out_num_samples, int32_t* out_loop_start_sample, int32_t* out_loop_end_sample, int be); diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index 1252fb2d..3cecf876 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -760,6 +760,34 @@ void wma_get_samples(ms_sample_data* msd, STREAMFILE* sf, int block_align, int s #endif } +int32_t xwma_get_samples(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int block_size) { + /* manually find total samples, why don't they put this in the header is beyond me */ + ms_sample_data msd = {0}; + + msd.channels = channels; + msd.data_offset = data_offset; + msd.data_size = data_size; + + if (format == 0x0162) + wmapro_get_samples(&msd, sf, block_size, sample_rate, 0x00E0); + else + wma_get_samples(&msd, sf, block_size, sample_rate, 0x001F); + + return msd.num_samples; +} + +int32_t xwma_dpds_get_samples(STREAMFILE* sf, uint32_t dpds_offset, uint32_t dpds_size, int channels, int be) { + int32_t (*read_s32)(off_t,STREAMFILE*) = be ? read_s32be : read_s32le; + uint32_t offset; + if (!dpds_offset || !dpds_size || !channels) + return 0; + + offset = dpds_offset + (dpds_size - 0x04); /* last entry */ + /* XWMA's seek table ("dpds") contains max decoded bytes (after encoder delay), checked vs xWMAEncode. + * WMAPRO usually encodes a few more tail samples though (see xwma_get_samples). */ + return read_s32(offset, sf) / channels / sizeof(int16_t); /* in PCM16 bytes */ +} + /* XMA hell for precise looping and gapless support, fixes raw sample values from headers * that don't count XMA's final subframe/encoder delay/encoder padding, and FFmpeg stuff. diff --git a/src/coding/ffmpeg_decoder_utils.c b/src/coding/ffmpeg_decoder_utils.c index c2d17088..38fc0d38 100644 --- a/src/coding/ffmpeg_decoder_utils.c +++ b/src/coding/ffmpeg_decoder_utils.c @@ -203,4 +203,39 @@ fail: return NULL; } +//TODO: make init_ffmpeg_xwma_fmt(be) too to pass fmt chunk + +ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size) { + ffmpeg_codec_data* data = NULL; + uint8_t buf[0x100]; + int bytes; + + bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), format, data_size, channels, sample_rate, avg_bitrate, block_size); + data = init_ffmpeg_header_offset(sf, buf,bytes, data_offset, data_size); + if (!data) goto fail; + + if (format == 0x161) { + int skip_samples = 0; + + /* Skip WMA encoder delay, not specified in the flags or containers (ASF/XWMA), + * but verified compared to Microsoft's output. Seems to match frame_samples * 2 */ + if (sample_rate >= 32000) + skip_samples = 4096; + else if (sample_rate >= 22050) + skip_samples = 2048; + else if (sample_rate >= 8000) + skip_samples = 1024; + + ffmpeg_set_skip_samples(data, skip_samples); + } + + //TODO WMAPro uses variable skips and is more complex + //TODO ffmpeg's WMA doesn't properly output trailing samples (ignored patch...) + + return data; +fail: + free_ffmpeg(data); + return NULL; +} + #endif diff --git a/src/meta/fsb5.c b/src/meta/fsb5.c index 024fe6c1..24c9b801 100644 --- a/src/meta/fsb5.c +++ b/src/meta/fsb5.c @@ -454,18 +454,16 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) { #ifdef VGM_USE_FFMPEG case 0x0E: { /* FMOD_SOUND_FORMAT_XWMA [from fsbankex tests, no known games] */ - uint8_t buf[0x100]; - int bytes, format, average_bps, block_align; + int format,avg_bitrate, block_size; - format = read_u16be(fsb5.extradata_offset+0x00,sf); - block_align = read_u16be(fsb5.extradata_offset+0x02,sf); - average_bps = read_u32be(fsb5.extradata_offset+0x04,sf); + format = read_u16be(fsb5.extradata_offset+0x00,sf); + block_size = read_u16be(fsb5.extradata_offset+0x02,sf); + avg_bitrate = read_u32be(fsb5.extradata_offset+0x04,sf); /* rest: seek entries + mini seek table? */ /* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */ - bytes = ffmpeg_make_riff_xwma(buf,0x100, format, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, average_bps, block_align); - vgmstream->codec_data = init_ffmpeg_header_offset(sb, buf,bytes, fsb5.stream_offset, fsb5.stream_size); - if ( !vgmstream->codec_data ) goto fail; + vgmstream->codec_data = init_ffmpeg_xwma(sf, fsb5.stream_offset, fsb5.stream_size, format, fsb5.channels, fsb5.sample_rate, avg_bitrate, block_size); + if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; diff --git a/src/meta/wwise.c b/src/meta/wwise.c index d48a6315..ee8c1ec3 100644 --- a/src/meta/wwise.c +++ b/src/meta/wwise.c @@ -38,8 +38,8 @@ typedef struct { int format; int channels; int sample_rate; - int block_align; - int average_bps; + int block_size; + int avg_bitrate; int bits_per_sample; uint8_t channel_type; uint32_t channel_layout; @@ -121,11 +121,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { if (ww.fmt_size != 0x14 && ww.fmt_size != 0x28 && ww.fmt_size != 0x18) goto fail; /* oldest, old, new */ if (ww.bits_per_sample != 4) goto fail; - if (ww.block_align != 0x24 * ww.channels) goto fail; + if (ww.block_size != 0x24 * ww.channels) goto fail; vgmstream->coding_type = coding_WWISE_IMA; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = ww.block_align / ww.channels; + vgmstream->interleave_block_size = ww.block_size / ww.channels; vgmstream->codec_endian = ww.big_endian; /* oldest version uses regular XBOX IMA with stereo mode [Shadowrun (PC)] */ @@ -155,7 +155,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { cfg.sample_rate = ww.sample_rate; cfg.big_endian = ww.big_endian; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */ + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */ /* autodetect format (fields are mostly common, see the end of the file) */ if (ww.vorb_offset) { @@ -305,7 +305,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x08; /* ww.block_align = 0x8 in older Wwise, samples per block in newer Wwise */ + vgmstream->interleave_block_size = 0x08; /* ww.block_size = 0x8 in older Wwise, samples per block in newer Wwise */ /* find coef position */ if (ww.wiih_offset) { /* older */ @@ -382,40 +382,18 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { } case XWMA: { /* X360 */ - ffmpeg_codec_data *ffmpeg_data = NULL; - uint8_t buf[0x100]; - int bytes; - if (ww.fmt_size != 0x18) goto fail; if (!ww.big_endian) goto fail; /* must be from Wwise X360 (PC LE XWMA is parsed elsewhere) */ - bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), ww.format, ww.data_size, ww.channels, ww.sample_rate, ww.average_bps, ww.block_align); - ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset, ww.data_size); - if ( !ffmpeg_data ) goto fail; - vgmstream->codec_data = ffmpeg_data; + vgmstream->codec_data = init_ffmpeg_xwma(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.avg_bitrate, ww.block_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 = ww.channels; - msd.data_offset = ww.data_offset; - msd.data_size = ww.data_size; - - if (ww.format == 0x0162) - wmapro_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x00E0); - else - wma_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x001F); - - vgmstream->num_samples = msd.num_samples; - if (!vgmstream->num_samples) - vgmstream->num_samples = ffmpeg_get_samples(ffmpeg_data); /* very wrong, from avg-br */ - //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 - } - + /* seek table seems BE dpds */ + vgmstream->num_samples = xwma_dpds_get_samples(sf, ww.seek_offset, ww.seek_size, ww.channels, ww.big_endian); + if (!vgmstream->num_samples) + vgmstream->num_samples = xwma_get_samples(sf, ww.data_offset, ww.data_size, ww.format, ww.channels, ww.sample_rate, ww.block_size); break; } @@ -423,7 +401,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { ffmpeg_codec_data * ffmpeg_data = NULL; if (ww.fmt_size != 0x24) goto fail; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* extra: size 0x12, unknown values */ @@ -442,7 +420,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { size_t seek_size; if (ww.fmt_size != 0x28) goto fail; - /* values up to 0x14 seem fixed and similar to HEVAG's (block_align 0x02/04, bits_per_sample 0x10) */ + /* values up to 0x14 seem fixed and similar to HEVAG's (block_size 0x02/04, bits_per_sample 0x10) */ vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf); /* 0x1c: null? @@ -476,7 +454,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { } case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile), Gears 5 (PC)] */ - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; /* extra: size 0x12 */ vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf); @@ -526,7 +504,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { int mapping; opus_config cfg = {0}; - if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; + if (ww.block_size != 0 || ww.bits_per_sample != 0) goto fail; if (!ww.seek_offset) goto fail; if (ww.channels > 8) goto fail; /* mapping not defined */ @@ -596,7 +574,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { case HEVAG: /* PSV */ /* changed values, another bizarre Wwise quirk */ - //ww.block_align /* unknown (1ch=2, 2ch=4) */ + //ww.block_size /* unknown (1ch=2, 2ch=4) */ //ww.bits_per_sample; /* unknown (0x10) */ //if (ww.bits_per_sample != 4) goto fail; @@ -647,11 +625,11 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) { case PTADPCM: /* newer ADPCM [Bayonetta 2 (Switch), Genshin Impact (PC)] */ if (ww.bits_per_sample != 4) goto fail; - if (ww.block_align != 0x24 * ww.channels && ww.block_align != 0x104 * ww.channels) goto fail; + if (ww.block_size != 0x24 * ww.channels && ww.block_size != 0x104 * ww.channels) goto fail; vgmstream->coding_type = coding_PTADPCM; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = ww.block_align / ww.channels; + vgmstream->interleave_block_size = ww.block_size / ww.channels; //vgmstream->codec_endian = ww.big_endian; //? if (ww.truncated) { @@ -827,8 +805,8 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { ww->format = read_u16(ww->fmt_offset + 0x00,sf); ww->channels = read_u16(ww->fmt_offset + 0x02,sf); ww->sample_rate = read_u32(ww->fmt_offset + 0x04,sf); - ww->average_bps = read_u32(ww->fmt_offset + 0x08,sf); - ww->block_align = read_u16(ww->fmt_offset + 0x0c,sf); + ww->avg_bitrate = read_u32(ww->fmt_offset + 0x08,sf); + ww->block_size = read_u16(ww->fmt_offset + 0x0c,sf); ww->bits_per_sample = read_u16(ww->fmt_offset + 0x0e,sf); if (ww->fmt_size > 0x10 && ww->format != 0x0165 && ww->format != 0x0166) /* ignore XMAWAVEFORMAT */ ww->extra_size = read_u16(ww->fmt_offset + 0x10,sf); @@ -900,7 +878,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) { /* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */ ww->codec = DSP; } - else if (ww->block_align == 0x104 * ww->channels) { + else if (ww->block_size == 0x104 * ww->channels) { /* Bayonetta 2 (Switch) */ ww->codec = PTADPCM; } diff --git a/src/meta/xwma.c b/src/meta/xwma.c index c57ba915..acd924dd 100644 --- a/src/meta/xwma.c +++ b/src/meta/xwma.c @@ -1,79 +1,101 @@ #include "meta.h" #include "../coding/coding.h" +#include "../util/chunks.h" + +typedef struct { + uint32_t data_offset; + uint32_t data_size; + uint32_t dpds_offset; + uint32_t dpds_size; + + int loop_flag; + + int format; + int channels; + int sample_rate; + int bytes; + int avg_bitrate; + int block_size; +} xwma_header_t; + /* 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; +VGMSTREAM* init_vgmstream_xwma(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + xwma_header_t xwma = {0}; /* checks */ + if (!is_id32be(0x00,sf, "RIFF")) + goto fail; + if (!is_id32be(0x08,sf, "XWMA")) + goto fail; /* .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" */ + if (!check_extensions(sf, "xwma,xwm")) 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; + { + enum { + CHUNK_fmt = 0x666d7420, /* "fmt " */ + CHUNK_data = 0x64617461, /* "data" */ + CHUNK_dpds = 0x64706473, /* "dpds" */ + }; + chunk_t rc = {0}; - channel_count = read_16bitLE(fmt_offset+0x02,streamFile); - loop_flag = 0; + rc.current = 0x0c; + while (next_chunk(&rc, sf)) { + switch(rc.type) { + case CHUNK_fmt: + xwma.format = read_u16le(rc.offset+0x00, sf); + xwma.channels = read_u16le(rc.offset+0x02, sf); + xwma.sample_rate = read_u32le(rc.offset+0x04, sf); + xwma.avg_bitrate = read_u32le(rc.offset+0x08, sf); + xwma.block_size = read_u16le(rc.offset+0x0c, sf); + break; + + case CHUNK_data: + xwma.data_offset = rc.offset; + xwma.data_size = rc.size; + break; + + case CHUNK_dpds: + xwma.dpds_offset = rc.offset; + xwma.dpds_size = rc.size; + break; + + default: + break; + } + } + if (!xwma.format || !xwma.data_offset) + goto fail; + } /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); + vgmstream = allocate_vgmstream(xwma.channels, xwma.loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(fmt_offset+0x04, streamFile); vgmstream->meta_type = meta_XWMA; + vgmstream->sample_rate = xwma.sample_rate; /* 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) + * - fix XWMA with buggy bit rates so FFmpeg can play them ok + * - remove 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); + vgmstream->codec_data = init_ffmpeg_xwma(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.avg_bitrate, xwma.block_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 = ffmpeg_get_samples(vgmstream->codec_data); /* from avg-br */ - //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 - } + /* try from (optional) seek table, or (less accurate) manual count */ + vgmstream->num_samples = xwma_dpds_get_samples(sf, xwma.dpds_offset, xwma.dpds_size, xwma.channels, 0); + if (!vgmstream->num_samples) + vgmstream->num_samples = xwma_get_samples(sf, xwma.data_offset, xwma.data_size, xwma.format, xwma.channels, xwma.sample_rate, xwma.block_size); } #else goto fail;