diff --git a/src/meta/ea_eaac.c b/src/meta/ea_eaac.c index d9b82306..3dadb53e 100644 --- a/src/meta/ea_eaac.c +++ b/src/meta/ea_eaac.c @@ -8,7 +8,7 @@ #define EAAC_VERSION_V0 0x00 /* SNR/SNS */ #define EAAC_VERSION_V1 0x01 /* SPS */ -#define EAAC_CODEC_NONE 0x00 /* internal 'codec not set' flag */ +#define EAAC_CODEC_NONE 0x00 /* internal 'codec not set' */ #define EAAC_CODEC_RESERVED 0x01 /* not used/reserved? /MP30/P6L0/P2B0/P2L0/P8S0/P8U0/PFN0? */ #define EAAC_CODEC_PCM 0x02 #define EAAC_CODEC_EAXMA 0x03 @@ -26,7 +26,7 @@ #define EAAC_FLAG_STREAMED 0x04 #define EAAC_BLOCKID0_DATA 0x00 -#define EAAC_BLOCKID0_END 0x80 +#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */ #define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */ #define EAAC_BLOCKID1_DATA 0x44 /* 'D' */ @@ -36,6 +36,8 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST static size_t get_snr_size(STREAMFILE *streamFile, off_t offset); static VGMSTREAM *parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t ast_offset); + + /* .SNR+SNS - from EA latest games (~2008-2013), v0 header */ VGMSTREAM * init_vgmstream_ea_snr_sns(STREAMFILE * streamFile) { VGMSTREAM * vgmstream = NULL; @@ -131,7 +133,7 @@ fail: } /* .SPS - from Frostbite engine games, v1 header */ -VGMSTREAM * init_vgmstream_ea_sps_fb(STREAMFILE *streamFile) { +VGMSTREAM * init_vgmstream_ea_sps_fb(STREAMFILE *streamFile) { //todo remove in the future, use better extractors VGMSTREAM * vgmstream = NULL; off_t start_offset = 0, header_offset = 0, sps_offset, max_offset; @@ -459,6 +461,30 @@ fail: return NULL; } +/* ************************************************************************* */ + +typedef struct { + int version; + int codec; + int channel_config; + int sample_rate; + int flags; + + int streamed; + int channels; + + int num_samples; + int loop_start; + int loop_end; + int loop_flag; + + off_t stream_offset; + off_t loop_offset; +} eaac_header; + +static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac); + + /* EA newest header from RwAudioCore (RenderWare?) / EAAudioCore library (still generated by sx.exe). * Audio "assets" come in separate RAM headers (.SNR/SPH) and raw blocked streams (.SNS/SPS), * or together in pseudoformats (.SNU, .SBR+.SBS banks, .AEMS, .MUS, etc). @@ -466,91 +492,108 @@ fail: static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type) { VGMSTREAM * vgmstream = NULL; STREAMFILE* temp_streamFile = NULL; - int channel_count, loop_flag = 0, streamed, version, codec, channel_config, flags; - int32_t header1, header2, sample_rate, num_samples, loop_start = 0, loop_end = 0; + uint32_t header1, header2; + eaac_header eaac = {0}; + /* EA SNR/SPH header */ - /* 4 bits: version */ - /* 4 bits: codec */ - /* 6 bits: channel config */ - /* 18 bits: sample rate */ - /* 4 bits: flags */ - /* 28 bits: number of samples */ - header1 = read_32bitBE(header_offset + 0x00, streamHead); - header2 = read_32bitBE(header_offset + 0x04, streamHead); - version = (header1 >> 28) & 0x0F; - codec = (header1 >> 24) & 0x0F; - channel_config = (header1 >> 18) & 0x3F; - sample_rate = (header1 & 0x03FFFF); /* some Dead Space 2 (PC) uses 96000 */ - flags = (header2 >> 28) & 0x0F; /* TODO: maybe even 3 bits and not 4? */ - num_samples = (header2 & 0x0FFFFFFF); - /* rest is optional, depends on flags header used (ex. SNU and SPS may have bigger headers): */ - /* 0x02: loop start sample, 0x00/04: nothing, 0x06: loop start sample and loop start block offset */ + header1 = (uint32_t)read_32bitBE(header_offset + 0x00, streamHead); + header2 = (uint32_t)read_32bitBE(header_offset + 0x04, streamHead); + eaac.version = (header1 >> 28) & 0x0F; /* 4 bits */ + eaac.codec = (header1 >> 24) & 0x0F; /* 4 bits */ + eaac.channel_config = (header1 >> 18) & 0x3F; /* 6 bits */ + eaac.sample_rate = (header1 & 0x03FFFF); /* 18 bits (some Dead Space 2 (PC) do use 96000) */ + eaac.flags = (header2 >> 28) & 0x0F; /* 4 bits *//* TODO: maybe even 3 bits and not 4? */ + eaac.num_samples = (header2 & 0x0FFFFFFF); /* 28 bits */ + /* rest is optional, depends on used flags and codec (handled below) */ + eaac.stream_offset = start_offset; - /* V0: SNR+SNS, V1: SPR+SPS (no apparent differences, other than the block flags used) */ - if (version != EAAC_VERSION_V0 && version != EAAC_VERSION_V1) { - VGM_LOG("EA SNS/SPS: unknown version\n"); + + /* V0: SNR+SNS, V1: SPR+SPS (no apparent differences, other than block flags) */ + if (eaac.version != EAAC_VERSION_V0 && eaac.version != EAAC_VERSION_V1) { + VGM_LOG("EA EAAC: unknown version\n"); goto fail; } - if (flags != EAAC_FLAG_NONE && - !(flags & (EAAC_FLAG_LOOPED | EAAC_FLAG_STREAMED))) { - VGM_LOG("EA SNS/SPS: unknown flags 0x%02x\n", flags); + /* catch unknown/garbage values just in case */ + if (eaac.flags != EAAC_FLAG_NONE && !(eaac.flags & (EAAC_FLAG_LOOPED | EAAC_FLAG_STREAMED))) { + VGM_LOG("EA EAAC: unknown flags 0x%02x\n", eaac.flags); goto fail; } - /* TODO: Properly implement looping, needed for Need for Speed: World (PC) */ - if (flags & EAAC_FLAG_LOOPED) { - loop_flag = 1; - loop_start = 0; - loop_end = num_samples; - } - - /* Non-streamed sounds are stored as a single block */ - streamed = (flags & EAAC_FLAG_STREAMED) != 0; + /* get loops (fairly involved due to the multiple layouts and mutant streamfiles) + * full loops aren't too uncommon [Dead Space (PC) sfx/ambiance], + * while actual looping is very rare [Need for Speed: World (PC)] */ + if (eaac.flags & EAAC_FLAG_LOOPED) { + eaac.loop_flag = 1; - /* accepted channel configs only seem to be mono/stereo/quad/5.1/7.1 */ - /* fail with unknown values just in case */ - switch(channel_config) { - case 0x00: channel_count = 1; break; - case 0x01: channel_count = 2; break; - case 0x03: channel_count = 4; break; - case 0x05: channel_count = 6; break; - case 0x07: channel_count = 8; break; - default: - VGM_LOG("EA SNS/SPS: unknown channel config 0x%02x\n", channel_config); + eaac.loop_start = read_32bitBE(header_offset+0x08, streamHead); + eaac.loop_end = eaac.num_samples; + + //todo header size always depends on flags or is implicit by loop values? see get_snr_size + if (eaac.loop_start > 0) + eaac.loop_offset = read_32bitBE(header_offset+0x0c, streamHead); /* normal loop */ + else + eaac.loop_offset = eaac.stream_offset; /* full loop */ + + //todo EATrax has extra values in header, which would coexist with loop values + if (eaac.codec == EAAC_CODEC_EATRAX) { + VGM_LOG("EA EAAC: unknown loop header for EATrax\n"); goto fail; + } + + //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"); + goto fail; + } + } + + /* Non-streamed sounds are stored as a single block (may not set block end flags) */ + eaac.streamed = (eaac.flags & EAAC_FLAG_STREAMED) != 0; + + /* accepted channel configs only seem to be mono/stereo/quad/5.1/7.1, from debug strings */ + switch(eaac.channel_config) { + case 0x00: eaac.channels = 1; break; + case 0x01: eaac.channels = 2; break; + case 0x03: eaac.channels = 4; break; + case 0x05: eaac.channels = 6; break; + case 0x07: eaac.channels = 8; break; + default: + VGM_LOG("EA EAAC: unknown channel config 0x%02x\n", eaac.channel_config); + goto fail; /* fail with unknown values just in case */ } /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(eaac.channels,eaac.loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; + vgmstream->sample_rate = eaac.sample_rate; + vgmstream->num_samples = eaac.num_samples; + vgmstream->loop_start_sample = eaac.loop_start; + vgmstream->loop_end_sample = eaac.loop_end; vgmstream->meta_type = meta_type; /* EA decoder list and known internal FourCCs */ - switch(codec) { + switch(eaac.codec) { - case EAAC_CODEC_PCM: /* "P6B0": PCM16BE [NBA Jam (Wii)] */ + case EAAC_CODEC_PCM: /* "P6B0": PCM16BE [NBA Jam (Wii)] */ vgmstream->coding_type = coding_PCM16_int; vgmstream->codec_endian = 1; vgmstream->layout_type = layout_blocked_ea_sns; break; #ifdef VGM_USE_FFMPEG - case EAAC_CODEC_EAXMA: { /* "EXm0": EA-XMA [Dante's Inferno (X360)] */ + case EAAC_CODEC_EAXMA: { /* "EXm0": EA-XMA [Dante's Inferno (X360)] */ uint8_t buf[0x100]; int bytes, block_size, block_count; size_t stream_size, virtual_size; ffmpeg_custom_config cfg = {0}; - stream_size = get_streamfile_size(streamData) - start_offset; - virtual_size = ffmpeg_get_eaxma_virtual_size(vgmstream->channels, streamed, start_offset,stream_size, streamData); + stream_size = get_streamfile_size(streamData) - eaac.stream_offset; //todo not correct for banks + virtual_size = ffmpeg_get_eaxma_virtual_size(vgmstream->channels, eaac.streamed, eaac.stream_offset,stream_size, streamData); block_size = 0x10000; /* todo unused and not correctly done by the parser */ block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0); @@ -561,7 +604,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST cfg.virtual_size = virtual_size; cfg.channels = vgmstream->channels; - vgmstream->codec_data = init_ffmpeg_config(streamData, buf,bytes, start_offset,stream_size, &cfg); + vgmstream->codec_data = init_ffmpeg_config(streamData, buf,bytes, eaac.stream_offset,stream_size, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; @@ -570,46 +613,60 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST } #endif - case EAAC_CODEC_XAS: /* "Xas1": EA-XAS [Dead Space (PC/PS3)] */ + case EAAC_CODEC_XAS: /* "Xas1": EA-XAS [Dead Space (PC/PS3)] */ vgmstream->coding_type = coding_EA_XAS; vgmstream->layout_type = layout_blocked_ea_sns; break; #ifdef VGM_USE_MPEG - case EAAC_CODEC_EALAYER3_V1: /* "EL31": EALayer3 v1 [Need for Speed: Hot Pursuit (PS3)] */ - case EAAC_CODEC_EALAYER3_V2_PCM: /* "L32P": EALayer3 v2 "PCM" [Battlefield 1943 (PS3)] */ - case EAAC_CODEC_EALAYER3_V2_SPIKE: { /* "L32S": EALayer3 v2 "Spike" [Dante's Inferno (PS3)] */ + case EAAC_CODEC_EALAYER3_V1: /* "EL31": EALayer3 v1 [Need for Speed: Hot Pursuit (PS3)] */ + case EAAC_CODEC_EALAYER3_V2_PCM: /* "L32P": EALayer3 v2 "PCM" [Battlefield 1943 (PS3)] */ + case EAAC_CODEC_EALAYER3_V2_SPIKE: { /* "L32S": EALayer3 v2 "Spike" [Dante's Inferno (PS3)] */ mpeg_custom_config cfg = {0}; - mpeg_custom_t type = (codec == 0x05 ? MPEG_EAL31b : (codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); + mpeg_custom_t type = (eaac.codec == 0x05 ? MPEG_EAL31b : (eaac.codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); - /* remove blocks on reads for some edge cases in L32P and to properly apply discard modes - * (otherwise, and removing discards, it'd work with layout_blocked_ea_sns) */ - temp_streamFile = setup_eaac_streamfile(streamData, version, codec, streamed, start_offset, 0); - if (!temp_streamFile) goto fail; + /* EALayer3 needs custom IO that removes blocks on reads to fix some edge cases in L32P + * and to properly apply discard modes (see ealayer3 decoder) + * (otherwise, and after removing discard code, it'd work with layout_blocked_ea_sns) */ start_offset = 0x00; /* must point to the custom streamfile's beginning */ - /* layout is still blocks, but should work fine with the custom mpeg decoder */ - vgmstream->codec_data = init_mpeg_custom(temp_streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, type, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->layout_type = layout_none; + if (eaac.loop_start > 0) { /* special (if hacky) loop handling, see comments */ + 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 { + temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed, eaac.stream_offset,0); + if (!temp_streamFile) goto fail; + + vgmstream->codec_data = init_mpeg_custom(temp_streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, type, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + } break; } #endif - case EAAC_CODEC_DSP: /* "Gca0"?: DSP [Need for Speed: Nitro sfx (Wii)] */ + case EAAC_CODEC_DSP: /* "Gca0"?: DSP [Need for Speed: Nitro (Wii) sfx] */ vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_blocked_ea_sns; /* DSP coefs are read in the blocks */ break; #ifdef VGM_USE_ATRAC9 - case EAAC_CODEC_EATRAX: { /* EATrax */ + case EAAC_CODEC_EATRAX: { /* EATrax (unknown FourCC) [Need for Speed: Most Wanted (Vita)] */ atrac9_config cfg = {0}; size_t total_size; - cfg.channels = vgmstream->channels; + /* EATrax is "buffered" ATRAC9, uses custom IO since it's kind of complex to add to the decoder */ + + start_offset = 0x00; /* must point to the custom streamfile's beginning */ + + cfg.channels = eaac.channels; cfg.config_data = read_32bitBE(header_offset + 0x08,streamHead); /* 0x10: frame size? (same as config data?) */ total_size = read_32bitLE(header_offset + 0x0c,streamHead); /* actual data size without blocks, LE b/c why make sense */ @@ -619,11 +676,9 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_none; - /* EATrax is "buffered" ATRAC9, uses custom IO since it's kind of complex to add to the decoder */ - temp_streamFile = setup_eaac_streamfile(streamData, version, codec, streamed, start_offset, total_size); + temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed, eaac.stream_offset, total_size); if (!temp_streamFile) goto fail; - start_offset = 0x00; /* must point to the custom streamfile's beginning */ break; } #endif @@ -633,7 +688,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST case EAAC_CODEC_EAOPUS: /* EAOpus (inside each SNS/SPS block is 16b frame size + standard? Opus packet) */ /* TODO */ default: - VGM_LOG("EA SNS/SPS: unknown codec 0x%02x\n", codec); + VGM_LOG("EA EAAC: unknown codec 0x%02x\n", eaac.codec); goto fail; } @@ -660,3 +715,73 @@ static size_t get_snr_size(STREAMFILE *streamFile, off_t offset) { default: return 0x08; } } + + +/* 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). */ +// 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 sizes[2] = { eaac->stream_offset + eaac->loop_offset, 0}; /* 0 = let streamfile guess */ + int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start}; + int segment_count = 2; /* intro/loop */ + int i; + + + /* init layout */ + data = init_layout_segmented(segment_count); + if (!data) goto fail; + + for (i = 0; i < segment_count; i++) { + temp_streamFile[i] = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed, offsets[i], sizes[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; + data->segments[i]->num_samples = num_samples[i]; + //data->segments[i]->meta_type = eaac->meta_type; /* bleh */ + + switch(eaac->codec) { +#ifdef VGM_USE_MPEG + case EAAC_CODEC_EALAYER3_V1: + case EAAC_CODEC_EALAYER3_V2_PCM: + case EAAC_CODEC_EALAYER3_V2_SPIKE: { + mpeg_custom_config cfg = {0}; + mpeg_custom_t type = (eaac->codec == 0x05 ? MPEG_EAL31b : (eaac->codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); + + 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; + break; + } +#endif + default: + goto fail; + } + + if (!vgmstream_open_stream(data->segments[i],temp_streamFile[i],0x00)) + goto fail; + } + + /* setup segmented VGMSTREAMs */ + if (!setup_layout_segmented(data)) + goto fail; + data->loop_segment = 1; + + return data; + +fail: + for (i = 0; i < segment_count; i++) + close_streamfile(temp_streamFile[i]); + free_layout_segmented(data); + return NULL; +}