mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-22 11:25:53 +01:00
205 lines
6.7 KiB
C
205 lines
6.7 KiB
C
|
#ifndef _RAGE_AUD_STREAMFILE_H_
|
||
|
#define _RAGE_AUD_STREAMFILE_H_
|
||
|
#include "deblock_streamfile.h"
|
||
|
#include "../util/endianness.h"
|
||
|
#include "../util/log.h"
|
||
|
|
||
|
#define RAGE_AUD_MAX_MUSIC_CHANNELS 7 /* max known */
|
||
|
|
||
|
/* ************************************************************************* */
|
||
|
|
||
|
typedef struct {
|
||
|
int start_entry; /* innacurate! */
|
||
|
int entries;
|
||
|
int32_t channel_skip;
|
||
|
int32_t channel_samples;
|
||
|
|
||
|
uint32_t frame_size;
|
||
|
|
||
|
/* derived */
|
||
|
uint32_t chunk_start; /* relative to block offset */
|
||
|
uint32_t chunk_size; /* size of this channel's data (not including padding) */
|
||
|
} rage_aud_block_t;
|
||
|
|
||
|
typedef struct {
|
||
|
int big_endian;
|
||
|
uint8_t codec;
|
||
|
int channels;
|
||
|
uint32_t block_offset;
|
||
|
uint32_t header_size;
|
||
|
rage_aud_block_t blk[RAGE_AUD_MAX_MUSIC_CHANNELS];
|
||
|
} rage_aud_block_info_t;
|
||
|
|
||
|
/* Block format:
|
||
|
* - block header for all channels (needed to find frame start)
|
||
|
* - frames from channel 1
|
||
|
* - ...
|
||
|
* - frames from channel N
|
||
|
* - usually there is padding between channels or blocks
|
||
|
*
|
||
|
* Header format:
|
||
|
* - base header (for all channels)
|
||
|
* 0x00: seek info offset (within block)
|
||
|
* 0x08: seek table offset
|
||
|
* 0x10: seek table offset
|
||
|
* - channel info (per channel)
|
||
|
* 0x00: start entry for that channel?
|
||
|
* may be off by +1/+2?
|
||
|
* 0x04: frames in this channel (may be different between channels)
|
||
|
* 0x08: samples to discard in the beginning of this block (MPEG/XMA2)
|
||
|
* this seems to repeat XMA frames, which decoders don't like, so maybe they just skip whole frames
|
||
|
* 0x0c: samples in channel without discard? (for MPEG/XMA2 can vary between channels)
|
||
|
* (next fields only exists for MPEG)
|
||
|
* 0x10: close to number of frames but varies a bit?
|
||
|
* 0x14: channel chunk size (not counting padding)
|
||
|
* - seek table (entries for all channels)
|
||
|
* 0x00: start?
|
||
|
* 0x04: end?
|
||
|
* - padding until this block's end
|
||
|
*/
|
||
|
static bool read_rage_aud_block(STREAMFILE* sf, rage_aud_block_info_t* bi) {
|
||
|
read_s32_t read_s32 = bi->big_endian ? read_s32be : read_s32le;
|
||
|
|
||
|
uint32_t channel_entry_size, seek_entry_size;
|
||
|
uint32_t offset = bi->block_offset;
|
||
|
int channels = bi->channels;
|
||
|
/* read stupid block crap + derived info at once so hopefully it's a bit easier to understand */
|
||
|
|
||
|
switch(bi->codec) {
|
||
|
case 0x0000: /* XMA1 */
|
||
|
channel_entry_size = 0x10;
|
||
|
seek_entry_size = 0x08;
|
||
|
break;
|
||
|
default:
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* base header */
|
||
|
{
|
||
|
offset += 0x18;
|
||
|
}
|
||
|
|
||
|
/* channel info table */
|
||
|
for (int ch = 0; ch < bi->channels; ch++) {
|
||
|
bi->blk[ch].start_entry = read_s32(offset + 0x00, sf);
|
||
|
bi->blk[ch].entries = read_s32(offset + 0x04, sf);
|
||
|
bi->blk[ch].channel_skip = read_s32(offset + 0x08, sf);
|
||
|
bi->blk[ch].channel_samples = read_s32(offset + 0x0c, sf);
|
||
|
/* others: optional */
|
||
|
|
||
|
offset += channel_entry_size;
|
||
|
}
|
||
|
|
||
|
/* seek table */
|
||
|
for (int ch = 0; ch < channels; ch++) {
|
||
|
offset += bi->blk[ch].entries * seek_entry_size;
|
||
|
}
|
||
|
|
||
|
/* derived info */
|
||
|
for (int ch = 0; ch < channels; ch++) {
|
||
|
switch(bi->codec) {
|
||
|
case 0x0000: /* XMA1 */
|
||
|
bi->blk[ch].frame_size = 0x800;
|
||
|
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
|
||
|
break;
|
||
|
|
||
|
/* in MPEG frames seem to be VBR, so would need channel chunk size */
|
||
|
default:
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* detect block header size (aligned to 0x800) and adjust offset */
|
||
|
{
|
||
|
/* seek table size seems consistent for all blocks; last one defines less entries yet still writes
|
||
|
* a table as big as prev blocks, repeating old values for unused entries, so final header size is consistent */
|
||
|
if (!bi->header_size)
|
||
|
bi->header_size = offset - bi->block_offset;
|
||
|
offset = bi->block_offset + align_size_to_block(bi->header_size, 0x800);
|
||
|
}
|
||
|
|
||
|
/* set frame starts per channel */
|
||
|
for (int ch = 0; ch < channels; ch++) {
|
||
|
bi->blk[ch].chunk_start = offset - bi->block_offset;
|
||
|
offset += bi->blk[ch].chunk_size;
|
||
|
}
|
||
|
|
||
|
/* beyond this is padding until chunk_start */
|
||
|
|
||
|
return true;
|
||
|
fail:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Find data that repeats in the beginning of a new block at the end of last block.
|
||
|
* When a new block starts there is some repeated data + channel_skip (for seeking + encoder delay?).
|
||
|
* Detect it so decoder may ignore it. */
|
||
|
static uint32_t get_block_repeated_size(STREAMFILE* sf, rage_aud_block_info_t* bi, int channel) {
|
||
|
|
||
|
if (bi->blk[channel].channel_skip == 0)
|
||
|
return 0;
|
||
|
if (bi->block_offset >= get_streamfile_size(sf))
|
||
|
return 0;
|
||
|
|
||
|
switch(bi->codec) {
|
||
|
case 0x0000: { /* XMA1 */
|
||
|
/* when data repeats seems to clone the last (super-)frame */
|
||
|
return bi->blk[channel].frame_size;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
;VGM_LOG("RAGE_AUD: found channel skip in codec %x\n", bi->codec); /* not seen */
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ************************************************************************* */
|
||
|
|
||
|
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
|
||
|
int channel = data->cfg.track_number;
|
||
|
rage_aud_block_info_t bi = {0};
|
||
|
|
||
|
bi.big_endian = data->cfg.big_endian;
|
||
|
bi.block_offset = data->physical_offset;
|
||
|
bi.channels = data->cfg.track_count;
|
||
|
bi.codec = data->cfg.track_type;
|
||
|
bi.header_size = data->cfg.config;
|
||
|
|
||
|
if (!read_rage_aud_block(sf, &bi))
|
||
|
return; //???
|
||
|
data->cfg.config = bi.header_size; /* fixed for all blocks but calc'd on first one */
|
||
|
|
||
|
uint32_t repeat_size = get_block_repeated_size(sf, &bi, channel);
|
||
|
|
||
|
data->block_size = data->cfg.chunk_size;
|
||
|
data->skip_size = bi.blk[channel].chunk_start + repeat_size;
|
||
|
data->data_size = bi.blk[channel].chunk_size - repeat_size;
|
||
|
}
|
||
|
|
||
|
/* deblocks RAGE_AUD blocks */
|
||
|
static STREAMFILE* setup_rage_aud_streamfile(STREAMFILE* sf, uint32_t stream_offset, uint32_t stream_size, uint32_t block_size, int channels, int channel, uint8_t codec, int big_endian) {
|
||
|
STREAMFILE* new_sf = NULL;
|
||
|
deblock_config_t cfg = {0};
|
||
|
|
||
|
if (channels > RAGE_AUD_MAX_MUSIC_CHANNELS || channel >= channels)
|
||
|
return NULL;
|
||
|
|
||
|
cfg.track_number = channel;
|
||
|
cfg.track_count = channels;
|
||
|
cfg.stream_start = stream_offset;
|
||
|
cfg.stream_size = stream_size;
|
||
|
cfg.chunk_size = block_size;
|
||
|
cfg.track_type = codec;
|
||
|
cfg.big_endian = big_endian;
|
||
|
//cfg.physical_offset = stream_offset;
|
||
|
//cfg.logical_size = rage_aud_io_size(sf, &cfg); /* force init */
|
||
|
cfg.block_callback = block_callback;
|
||
|
|
||
|
new_sf = open_wrap_streamfile(sf);
|
||
|
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||
|
//new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||
|
return new_sf;
|
||
|
}
|
||
|
|
||
|
#endif
|