Add proper H4M decoding [Resident Evil 0 (GC), Tales of Symphonia (GC)]

This commit is contained in:
bnnm 2018-08-19 00:38:08 +02:00
parent 93b66b2aa6
commit d93a4b2c7a
7 changed files with 107 additions and 35 deletions

View File

@ -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);

View File

@ -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 */

View File

@ -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"},

View File

@ -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 {

View File

@ -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);

View File

@ -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++) {

View File

@ -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 */