mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-29 19:37:30 +01:00
Add .H4M videos (wip)
This commit is contained in:
parent
66e2c9c671
commit
9d86c42ea4
@ -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"},
|
||||
|
@ -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
102
src/layout/blocked_h4m.c
Normal 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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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" />
|
||||
|
@ -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
105
src/meta/h4m.c
Normal 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;
|
||||
}
|
@ -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*/
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user