mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-29 19:37:30 +01:00
Fix multichannel EA-XMA [Dante's Inferno (X360)]
This commit is contained in:
parent
8dfac7c465
commit
8aae6ed794
@ -214,7 +214,7 @@ 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);
|
||||
size_t ffmpeg_get_eaxma_virtual_size(int channels, off_t real_offset, size_t real_size, STREAMFILE *streamFile);
|
||||
#endif
|
||||
|
||||
/* coding_utils */
|
||||
|
@ -3,58 +3,121 @@
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
#define EAXMA_XMA_BLOCK_SIZE 0x800
|
||||
#define EAXMA_XMA_MAX_PACKETS_PER_SNS_BLOCK 3 /* only seen up to 3 (Dante's Inferno) */
|
||||
#define EAXMA_XMA_MAX_STREAMS_PER_SNS_BLOCK 4 /* XMA2 max is 8ch = 4 * 2ch */
|
||||
#define EAXMA_XMA_PACKET_SIZE 0x800
|
||||
#define EAXMA_XMA_BUFFER_SIZE (EAXMA_XMA_MAX_PACKETS_PER_SNS_BLOCK * EAXMA_XMA_MAX_STREAMS_PER_SNS_BLOCK * EAXMA_XMA_PACKET_SIZE)
|
||||
|
||||
/**
|
||||
* 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
|
||||
* EA-XMA is XMA2 with padding removed (so a real 0x450 block would be padded to a virtual 0x800 block).
|
||||
* Each EA-XMA SNS block contains 1~3 packets per stream, and multistream uses fully separate streams
|
||||
* (no packet_skip set). We'll pad and reinterleave packets so it resembles standard XMA2.
|
||||
*
|
||||
* XMA2 data layout (XMA1 is the same but doesn't use blocks, they are only for seeking):
|
||||
* - frames (containing 1..4 subframes): decode into 128*4 samples
|
||||
* - packets: size 0x800, containing N frames (last frame can spill into next packet), must be padded
|
||||
* - blocks: fixed size, containing N packets (last packet's frames won't spill into next block)
|
||||
* - stream: N interleaved packets (1/2ch) for multichannel (Nch) audio. Interleave is not fixed:
|
||||
* at file start/new block has one packet per stream, then must follow the "packet_skip" value
|
||||
* in the XMA packet header to find its next packet (skiping packets from other streams).
|
||||
* ex.: s1_p1 skip1, s2_p1 skip2, s1_p2 skip0 s1_p3 skip1, s2_p2 skip1, s1_p4...
|
||||
*/
|
||||
|
||||
static int get_block_max_packets(int num_streams, off_t packets_offset, STREAMFILE * streamfile);
|
||||
|
||||
|
||||
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 */
|
||||
uint8_t v_buf[EAXMA_XMA_BUFFER_SIZE]; /* 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;
|
||||
/* EA-XMA always uses late XMA2 streams (2ch + ... + 1/2ch) */
|
||||
int num_streams = (data->config.channels / 2) + (data->config.channels % 2 ? 1 : 0);
|
||||
|
||||
|
||||
/* read and transform SNS/EA-XMA block into XMA block by adding padding */
|
||||
/* read and transform SNS/EA-XMA blocks into XMA packets */
|
||||
while (buf_done < buf_size) {
|
||||
int bytes_to_copy;
|
||||
size_t data_size, extra_size = 0, gap_size = 0;
|
||||
int s, p, bytes_to_copy, max_packets;
|
||||
size_t data_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 */
|
||||
/* 0x04(4): decoded samples */
|
||||
off_t packets_offset = real_offset + 0x08;
|
||||
|
||||
/* 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 */
|
||||
max_packets = get_block_max_packets(num_streams, packets_offset, data->streamfile);
|
||||
if (max_packets == 0) goto fail;
|
||||
|
||||
if (data_size + extra_size > 0x8000) {
|
||||
VGM_LOG("EA-XMA: total size bigger than buffer at %lx\n", (off_t)real_offset);
|
||||
return 0;
|
||||
if (max_packets * num_streams * EAXMA_XMA_PACKET_SIZE > EAXMA_XMA_BUFFER_SIZE) {
|
||||
VGM_LOG("EA XMA: block too big at %lx\n", (off_t)real_offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes_to_copy = data_size + extra_size - gap_size;
|
||||
/* data is divided into a sub-block per stream (N packets), can be smaller than block_size (= has padding)
|
||||
* copy XMA data re-interleaving for multichannel. To simplify some calcs fills the same number of packets
|
||||
* per stream and adjusts packet headers (see above for XMA2 multichannel layout). */
|
||||
//to-do this doesn't make correct blocks sizes (but blocks are not needed to decode)
|
||||
for (s = 0; s < num_streams; s++) {
|
||||
size_t packets_size;
|
||||
size_t packets_size4 = read_32bitBE(packets_offset, data->streamfile); /* size * 4, no idea */
|
||||
|
||||
packets_size = (packets_size4 / 4) - 0x04;
|
||||
|
||||
/* Re-interleave all packets in order, one per stream. If one stream has more packets than
|
||||
* others we add empty packets to keep the same number for all, avoiding packet_skip calcs */
|
||||
for (p = 0; p < max_packets; p++) {
|
||||
off_t packet_offset = packets_offset + 0x04 + p * EAXMA_XMA_PACKET_SIZE; /* can be off but will copy 0 */
|
||||
off_t v_buf_offset = p * EAXMA_XMA_PACKET_SIZE * num_streams + s * EAXMA_XMA_PACKET_SIZE;
|
||||
size_t packet_to_do = packets_size - p * EAXMA_XMA_PACKET_SIZE;
|
||||
size_t extra_size = 0;
|
||||
uint32_t header;
|
||||
|
||||
if (packets_size < p * EAXMA_XMA_PACKET_SIZE)
|
||||
packet_to_do = 0; /* empty packet */
|
||||
else if (packet_to_do > EAXMA_XMA_PACKET_SIZE)
|
||||
packet_to_do = EAXMA_XMA_PACKET_SIZE;
|
||||
|
||||
/* padding will be full size if packet_to_do is 0 */
|
||||
if (packet_to_do < EAXMA_XMA_PACKET_SIZE)
|
||||
extra_size = EAXMA_XMA_PACKET_SIZE - (packet_to_do % EAXMA_XMA_PACKET_SIZE);
|
||||
|
||||
/* copy data (or fully pad if empty packet) */
|
||||
read_streamfile(v_buf + v_buf_offset, packet_offset, packet_to_do, data->streamfile);
|
||||
memset(v_buf + v_buf_offset + packet_to_do, 0xFF, extra_size); /* add padding, typically 0xFF */
|
||||
|
||||
/* rewrite packet header to add packet skips for multichannel (EA XMA streams are fully separate and have none)
|
||||
* header bits: 6=num_frames, 15=first_frame_bits_offset, 3=metadata, 8=packet_skip */
|
||||
if (packet_to_do == 0)
|
||||
header = 0x3FFF800; /* new empty packet header (0 num_frames, first_frame_bits_offset set to max) */
|
||||
else
|
||||
header = (uint32_t)read_32bitBE(packet_offset, data->streamfile);
|
||||
|
||||
/* get base header + change packet_skip since we know interleave is always 1 packet per stream */
|
||||
header = (header & 0xFFFFFF00) | ((header & 0x000000FF) + num_streams - 1);
|
||||
put_32bitBE(v_buf + v_buf_offset, header);
|
||||
}
|
||||
|
||||
packets_offset += (packets_size4 / 4);
|
||||
}
|
||||
|
||||
if (buf_done == 0) /* first read */
|
||||
gap_size = virtual_offset - virtual_base; /* might start a few bytes into the XMA */
|
||||
|
||||
data_size = max_packets * num_streams * EAXMA_XMA_PACKET_SIZE;
|
||||
|
||||
bytes_to_copy = data_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 */
|
||||
/* pad + copy */
|
||||
memcpy(buf + buf_done, v_buf + gap_size, bytes_to_copy);
|
||||
buf_done += bytes_to_copy;
|
||||
|
||||
/* move when block is fully done */
|
||||
if (data_size + extra_size == bytes_to_copy + gap_size) {
|
||||
if (data_size == bytes_to_copy + gap_size) {
|
||||
real_offset += (block_size & 0x00FFFFFF);
|
||||
virtual_base += data_size + extra_size;
|
||||
virtual_base += data_size;
|
||||
}
|
||||
|
||||
buf_done += bytes_to_copy;
|
||||
|
||||
/* exit on last block just in case, though should reach file size */
|
||||
if (block_size & 0x80000000)
|
||||
break;
|
||||
@ -64,6 +127,9 @@ int ffmpeg_custom_read_eaxma(ffmpeg_codec_data *data, uint8_t *buf, int buf_size
|
||||
data->real_offset = real_offset;
|
||||
data->virtual_base = virtual_base;
|
||||
return buf_size;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t ffmpeg_custom_seek_eaxma(ffmpeg_codec_data *data, int64_t virtual_offset) {
|
||||
@ -89,8 +155,8 @@ int64_t ffmpeg_custom_seek_eaxma(ffmpeg_codec_data *data, int64_t virtual_offset
|
||||
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);
|
||||
if (data_size % EAXMA_XMA_PACKET_SIZE)
|
||||
extra_size = EAXMA_XMA_PACKET_SIZE - (data_size % EAXMA_XMA_PACKET_SIZE);
|
||||
|
||||
/* stop if virtual_offset lands inside current block */
|
||||
if (data_size + extra_size > virtual_offset)
|
||||
@ -117,26 +183,32 @@ int64_t ffmpeg_custom_size_eaxma(ffmpeg_codec_data *data) {
|
||||
}
|
||||
|
||||
/* 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 ffmpeg_get_eaxma_virtual_size(int channels, off_t real_offset, size_t real_size, STREAMFILE *streamFile) {
|
||||
size_t virtual_size = 0;
|
||||
size_t real_end_offset = real_offset + real_size;
|
||||
/* EA-XMA always uses late XMA2 streams (2ch + ... + 1/2ch) */
|
||||
int num_streams = (channels / 2) + (channels % 2 ? 1 : 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;
|
||||
while (real_offset < real_end_offset) {
|
||||
int max_packets;
|
||||
size_t block_size = read_32bitBE(real_offset + 0x00, streamFile);
|
||||
/* 0x04(4): decoded samples */
|
||||
off_t packets_offset = real_offset + 0x08;
|
||||
|
||||
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);
|
||||
max_packets = get_block_max_packets(num_streams, packets_offset, streamFile);
|
||||
if (max_packets == 0) goto fail;
|
||||
|
||||
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);
|
||||
/* fixed data_size per block for multichannel, see reads */
|
||||
virtual_size += max_packets * num_streams * EAXMA_XMA_PACKET_SIZE;
|
||||
|
||||
real_offset += (block_size & 0x00FFFFFF);
|
||||
|
||||
/* exit on last block just in case, though should reach real_size */
|
||||
if (block_size & 0x80000000)
|
||||
@ -147,7 +219,33 @@ size_t ffmpeg_get_eaxma_virtual_size(off_t real_offset, size_t real_size, STREAM
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* a block can have N streams each with a varying number of packets, get max */
|
||||
static int get_block_max_packets(int num_streams, off_t packets_offset, STREAMFILE * streamfile) {
|
||||
int s;
|
||||
int max_packets = 0;
|
||||
|
||||
for (s = 0; s < num_streams; s++) {
|
||||
size_t packets_size;
|
||||
size_t packets_size4 = read_32bitBE(packets_offset, streamfile); /* size * 4, no idea */
|
||||
int num_packets;
|
||||
|
||||
if (packets_size4 == 0) {
|
||||
VGM_LOG("EA XMA: null packets in stream %i at %lx\n", s, (off_t)packets_offset);
|
||||
goto fail;
|
||||
}
|
||||
packets_size = (packets_size4 / 4) - 0x04;
|
||||
|
||||
num_packets = (int)(packets_size / EAXMA_XMA_PACKET_SIZE) + 1;
|
||||
if (num_packets > max_packets)
|
||||
max_packets = num_packets;
|
||||
}
|
||||
|
||||
return max_packets;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -18,15 +18,15 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
|
||||
/* 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
|
||||
* 0x01(1): flags/count? (when set has extra block data before start_offset)
|
||||
* 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) */
|
||||
* 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) {
|
||||
/* use start_offset as endianness flag */
|
||||
if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x0000FFFF) {
|
||||
read_32bit = read_32bitBE;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
@ -48,6 +48,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
|
||||
#if 0
|
||||
//todo not working ok with blocks in XAS
|
||||
//todo check if EA-XMA loops (Dante's Inferno doesn't)
|
||||
if (flags & 0x60) { /* full loop, seen in ambient tracks */
|
||||
loop_flag = 1;
|
||||
loop_start = 0;
|
||||
@ -81,7 +82,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
vgmstream->meta_type = meta_EA_SNU;
|
||||
|
||||
switch(codec) {
|
||||
case 0x04: /* "Xas1": EA-XAS (Dead Space) */
|
||||
case 0x04: /* "Xas1": EA-XAS (Dead Space PC/PS3) */
|
||||
vgmstream->coding_type = coding_EA_XAS;
|
||||
vgmstream->layout_type = layout_ea_sns_blocked;
|
||||
break;
|
||||
@ -112,8 +113,8 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
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; /* ? */
|
||||
virtual_size = ffmpeg_get_eaxma_virtual_size(vgmstream->channels, start_offset,stream_size, streamFile);
|
||||
block_size = 0x10000; /* todo unused and not correctly done by the parser */
|
||||
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);
|
||||
@ -122,6 +123,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
memset(&cfg, 0, sizeof(ffmpeg_custom_config));
|
||||
cfg.type = FFMPEG_EA_XMA;
|
||||
cfg.virtual_size = virtual_size;
|
||||
cfg.channels = vgmstream->channels;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_config(streamFile, buf,bytes, start_offset,stream_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
|
@ -1039,6 +1039,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
int stream_index; /* FFmpeg's sub-stream (as opposed to an internal stream in custom read/seeks) */
|
||||
int codec_endian;
|
||||
int channels;
|
||||
|
||||
ffmpeg_custom_t type; /* ffmpeg subtype */
|
||||
size_t virtual_size; /* external value, if meta needs to know/supply it */
|
||||
|
Loading…
x
Reference in New Issue
Block a user