mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-25 07:20:10 +01:00
Add proper H4M decoding [Resident Evil 0 (GC), Tales of Symphonia (GC)]
This commit is contained in:
parent
93b66b2aa6
commit
d93a4b2c7a
@ -36,6 +36,7 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o
|
||||
void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format);
|
||||
size_t ima_bytes_to_samples(size_t bytes, int channels);
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
|
||||
size_t xbox_ima_bytes_to_samples(size_t bytes, int channels);
|
||||
|
@ -935,6 +935,80 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* IMA with variable frame formats controlled by the block layout. The original code uses
|
||||
* tables mapping all standard IMA combinations (to optimize calculations), but decodes the same.
|
||||
* Based on HCS's and Nisto's reverse engineering in h4m_audio_decode. */
|
||||
void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format) {
|
||||
int i, samples_done = 0;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_index = stream->adpcm_step_index;
|
||||
size_t header_size;
|
||||
int is_stereo = (channelspacing > 1);
|
||||
|
||||
/* external interleave (blocked, should call 1 frame) */
|
||||
|
||||
/* custom header, per channel */
|
||||
if (first_sample == 0) {
|
||||
int channel_pos = is_stereo ? (1 - channel) : channel; /* R hist goes first */
|
||||
switch(frame_format) {
|
||||
case 1: /* combined hist+index */
|
||||
hist1 = read_16bitBE(stream->offset + 0x02*channel_pos + 0x00,stream->streamfile) & 0xFFFFFF80;
|
||||
step_index = (uint8_t)read_8bit(stream->offset + 0x02*channel_pos + 0x01,stream->streamfile) & 0x7f;
|
||||
break;
|
||||
case 3: /* separate hist+index */
|
||||
hist1 = read_16bitBE(stream->offset + 0x03*channel_pos + 0x00,stream->streamfile);
|
||||
step_index = (uint8_t)read_8bit(stream->offset + 0x03*channel_pos + 0x02,stream->streamfile);
|
||||
break;
|
||||
case 2: /* no hist/index (continues from previous frame) */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* write header sample (last nibble is skipped) */
|
||||
if (frame_format == 1 || frame_format == 3) {
|
||||
outbuf[samples_done * channelspacing] = (short)hist1;
|
||||
samples_done++;
|
||||
samples_to_do--;
|
||||
}
|
||||
|
||||
/* clamp corrupted data just in case */
|
||||
if (step_index < 0) step_index = 0;
|
||||
if (step_index > 88) step_index = 88;
|
||||
}
|
||||
else {
|
||||
/* offset adjust for header sample */
|
||||
if (frame_format == 1 || frame_format == 3) {
|
||||
first_sample--;
|
||||
}
|
||||
}
|
||||
|
||||
/* offset adjust */
|
||||
switch(frame_format) {
|
||||
case 1: header_size = (channelspacing*0x02); break;
|
||||
case 3: header_size = (channelspacing*0x03); break;
|
||||
default: header_size = 0; break;
|
||||
}
|
||||
|
||||
/* decode block nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
off_t byte_offset = is_stereo ?
|
||||
stream->offset + header_size + i : /* stereo: one nibble per channel */
|
||||
stream->offset + header_size + i/2; /* mono: consecutive nibbles */
|
||||
int nibble_shift = is_stereo ?
|
||||
(!(channel&1) ? 0:4) : /* stereo: L=low, R=high */
|
||||
(!(i&1) ? 0:4); /* mono: low first */
|
||||
|
||||
std_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
||||
|
||||
outbuf[samples_done * channelspacing] = (short)(hist1);
|
||||
samples_done++;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* ************************************************************* */
|
||||
|
||||
size_t ima_bytes_to_samples(size_t bytes, int channels) {
|
||||
/* 2 samples per byte (2 nibbles) in stereo or mono config */
|
||||
|
@ -552,6 +552,8 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"},
|
||||
{coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"},
|
||||
|
||||
{coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"},
|
||||
|
||||
{coding_MSADPCM, "Microsoft 4-bit ADPCM"},
|
||||
{coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"},
|
||||
{coding_WS, "Westwood Studios VBR ADPCM"},
|
||||
|
@ -42,38 +42,19 @@ void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
block_size = 0x08 + frame_size;
|
||||
block_samples = frame_samples;
|
||||
|
||||
|
||||
/* skip data from other audio tracks */
|
||||
if (vgmstream->num_streams) {
|
||||
if (vgmstream->num_streams > 1 && vgmstream->stream_index > 1) {
|
||||
uint32_t audio_bytes = frame_size - 0x04;
|
||||
block_skip += (audio_bytes / vgmstream->num_streams) * vgmstream->stream_index;
|
||||
block_skip += (audio_bytes / vgmstream->num_streams) * (vgmstream->stream_index-1);
|
||||
}
|
||||
|
||||
//VGM_ASSERT(frame_format < 1 && frame_format > 3, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset);
|
||||
VGM_ASSERT(frame_format == 1, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset);
|
||||
|
||||
//todo handle in the decoder?
|
||||
//todo right channel first?
|
||||
/* get ADPCM hist (usually every new block) */
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
if (frame_format == 1) { /* combined hist+index */
|
||||
vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x02*i + 0x00,streamFile) & 0xFFFFFF80;
|
||||
vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x02*i + 0x01,streamFile) & 0x7f;
|
||||
vgmstream->ch[i].offset = block_offset + block_skip + 0x02*vgmstream->channels;
|
||||
}
|
||||
else if (frame_format == 3) { /* separate hist+index */
|
||||
vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x03*i + 0x00,streamFile);
|
||||
vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x03*i + 0x02,streamFile);
|
||||
vgmstream->ch[i].offset = block_offset + block_skip + 0x03*vgmstream->channels;
|
||||
}
|
||||
else if (frame_format == 2) { /* no hist/index */
|
||||
vgmstream->ch[i].offset = block_offset + block_skip;
|
||||
}
|
||||
}
|
||||
/* pass current mode to the decoder */
|
||||
vgmstream->codec_version = (frame_format << 8) | (vgmstream->codec_version & 0xFF);
|
||||
|
||||
//todo temp hack, at it must write header sample and ignore the last nibble to get fully correct output
|
||||
if (frame_format == 1 || frame_format == 3) {
|
||||
block_samples--;
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->ch[i].offset = block_offset + block_skip;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -46,15 +46,16 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) {
|
||||
/* 0x3a: unk3A (0 or 0x12) */
|
||||
/* 0x3b: unk3B (0) */
|
||||
channel_count = read_8bit(0x3c,streamFile);
|
||||
if (read_8bit(0x3d,streamFile) != 16) /* bitdepth */ //todo Pikmin not working
|
||||
goto fail;
|
||||
format = read_8bit(0x3e,streamFile); /* flags? */
|
||||
if (read_8bit(0x3d,streamFile) != 16) /* bitdepth */
|
||||
goto fail; //todo Pikmin (GC) is using some kind of variable blocks
|
||||
format = (uint8_t)read_8bit(0x3e,streamFile); /* flags? */
|
||||
extra_tracks = read_8bit(0x3f,streamFile);
|
||||
sample_rate = read_32bitBE(0x40,streamFile);
|
||||
|
||||
loop_flag = 0;
|
||||
|
||||
total_subsongs = extra_tracks + 1; /* tracks for languages [Pokemon Channel], or sometimes used to fake multichannel [Tales of Symphonia] */
|
||||
/* tracks for languages [Pokemon Channel], or sometimes used to fake multichannel [Tales of Symphonia] */
|
||||
total_subsongs = extra_tracks + 1;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
@ -72,9 +73,10 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) {
|
||||
|
||||
switch(format & 0x7F) {
|
||||
case 0x00:
|
||||
vgmstream->coding_type = coding_DVI_IMA; //todo H4M_IMA
|
||||
vgmstream->coding_type = coding_H4M_IMA;
|
||||
break;
|
||||
/* no games known to use this, h4m_audio_decode may decode them */
|
||||
|
||||
/* no games known to use these, h4m_audio_decode may decode them */
|
||||
case 0x01: /* Uncompressed PCM */
|
||||
case 0x04: /* 8-bit (A)DPCM */
|
||||
default:
|
||||
@ -93,6 +95,7 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) {
|
||||
vgmstream->num_samples += vgmstream->current_block_samples;
|
||||
}
|
||||
while (vgmstream->next_block_offset < get_streamfile_size(streamFile));
|
||||
vgmstream->full_block_size = 0; /* extra cleanup for H4M */
|
||||
}
|
||||
|
||||
block_update_h4m(start_offset, vgmstream);
|
||||
|
@ -1108,6 +1108,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
return (0x800 - 0x04) * 2;
|
||||
case coding_RAD_IMA_mono:
|
||||
return 32;
|
||||
case coding_H4M_IMA:
|
||||
return 0; /* variable (block-controlled) */
|
||||
|
||||
case coding_XA:
|
||||
return 28*8 / vgmstream->channels; /* 8 subframes per frame, mono/stereo */
|
||||
@ -1282,6 +1284,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
return 0x24 * vgmstream->channels;
|
||||
case coding_APPLE_IMA4:
|
||||
return 0x22;
|
||||
case coding_H4M_IMA:
|
||||
return 0x00; /* variable (block-controlled) */
|
||||
|
||||
case coding_XA:
|
||||
return 0x80;
|
||||
@ -1816,6 +1820,15 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
case coding_H4M_IMA:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
uint16_t frame_format = (uint16_t)((vgmstream->codec_version >> 8) & 0xFFFF);
|
||||
|
||||
decode_h4m_ima(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
vgmstream->channels,vgmstream->samples_into_block,
|
||||
samples_to_do, chan, frame_format);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_WS:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
|
@ -144,6 +144,8 @@ typedef enum {
|
||||
coding_AWC_IMA, /* Rockstar AWC IMA ADPCM */
|
||||
coding_UBI_IMA, /* Ubisoft IMA ADPCM */
|
||||
|
||||
coding_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */
|
||||
|
||||
coding_MSADPCM, /* Microsoft ADPCM (stereo/mono) */
|
||||
coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */
|
||||
coding_WS, /* Westwood Studios VBR ADPCM */
|
||||
@ -860,7 +862,6 @@ typedef enum {
|
||||
VORBIS_OGL, /* Shin'en OGL: custom packet headers */
|
||||
VORBIS_SK, /* Silicon Knights AUD: "OggS" replaced by "SK" */
|
||||
VORBIS_VID1, /* Neversoft VID1: custom packet blocks/headers */
|
||||
//VORBIS_LYN /* Ubisoft LyN: two interleaved Ogg (including setup, duplicated) */
|
||||
} vorbis_custom_t;
|
||||
|
||||
/* config for Wwise Vorbis (3 types for flexibility though not all combinations exist) */
|
||||
@ -1129,9 +1130,6 @@ typedef enum {
|
||||
FFMPEG_STANDARD, /* default FFmpeg */
|
||||
FFMPEG_SWITCH_OPUS, /* Opus without Ogg layer */
|
||||
FFMPEG_EA_XMA, /* XMA with padding removed and custom streams in SNS blocks */
|
||||
//FFMPEG_EA_SCHL, /* Normal header+data (ex. ATRAC3) in SCxx blocks */
|
||||
//FFMPEG_SFH, /* ATRAC3plus header+data in SFH blocks */
|
||||
//FFMPEG_AWC_XMA, /* XMA data in AWC blocks, 1 streams per channel */
|
||||
} ffmpeg_custom_t;
|
||||
|
||||
/* config for the above modes */
|
||||
|
Loading…
Reference in New Issue
Block a user