diff --git a/src/coding/coding.h b/src/coding/coding.h index 3ec4efca..0c21a18d 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -202,9 +202,8 @@ void free_at3plus(maiatrac3plus_codec_data *data); #ifdef VGM_USE_FFMPEG /* ffmpeg_decoder */ ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); -ffmpeg_codec_data * init_ffmpeg_offset_index(STREAMFILE *streamFile, uint64_t start, uint64_t size, int stream_index); 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_index(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int stream_index); +ffmpeg_codec_data * init_ffmpeg_config(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, ffmpeg_custom_config * config); void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels); void reset_ffmpeg(VGMSTREAM *vgmstream); @@ -212,6 +211,10 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample); void free_ffmpeg(ffmpeg_codec_data *data); void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples); + + +size_t ffmpeg_make_opus_header(uint8_t * buf, int buf_size, int channels, int skip, int sample_rate); +size_t ffmpeg_get_eaxma_virtual_size(off_t real_offset, size_t real_size, STREAMFILE *streamFile); #endif /* coding_utils */ @@ -256,5 +259,4 @@ void xma2_parse_xma2_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * cha size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align); size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align); - #endif /*_CODING_H*/ diff --git a/src/coding/ffmpeg_decoder_utils_ea_xma.c b/src/coding/ffmpeg_decoder_utils_ea_xma.c new file mode 100644 index 00000000..84e69fd7 --- /dev/null +++ b/src/coding/ffmpeg_decoder_utils_ea_xma.c @@ -0,0 +1,153 @@ +#include "coding.h" +#include "ffmpeg_decoder_utils.h" + +#ifdef VGM_USE_FFMPEG + +#define EAXMA_XMA_BLOCK_SIZE 0x800 + +/** + * EA-XMA is XMA with padding removed (so a real 0x450 block would be padded to a virtual 0x800 block). + * //todo missing multichannel (packet multistream) support, unknown layout + */ + + +int ffmpeg_custom_read_eaxma(ffmpeg_codec_data *data, uint8_t *buf, int buf_size) { + uint8_t v_buf[0x8000]; /* intermediate buffer, could be simplified */ + int buf_done = 0; + uint64_t real_offset = data->real_offset; + uint64_t virtual_offset = data->virtual_offset - data->header_size; + uint64_t virtual_base = data->virtual_base; + + + /* read and transform SNS/EA-XMA block into XMA block by adding padding */ + while (buf_done < buf_size) { + int bytes_to_copy; + size_t data_size, extra_size = 0, gap_size = 0; + size_t block_size = read_32bitBE(real_offset, data->streamfile); + /* 0x04(4): some kind of size? 0x08(4): decoded samples */ + + /* setup */ + data_size = (block_size & 0x00FFFFFF) - 0x0c; //todo last block size may be slightly off? + if (data_size % EAXMA_XMA_BLOCK_SIZE) /* aligned padding */ + extra_size = EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE); + if (buf_done == 0) /* first read */ + gap_size = virtual_offset - virtual_base; /* might start a few bytes into the block */ + + if (data_size + extra_size > 0x8000) { + VGM_LOG("EA-XMA: total size bigger than buffer at %lx\n", (off_t)real_offset); + return 0; + } + + bytes_to_copy = data_size + extra_size - gap_size; + if (bytes_to_copy > buf_size - buf_done) + bytes_to_copy = buf_size - buf_done; + + /* transform */ + read_streamfile(v_buf, real_offset + 0x0c, data_size, data->streamfile); + memset(v_buf + data_size, 0xFF, extra_size); /* padding can be any value, typically 0xFF */ + memcpy(buf + buf_done, v_buf + gap_size, bytes_to_copy); + + /* move when block is fully done */ + if (data_size + extra_size == bytes_to_copy + gap_size) { + real_offset += (block_size & 0x00FFFFFF); + virtual_base += data_size + extra_size; + } + + buf_done += bytes_to_copy; + + /* exit on last block just in case, though should reach file size */ + if (block_size & 0x80000000) + break; + } + + + data->real_offset = real_offset; + data->virtual_base = virtual_base; + return buf_size; +} + +int64_t ffmpeg_custom_seek_eaxma(ffmpeg_codec_data *data, int64_t virtual_offset) { + int64_t real_offset, virtual_base; + int64_t current_virtual_offset = data->virtual_offset; + + /* Find SNS block start closest to offset. ie. virtual_offset 0x1A10 could mean SNS blocks + * of 0x456+0x820 padded to 0x800+0x1000 (base) + 0x210 (extra for reads), thus real_offset = 0xC76 */ + + if (virtual_offset > current_virtual_offset) { /* seek after current: start from current block */ + real_offset = data->real_offset; + virtual_base = data->virtual_base; + } + else { /* seek before current: start from the beginning */ + real_offset = data->real_start; + virtual_base = 0; + } + + + /* find target block */ + while (virtual_base < virtual_offset) { + size_t data_size, extra_size = 0; + size_t block_size = read_32bitBE(real_offset, data->streamfile); + + data_size = (block_size & 0x00FFFFFF) - 0x0c; + if (data_size % EAXMA_XMA_BLOCK_SIZE) + extra_size = EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE); + + /* stop if virtual_offset lands inside current block */ + if (data_size + extra_size > virtual_offset) + break; + + real_offset += (block_size & 0x00FFFFFF); + virtual_base += data_size + extra_size; + } + + /* closest we can use for reads */ + data->real_offset = real_offset; + data->virtual_base = virtual_base; + + return virtual_offset; +} + +int64_t ffmpeg_custom_size_eaxma(ffmpeg_codec_data *data) { + + uint64_t virtual_size = data->config.virtual_size; + if (!virtual_size) + return 0; + + return virtual_size + data->header_size; +} + +/* needed to know in meta for fake RIFF */ +size_t ffmpeg_get_eaxma_virtual_size(off_t real_offset, size_t real_size, STREAMFILE *streamFile) { + size_t virtual_size = 0; + + /* count all SNS/EAXMA blocks size + padding size */ + while (real_offset < real_size) { + size_t data_size; + size_t block_size = read_32bitBE(real_offset, streamFile); + + data_size = (block_size & 0x00FFFFFF) - 0x0c; + + if ((block_size & 0xFF000000) && !(block_size & 0x80000000)) { + VGM_LOG("EA-XMA: unknown flag found at %lx\n", (off_t)real_offset); + goto fail; + } + + real_offset += (block_size & 0x00FFFFFF); + + virtual_size += data_size; + if (data_size % EAXMA_XMA_BLOCK_SIZE) /* XMA block padding */ + virtual_size += EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE); + + /* exit on last block just in case, though should reach real_size */ + if (block_size & 0x80000000) + break; + } + + return virtual_size; + +fail: + return 0; + +} + +#endif diff --git a/src/meta/ea_snu.c b/src/meta/ea_snu.c index 72253333..c21a7252 100644 --- a/src/meta/ea_snu.c +++ b/src/meta/ea_snu.c @@ -1,27 +1,38 @@ #include "meta.h" #include "../layout/layout.h" +#include "../coding/coding.h" -/* .SNU - EA new-ish header (Dead Space, The Godfather 2) */ + +/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2) */ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; int channel_count, loop_flag = 0, channel_config, codec, sample_rate, flags; uint32_t num_samples, loop_start = 0, loop_end = 0; off_t start_offset; + int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; /* check extension, case insensitive */ if (!check_extensions(streamFile,"snu")) goto fail; - /* check header */ - //if ((read_32bitBE(0x00,streamFile) & 0x00FFFF00 != 0x00000000) && (read_32bitBE(0x0c,streamFile) != 0x00000000)) - // goto fail; - /* 0x00: related to sample rate?, 0x02: always 0?, 0x03: related to channels? (usually match but may be 0) */ - /* 0x04: some size, maybe >>2 ~= number of 0x4c frames (BE/LE depending on platform) */ - /* 0x08: always 0x20? (also BE/LE), 0x0c: always 0? */ + /* check header (the first 0x10 are BE/LE depending on platform) */ + /* 0x00(1): related to sample rate? (03=48000) + * 0x01(1): flags? (when set seems to be a bank and has extra data before start_offset) //todo + * 0x02(1): always 0? + * 0x03(1): channels? (usually matches but rarely may be 0) + * 0x04(4): some size, maybe >>2 ~= number of frames + * 0x08(4): start offset + * 0x0c(4): some sub-offset? (0x20, found when 0x01 is set) */ + /* use start offset as endianness flag */ + if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x00F00000) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } - start_offset = 0x20; /* first block */ + start_offset = read_32bit(0x08,streamFile); codec = read_8bit(0x10,streamFile); channel_config = read_8bit(0x11,streamFile); @@ -36,7 +47,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { } #if 0 - //todo not working ok with blocks + //todo not working ok with blocks in XAS if (flags & 0x60) { /* full loop, seen in ambient tracks */ loop_flag = 1; loop_start = 0; @@ -45,7 +56,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { #endif //channel_count = (channel_config >> 2) + 1; //todo test - /* 01/02/03 = 1 ch?, 05/06/07 = 2/3 ch?, 0d/0e/0f = 4/5 ch?, 14/15/16/17 = 6/7 ch?, 1d/1e/1f = 8 ch? */ + /* 01/02/03 = 1 ch?, 05/06/07 = 2/3 ch?, 0d/0e/0f = 4/5 ch?, 15/16/17 = 6/7 ch?, 1d/1e/1f = 8 ch? */ switch(channel_config) { case 0x00: channel_count = 1; break; case 0x04: channel_count = 2; break; @@ -68,20 +79,64 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { vgmstream->loop_end_sample = loop_end; vgmstream->meta_type = meta_EA_SNU; - vgmstream->layout_type = layout_ea_sns_blocked; switch(codec) { - case 0x04: /* "Xas1": EA-XAS (Dead Space) */ + case 0x04: /* "Xas1": EA-XAS (Dead Space) */ vgmstream->coding_type = coding_EA_XAS; + vgmstream->layout_type = layout_ea_sns_blocked; break; +#if 0 +#ifdef VGM_USE_MPEG + case 0x07: { /* "EL32S": EALayer3 v2 "S" (Dante's Inferno PS3) */ + mpeg_custom_config cfg; + off_t mpeg_start_offset = start_offset + 0x08; + + memset(&cfg, 0, sizeof(mpeg_custom_config)); + + /* layout is still blocks, but should work fine with the custom mpeg decoder */ + vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAL32S, &cfg); + if (!vgmstream->codec_data) goto fail; + + vgmstream->layout_type = layout_ea_sns_blocked; + break; + } +#endif +#endif + +#ifdef VGM_USE_FFMPEG + case 0x03: { /* "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; + + stream_size = get_streamfile_size(streamFile) - start_offset; + virtual_size = ffmpeg_get_eaxma_virtual_size(start_offset,stream_size, streamFile); + block_size = 0x8000; /* ? */ + block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0); + + bytes = ffmpeg_make_riff_xma2(buf, 0x100, vgmstream->num_samples, virtual_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); + if (bytes <= 0) goto fail; + + memset(&cfg, 0, sizeof(ffmpeg_custom_config)); + cfg.type = FFMPEG_EA_XMA; + cfg.virtual_size = virtual_size; + + vgmstream->codec_data = init_ffmpeg_config(streamFile, buf,bytes, start_offset,stream_size, &cfg); + if (!vgmstream->codec_data) goto fail; + + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } +#endif case 0x00: /* "NONE" */ case 0x01: /* not used? */ case 0x02: /* "P6B0": PCM16BE */ - case 0x03: /* "EXm0": EA-XMA */ + case 0x05: /* "EL31": EALayer3 v1 b (with PCM blocks in normal EA-frames?) */ case 0x06: /* "EL32P": EALayer3 v2 "P" */ - case 0x07: /* "EL32S": EALayer3 v2 "S" */ case 0x09: /* EASpeex? */ case 0x0c: /* EAOpus? */ case 0x0e: /* XAS variant? */ @@ -97,7 +152,8 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) { if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; - ea_sns_block_update(start_offset, vgmstream); + if (vgmstream->layout_type == layout_ea_sns_blocked) + ea_sns_block_update(start_offset, vgmstream); return vgmstream;