Add MTA2 decoder/meta support [Metal Gear Solid 4 (PS3)]

This commit is contained in:
bnnm 2017-05-18 19:16:44 +02:00
parent 08a01fec1f
commit 625c18a87e
10 changed files with 330 additions and 2 deletions

View File

@ -114,6 +114,9 @@ void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing,
/* mtaf_decoder */
void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int channels);
/* mta2_decoder */
void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
/* mc3_decoder */
void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);

186
src/coding/mta2_decoder.c Normal file
View File

@ -0,0 +1,186 @@
#include "coding.h"
#include "../util.h"
/* MTA2 (EA XAS variant?) decoder based on:
* - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1]
* - Solid4 tools: https://github.com/GHzGangster/Drebin
*
* MTA2 layout:
* - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams
* ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1
* * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience)
* - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4)
* ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?)
* - in case of "macroblock" layout, there are also headers before N tracks (like other MGS games)
*
* Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls
* but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and
* expects samples_to_do to be block_samples at most (could be simplified, I guess).
*
* Because of how the macroblock/track and stream's offset per channel work, they are supported by
* autodetecting and skipping when needed (ideally should keep a special layout/count, but this is simpler).
*/
static const int c1[8] = { /* mod table 1 */
0, 240, 460, 392, 488, 460, 460, 240
};
static const int c2[8] = { /* mod table 2 */
0, 0, -208, -220, -240, -240, -220, -104
};
static const int c3[32] = { /* shift table */
256, 335, 438, 573, 749, 979, 1281, 1675,
2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327,
18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568,
160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576
};
/* expands nibble */
static short calculate_output(int nibble, short smp1, short smp2, int mod, int sh) {
int output;
if (nibble > 7) /* sign extend */
nibble = nibble - 16;
output = (smp1 * c1[mod] + smp2 * c2[mod] + (nibble * c3[sh]) + 128) >> 8;
output = clamp16(output);
return (short)output;
}
/* autodetect and skip "macroblocks" */
static void mta2_block_update(VGMSTREAMCHANNEL * stream) {
int block_type, block_size, block_tracks, repeat = 1;
/* may need to skip N empty blocks */
do {
block_type = read_32bitBE(stream->offset + 0x00, stream->streamfile);
block_size = read_32bitBE(stream->offset + 0x04, stream->streamfile); /* including this header */
/* 0x08: always null */
block_tracks = read_32bitBE(stream->offset + 0x0c, stream->streamfile); /* total tracks of variable size (can be 0) */
/* 0x10001: music, 0x20001: sfx?, 0xf0: loop control (goes at the end) */
if (block_type != 0x00010001 && block_type != 0x00020001 && block_type != 0x000000F0)
return; /* not a block */
/* frame=010001+00/etc can be mistaken as block_type, do extra checks */
{
int i, track_channels = 0;
uint16_t channel_layout = (block_size >> 16);
uint16_t track_size = (block_size & 0xFFFF);
/* has chanel layout == may be a track */
if (channel_layout > 0 && channel_layout <= 0xFF) {
for (i = 0; i < 8; i++) {
if ((channel_layout >> i) & 0x01)
track_channels++;
}
if (track_channels*0x90 == track_size)
return;
}
}
if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */
VGM_LOG("MTA2: bad block @ %08lx\n", stream->offset);
stream->offset += 0x10;
repeat = 0;
}
else if (block_tracks == 0) { /* empty block (common), keep repeating */
stream->offset += block_size;
}
else { /* normal block, position into next track header */
stream->offset += 0x10;
repeat = 0;
}
} while (repeat);
}
/* decodes a block for a channel, skipping macroblocks/tracks if needed */
void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0;
int i, group, row, col;
int num_track = 0, channel_layout, track_channels = 0, track_channel;
/* block/track skip */
do {
/* autodetect and skip macroblock header */
mta2_block_update(stream);
/* parse track header (0x10) and skip tracks that our current channel doesn't belong to */
num_track = read_8bit(stream->offset+0x00,stream->streamfile); /* 0=first */
/* 0x01(3): num_frame (0=first), 0x04(1): 0? */
channel_layout = read_8bit(stream->offset+0x05,stream->streamfile); /* bitmask, see mta2.c */
frame_size = read_16bitBE(stream->offset+0x06,stream->streamfile); /* not including this header */
/* 0x08(8): null */
if (num_track < 0)
break; /* EOF: whatever */
track_channels = 0;
for (i = 0; i < 8; i++) {
if ((channel_layout >> i) & 0x01)
track_channels++;
}
/* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */
if (channel / track_channels == num_track)
break; /* channel belongs to this track */
/* keep looping for our track */
stream->offset += 0x10 + frame_size;
}
while (1);
track_channel = channel % track_channels;
channel_block_samples = (0x80*2);
channel_first_sample = first_sample % (0x80*2);
/* parse channel frame (header 0x04*4 + data 0x20*4) */
for (group = 0; group < 4; group++) {
short smp2, smp1, mod, sh, output;
int group_header = read_32bitBE(stream->offset + 0x10 + track_channel*0x90 + group*0x4, stream->streamfile);
smp2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */
smp1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */
mod = (group_header >> 5) & 0x7; /* mid 3b */
sh = group_header & 0x1f; /* lower 5b */
/* write header samples (skips the last 2 group nibbles), like Drebin's decoder
* last 2 nibbles and next 2 header hist should match though */
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = smp2;
samples_done++;
}
sample_count++;
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = smp1;
samples_done++;
}
sample_count++;
for (row = 0; row < 8; row++) {
for (col = 0; col < 4*2; col++) {
uint8_t nibbles = read_8bit(stream->offset + 0x10 + 0x10 + track_channel*0x90 + group*0x4 + row*0x10 + col/2, stream->streamfile);
int nibble_shift = (!(col&1) ? 4 : 0); /* upper first */
output = calculate_output((nibbles >> nibble_shift) & 0xf, smp1, smp2, mod, sh);
/* ignore last 2 nibbles (uses first 2 header samples) */
if (row < 7 || col < 3*2) {
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = output;
samples_done++;
}
sample_count++;
}
smp2 = smp1;
smp1 = output;
}
}
}
/* block fully done */
if (channel_first_sample + samples_done == channel_block_samples) {
stream->offset += 0x10 + frame_size;
}
}

View File

@ -53,6 +53,7 @@ static const char* extension_list[] = {
"bfwav",
"bfwavnsmbu",
"bg00",
"bgm",
"bgw",
"bh2pcm",
"bik",
@ -82,6 +83,7 @@ static const char* extension_list[] = {
"cps",
"cxs",
"dbm",
"dcs",
"ddsp",
"de2",
@ -168,6 +170,7 @@ static const char* extension_list[] = {
"msf",
"mss",
"msvp",
"mta2",
"mtaf",
"mus",
"musc",
@ -288,11 +291,9 @@ static const char* extension_list[] = {
"vgs",
"vgv",
"vig",
"vds",
"vdm",
"vms",
"vms",
"voi",
"vpk",
"vs",
@ -446,6 +447,7 @@ static const coding_info coding_info_list[] = {
{coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"},
{coding_LSF, "lsf 4-bit ADPCM"},
{coding_MTAF, "Konami MTAF 4-bit ADPCM"},
{coding_MTA2, "Konami MTA2 4-bit ADPCM"},
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
#ifdef VGM_USE_VORBIS
@ -861,6 +863,7 @@ static const meta_info meta_info_list[] = {
{meta_GTD, "GTD/GHS header"},
{meta_TA_AAC_X360, "tri-Ace AAC (X360) header"},
{meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"},
{meta_PS3_MTA2, "Konami MTA2 header"},
#ifdef VGM_USE_VORBIS
{meta_OGG_VORBIS, "Ogg Vorbis"},

View File

@ -966,6 +966,10 @@
RelativePath=".\meta\ps3_msf.c"
>
</File>
<File
RelativePath=".\meta\ps3_mta2.c"
>
</File>
<File
RelativePath=".\meta\ps3_past.c"
>
@ -1358,6 +1362,10 @@
RelativePath=".\coding\mtaf_decoder.c"
>
</File>
<File
RelativePath=".\coding\mta2_decoder.c"
>
</File>
<File
RelativePath=".\coding\nds_procyon_decoder.c"
>

View File

@ -129,6 +129,7 @@
<ClCompile Include="coding\lsf_decoder.c" />
<ClCompile Include="coding\mp4_aac_decoder.c" />
<ClCompile Include="coding\mtaf_decoder.c" />
<ClCompile Include="coding\mta2_decoder.c" />
<ClCompile Include="layout\ps2_iab_blocked.c" />
<ClCompile Include="layout\ps2_strlr_blocked.c" />
<ClCompile Include="layout\scd_int_layout.c" />
@ -343,6 +344,7 @@
<ClCompile Include="meta\ps2_xau.c" />
<ClCompile Include="meta\ps3_cps.c" />
<ClCompile Include="meta\ps3_msf.c" />
<ClCompile Include="meta\ps3_mta2.c" />
<ClCompile Include="meta\ps3_xvag.c" />
<ClCompile Include="meta\psx_cdxa.c" />
<ClCompile Include="meta\psx_fag.c" />

View File

@ -580,6 +580,9 @@
<ClCompile Include="meta\ps3_msf.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ps3_mta2.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ps3_xvag.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -1009,6 +1012,9 @@
<ClCompile Include="coding\mtaf_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="coding\mta2_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\tun.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -677,4 +677,6 @@ VGMSTREAM * init_vgmstream_gtd(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ta_aac_x360(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ta_aac_ps3(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile);
#endif /*_META_H*/

104
src/meta/ps3_mta2.c Normal file
View File

@ -0,0 +1,104 @@
#include "meta.h"
#include "../util.h"
/* MTA2 - found in Metal Gear Solid 4 */
VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t header_offset, start_offset;
int loop_flag, channel_count, sample_rate; //block_offset;
int32_t loop_start, loop_end;
/* check extension */
/* .mta2: normal file, .bgm: mta2 with block layout, .dbm: iPod metadata + block layout mta2 */
if ( !check_extensions(streamFile,"mta2,bgm,dbm"))
goto fail;
/* base header (everything is very similar to MGS3's MTAF but BE) */
if (read_32bitBE(0x00,streamFile) == 0x4d544132) { /* "MTA2" (.mta) */
//block_offset = 0;
header_offset = 0x00;
} else if (read_32bitBE(0x20,streamFile) == 0x4d544132) { /* "MTA2" @ 0x20 (.bgm) */
//block_offset = 0x10;
header_offset = 0x20;
} else if (read_32bitBE(0x00, streamFile) == 0x444C424D
&& read_32bitBE(0x820,streamFile) == 0x4d544132) { /* "DLBM" + "MTA2" @ 0x820 (.dbm) */
//block_offset = 0x810;
header_offset = 0x820;
} else {
goto fail;
}
/* 0x04(4): file size -4-4 (not including block headers in case of block layout) */
/* 0x08(4): version? (1), 0x0c(52): null */
/* HEAD chunk */
if (read_32bitBE(header_offset+0x40, streamFile) != 0x48454144) /* "HEAD" */
goto fail;
if (read_32bitBE(header_offset+0x44, streamFile) != 0xB0) /* HEAD size */
goto fail;
/* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */
channel_count = read_16bitBE(header_offset+0x56, streamFile); /* counting all tracks */
/* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */
/* 0x80 .. 0xf8: null */
loop_start = read_32bitBE(header_offset+0x58, streamFile);
loop_end = read_32bitBE(header_offset+0x5c, streamFile);
loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */
#if 0
/* those values look like some kind of loop offsets */
if (loop_start/0x100 != read_32bitBE(header_offset+0x68, streamFile) ||
loop_end /0x100 != read_32bitBE(header_offset+0x6C, streamFile) ) {
VGM_LOG("MTA2: wrong loop points\n");
goto fail;
}
#endif
sample_rate = read_32bitBE(header_offset+0x7c, streamFile);
if (sample_rate) { /* sample rate in 32b float (WHY?) typically 48000.0 */
float sample_float;
memcpy(&sample_float, &sample_rate, 4);
sample_rate = (int)sample_float;
} else { /* default when not specified (most of the time) */
sample_rate = 48000;
}
/* TRKP chunks (x16) */
/* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */
/* there is channel layout bitmask @ 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely:
* FRONT_L = 0x01, FRONT_R = 0x02, FRONT_M = 0x04, BACK_L = 0x08, BACK_R = 0x10, BACK_M = 0x20 */
/* DATA chunk */
if (read_32bitBE(header_offset+0x7f8, streamFile) != 0x44415441) // "DATA"
goto fail;
/* 0x7fc: data size (without blocks in case of blocked layout) */
start_offset = header_offset + 0x800;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = loop_end;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
vgmstream->coding_type = coding_MTA2;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_PS3_MTA2;
/* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -360,6 +360,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_rsd6xma,
init_vgmstream_ta_aac_x360,
init_vgmstream_ta_aac_ps3,
init_vgmstream_ps3_mta2,
#ifdef VGM_USE_FFMPEG
init_vgmstream_mp4_aac_ffmpeg,
@ -1065,6 +1066,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return 54;
case coding_MTAF:
return 0x80*2;
case coding_MTA2:
return 0x80*2;
case coding_MC3:
return 10;
case coding_CRI_HCA:
@ -1191,6 +1194,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
case coding_MSADPCM:
case coding_MTAF:
return vgmstream->interleave_block_size;
case coding_MTA2:
return 0x90;
case coding_MC3:
return 4;
default:
@ -1735,6 +1740,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
chan, vgmstream->channels);
}
break;
case coding_MTA2:
for (chan=0;chan<vgmstream->channels;chan++) {
decode_mta2(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do,
chan);
}
break;
case coding_MC3:
for (chan=0;chan<vgmstream->channels;chan++) {
decode_mc3(vgmstream, &vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,

View File

@ -134,6 +134,7 @@ typedef enum {
coding_SASSC, /* Activision EXAKT SASSC DPCM */
coding_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/
coding_MTAF, /* Konami MTAF ADPCM (IMA-derived) */
coding_MTA2, /* Konami MTA2 ADPCM */
coding_MC3, /* Paradigm MC3 3-bit ADPCM */
/* others */
@ -615,6 +616,7 @@ typedef enum {
meta_GTD, /* Knights Contract (X360/PS3), Valhalla Knights 3 (PSV) */
meta_TA_AAC_X360, /* tri-ace AAC (Star Ocean 4, End of Eternity, Infinite Undiscovery) */
meta_TA_AAC_PS3, /* tri-ace AAC (Star Ocean International, Resonance of Fate) */
meta_PS3_MTA2, /* Metal Gear Solid 4 MTA2 */
#ifdef VGM_USE_VORBIS
meta_OGG_VORBIS, /* Ogg Vorbis */