Add .H4M videos (wip)

This commit is contained in:
bnnm 2018-06-03 13:08:41 +02:00
parent 66e2c9c671
commit 9d86c42ea4
11 changed files with 237 additions and 0 deletions

View File

@ -139,6 +139,7 @@ static const char* extension_list[] = {
"gtd",
"gwm",
"h4m",
"hca",
"hgc1",
"his",
@ -623,6 +624,7 @@ static const layout_info layout_info_list[] = {
{layout_blocked_ea_wve_au00, "blocked (EA WVE au00)"},
{layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"},
{layout_blocked_sthd, "blocked (STHD)"},
{layout_blocked_h4m, "blocked (H4M)"},
};
static const meta_info meta_info_list[] = {
@ -1020,6 +1022,7 @@ static const meta_info meta_info_list[] = {
{meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"},
{meta_OGG_GWM, "Ogg Vorbis (GWM header)"},
{meta_DSP_SADF, "Procyon Studio SADF header"},
{meta_H4M, "Hudson HVQM4 header"},
#ifdef VGM_USE_FFMPEG
{meta_FFmpeg, "FFmpeg supported file format"},

View File

@ -202,6 +202,9 @@ static void block_update(VGMSTREAM * vgmstream) {
case layout_blocked_sthd:
block_update_sthd(vgmstream->next_block_offset,vgmstream);
break;
case layout_blocked_h4m:
block_update_h4m(vgmstream->next_block_offset,vgmstream);
break;
default:
break;
}

102
src/layout/blocked_h4m.c Normal file
View File

@ -0,0 +1,102 @@
#include "layout.h"
#include "../coding/coding.h"
/* H4M video blocks with audio frames, based on h4m_audio_decode */
void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream) {
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
int i;
size_t block_size, block_samples;
/* use full_block_size as counter (a bit hacky but whatevs) */
if (vgmstream->full_block_size <= 0) {
/* new full block */
/* 0x00: last_full_block_size */
uint32_t full_block_size = read_32bitBE(block_offset+0x04, streamFile);
/* 0x08: vid_frame_count */
/* 0x0c: aud_frame_count */
/* 0x10: block_header_unk (0x01000000, except 0 in a couple of Bomberman Jetters files) */
vgmstream->full_block_size = full_block_size; /* not including 0x14 block header */
block_size = 0x14; /* skip header and point to first frame in full block */
block_samples = 0; /* signal new block_update_h4m */
}
else {
/* new audio or video frames in the current full block */
uint16_t frame_type = read_16bitBE(block_offset+0x00, streamFile);
uint16_t frame_format = read_16bitBE(block_offset+0x02, streamFile);
uint32_t frame_size = read_32bitBE(block_offset+0x04, streamFile); /* not including 0x08 frame header */
if (frame_type == 0x00) {
/* HVQM4_AUDIO (there are more checks with frame_format but not too relevant for vgmstream) */
uint32_t frame_samples = read_32bitBE(block_offset+0x08, streamFile);
size_t block_skip;
if (vgmstream->codec_version & 0x80) {
frame_samples /= 2; /* ??? */
}
block_skip = 0x08 + 0x04;
block_size = 0x08 + frame_size;
block_samples = frame_samples;
/* skip data from other audio tracks */
if (vgmstream->num_streams) {
uint32_t audio_bytes = frame_size - 0x04;
block_skip += (audio_bytes / vgmstream->num_streams) * vgmstream->stream_index;
}
//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;
}
}
//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--;
}
}
else {
block_size = 0x08 + frame_size;
block_samples = 0; /* signal new block_update_h4m */
}
vgmstream->full_block_size -= block_size;
}
/* EOF check, there is some footer/garbage at the end */
if (block_offset == get_streamfile_size(streamFile)
|| block_offset + block_size > get_streamfile_size(streamFile)) {
//block_samples = -1; /* signal end block */
vgmstream->full_block_size = 0;
vgmstream->current_block_samples = 0;
vgmstream->current_block_offset = get_streamfile_size(streamFile);
vgmstream->next_block_offset = get_streamfile_size(streamFile);
return;
}
vgmstream->current_block_samples = block_samples;
vgmstream->current_block_offset = block_offset;
vgmstream->next_block_offset = block_offset + block_size;
}

View File

@ -43,6 +43,7 @@ void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_ea_wve_au00(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_ea_wve_ad10(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_sthd(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream);
/* other layouts */
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);

View File

@ -483,6 +483,10 @@
<File
RelativePath=".\meta\gtd.c"
>
</File>
<File
RelativePath=".\meta\h4m.c"
>
</File>
<File
RelativePath=".\meta\halpst.c"
@ -1822,6 +1826,10 @@
RelativePath=".\layout\blocked_gsb.c"
>
</File>
<File
RelativePath=".\layout\blocked_h4m.c"
>
</File>
<File
RelativePath=".\layout\blocked_halpst.c"
>

View File

@ -235,6 +235,7 @@
<ClCompile Include="meta\bar.c" />
<ClCompile Include="meta\gsp_gsb.c" />
<ClCompile Include="meta\gtd.c" />
<ClCompile Include="meta\h4m.c" />
<ClCompile Include="meta\halpst.c" />
<ClCompile Include="meta\hca.c" />
<ClCompile Include="meta\his.c" />
@ -503,6 +504,7 @@
<ClCompile Include="layout\blocked_emff.c" />
<ClCompile Include="layout\blocked_filp.c" />
<ClCompile Include="layout\blocked_gsb.c" />
<ClCompile Include="layout\blocked_h4m.c" />
<ClCompile Include="layout\blocked_halpst.c" />
<ClCompile Include="layout\blocked_matx.c" />
<ClCompile Include="layout\interleave.c" />

View File

@ -295,6 +295,9 @@
<ClCompile Include="meta\gtd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\h4m.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\halpst.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -1075,6 +1078,9 @@
<ClCompile Include="layout\blocked_gsb.c">
<Filter>layout\Source Files</Filter>
</ClCompile>
<ClCompile Include="layout\blocked_h4m.c">
<Filter>layout\Source Files</Filter>
</ClCompile>
<ClCompile Include="layout\blocked_halpst.c">
<Filter>layout\Source Files</Filter>
</ClCompile>

105
src/meta/h4m.c Normal file
View File

@ -0,0 +1,105 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
/* H4M - from Hudson HVQM4 videos [Resident Evil 0 (GC), Tales of Symphonia (GC)]
* (info from hcs/Nisto's h4m_audio_decode) */
VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count;
int format, extra_tracks, sample_rate;
int total_subsongs, target_subsong = streamFile->stream_index;
/* checks */
if (!check_extensions(streamFile, "h4m"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x4856514D && /* "HVQM" */
read_32bitBE(0x04,streamFile) != 0x3420312E) /* "4 1." */
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x33000000 && /* "3\0\0\0" */
read_32bitBE(0x08,streamFile) != 0x35000000) /* "5\0\0\0" */
goto fail;
/* header */
start_offset = read_32bitBE(0x10, streamFile); /* header_size */
if (start_offset != 0x44) /* known size */
goto fail;
if (read_32bitBE(0x14, streamFile) != get_streamfile_size(streamFile) - start_offset) /* body_size */
goto fail;
if (read_32bitBE(0x18, streamFile) == 0) /* blocks */
goto fail;
/* 0x1c: video_frames */
if (read_32bitBE(0x20, streamFile) == 0) /* audio_frames */
goto fail;
/* 0x24: frame interval */
/* 0x28: max_video_frame_size */
/* 0x2c: unk2C (0) */
if (read_32bitBE(0x30, streamFile) == 0) /* max_audio_frame_size */
goto fail;
/* 0x34: hres */
/* 0x36: vres */
/* 0x38: h_srate */
/* 0x39: v_srate */
/* 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? */
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] */
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = get_streamfile_size(streamFile) / total_subsongs; /* approx... */
vgmstream->codec_version = format; /* for blocks */
vgmstream->meta_type = meta_H4M;
vgmstream->layout_type = layout_blocked_h4m;
switch(format & 0x7F) {
case 0x00:
vgmstream->coding_type = coding_DVI_IMA; //todo H4M_IMA
break;
/* no games known to use this, h4m_audio_decode may decode them */
case 0x01: /* Uncompressed PCM */
case 0x04: /* 8-bit (A)DPCM */
default:
VGM_LOG("H4M: unknown codec %x\n", format);
goto fail;
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
/* calc num_samples manually */
{
vgmstream->next_block_offset = start_offset;
do {
block_update_h4m(vgmstream->next_block_offset,vgmstream);
vgmstream->num_samples += vgmstream->current_block_samples;
}
while (vgmstream->next_block_offset < get_streamfile_size(streamFile));
}
block_update_h4m(start_offset, vgmstream);
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -754,4 +754,7 @@ VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_sadf(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile);
#endif /*_META_H*/

View File

@ -410,6 +410,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_ubi_bao_pk,
init_vgmstream_dsp_switch_audio,
init_vgmstream_dsp_sadf,
init_vgmstream_h4m,
init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG
@ -936,6 +937,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
case layout_blocked_ea_wve_au00:
case layout_blocked_ea_wve_ad10:
case layout_blocked_sthd:
case layout_blocked_h4m:
render_vgmstream_blocked(buffer,sample_count,vgmstream);
break;
case layout_aix:

View File

@ -252,6 +252,7 @@ typedef enum {
layout_blocked_ea_wve_au00, /* EA WVE au00 blocks */
layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */
layout_blocked_sthd, /* Dream Factory STHD */
layout_blocked_h4m, /* H4M video */
/* otherwise odd */
layout_aix, /* CRI AIX's wheels within wheels */
@ -678,6 +679,7 @@ typedef enum {
meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */
meta_TA_AAC_VITA, /* tri-Ace AAC (Judas Code) */
meta_OGG_GWM, /* Ogg Vorbis with encryption [Metronomicon (PC)] */
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
#ifdef VGM_USE_FFMPEG
meta_FFmpeg,