Add AWC MP3 [Red Dead Redemption (PS3), GTA5 (PS3)]

This commit is contained in:
bnnm 2017-08-28 15:14:24 +02:00
parent c4a6e6e194
commit 953022b983
15 changed files with 445 additions and 12 deletions

View File

@ -49,6 +49,7 @@ VGMSTREAM_DECLARE_FILE_TYPE("AST", ast);
VGMSTREAM_DECLARE_FILE_TYPE("ATRAC3plus", at3); VGMSTREAM_DECLARE_FILE_TYPE("ATRAC3plus", at3);
VGMSTREAM_DECLARE_FILE_TYPE("AUD", aud); VGMSTREAM_DECLARE_FILE_TYPE("AUD", aud);
VGMSTREAM_DECLARE_FILE_TYPE("AUS", aus); VGMSTREAM_DECLARE_FILE_TYPE("AUS", aus);
VGMSTREAM_DECLARE_FILE_TYPE("AWC", awc);
VGMSTREAM_DECLARE_FILE_TYPE("B1S", b1s); VGMSTREAM_DECLARE_FILE_TYPE("B1S", b1s);
VGMSTREAM_DECLARE_FILE_TYPE("BAF", baf); VGMSTREAM_DECLARE_FILE_TYPE("BAF", baf);

View File

@ -63,7 +63,7 @@ supports Winamp plugins you may also use in_vgmstream.dll instead.
Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream exts, Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream exts,
you need to manually fix it by going to options > plugins > input > vgmstream you need to manually fix it by going to options > plugins > input > vgmstream
and in the "priority filetypes" put: ckd,fsb,genh,msf,p3d,rak,scd,xvag and in the "priority filetypes" put: awc,ckd,fsb,genh,msf,p3d,rak,scd,xvag
--- foo_input_vgmstream --- --- foo_input_vgmstream ---
Every should be installed automatically by the .fb2k-component bundle. Every should be installed automatically by the .fb2k-component bundle.

View File

@ -54,7 +54,6 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m
break; break;
case MPEG_LYN: case MPEG_LYN:
case MPEG_AWC:
goto fail; /* not fully implemented */ goto fail; /* not fully implemented */
case MPEG_STANDARD: case MPEG_STANDARD:

View File

@ -0,0 +1,138 @@
#include "mpeg_decoder.h"
#ifdef VGM_USE_MPEG
/**
* AWC music uses blocks (sfx doesn't), the fun part being each channel has different num_samples/frames
* per block, so it's unsuitable for the normal "blocked" layout and parsed here instead.
* Channel data is separate within the block (first all frames of ch0, then ch1, etc), padded, and sometimes
* the last few frames of a channel are repeated in the new block (marked with the "discard samples" field).
*/
static size_t get_block_header_size(STREAMFILE *streamFile, off_t offset, mpeg_codec_data *data) {
size_t header_size = 0;
int i;
int entries = data->config.channels;
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
for (i = 0; i < entries; i++) {
header_size += 0x18;
header_size += read_32bit(offset + 0x18*i + 0x04, streamFile) * 0x04; /* entries in the table */
}
if (header_size % 0x800) /* padded */
header_size += 0x800 - (header_size % 0x800);
return header_size;
}
/* init config and validate */
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
mpeg_frame_info info;
int is_music;
/* start_offset can point to a block header that always starts with 0 (music) or normal data (sfx) */
is_music = read_32bitBE(start_offset, streamFile) == 0x00000000;
if (is_music)
start_offset += get_block_header_size(streamFile, start_offset, data);
/* get frame info at offset */
if ( !mpeg_get_frame_info(streamFile, start_offset, &info))
goto fail;
switch(info.layer) {
case 1: *coding_type = coding_MPEG_layer1; break;
case 2: *coding_type = coding_MPEG_layer2; break;
case 3: *coding_type = coding_MPEG_layer3; break;
default: goto fail;
}
data->channels_per_frame = info.channels;
data->samples_per_frame = info.frame_samples;
/* extra checks */
if (is_music) {
if (data->config.chunk_size <= 0)
goto fail; /* needs block size */
}
/* no big encoder delay added (for sfx they can start in less than ~300 samples) */
return 1;
fail:
return 0;
}
/* writes data to the buffer and moves offsets, parsing AWC blocks */
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
mpeg_custom_stream *ms = data->streams[num_stream];
mpeg_frame_info info;
size_t current_data_size = 0, data_offset;
size_t file_size = get_streamfile_size(stream->streamfile);
int i;
/* blocked layout used for music */
if (data->config.chunk_size) {
/* block ended for this channel, move to next block start */
if (ms->current_size_count > 0 && ms->current_size_count == ms->current_size_target) {
//mpg123_open_feed(ms->m); //todo reset maybe needed?
data_offset = stream->offset - stream->channel_start_offset; /* ignoring header */
data_offset -= data_offset % data->config.chunk_size; /* start of current block */
stream->offset = stream->channel_start_offset + data_offset + data->config.chunk_size;
ms->current_size_count = 0;
ms->current_size_target = 0;
}
/* just in case, shouldn't happen */
if (stream->offset >= file_size) {
goto fail;
}
/* block starts for this channel, point to mpeg data */
if (ms->current_size_count == 0) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->config.big_endian ? read_32bitBE : read_32bitLE;
off_t channel_offset = 0;
/* block has a header with base info per channel and table per channel (see blocked_awc.c) */
ms->decode_to_discard = read_32bit(stream->offset + 0x18*num_stream + 0x08, stream->streamfile);
ms->current_size_target = read_32bit(stream->offset + 0x18*num_stream + 0x14, stream->streamfile);
for (i = 0; i < num_stream; i++) { /* num_stream serves as channel */
size_t channel_size = read_32bit(stream->offset + 0x18*i + 0x14, stream->streamfile);
if (channel_size % 0x10) /* 32b aligned */
channel_size += 0x10 - channel_size % 0x10;
channel_offset += channel_size;
}
//VGM_ASSERT(ms->decode_to_discard > 0, "AWC: s%i discard of %x found at chunk %lx\n", num_stream, ms->decode_to_discard, stream->offset);
//;VGM_LOG("AWC: s%i off=%lx to %lx\n", num_stream, stream->offset, stream->offset + channel_offset + get_block_header_size(stream->streamfile, stream->offset, data));
stream->offset += channel_offset + get_block_header_size(stream->streamfile, stream->offset, data);
}
}
/* update frame */
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
goto fail;
current_data_size = info.frame_size;
data->bytes_in_buffer = read_streamfile(data->buffer,stream->offset, current_data_size, stream->streamfile);
stream->offset += current_data_size;
ms->current_size_count += current_data_size;
return 1;
fail:
return 0;
}
#endif

View File

@ -579,7 +579,7 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
if (decode_to_discard == 576) if (decode_to_discard == 576)
decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_number; decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_number;
data->decode_to_discard += decode_to_discard; ms->decode_to_discard += decode_to_discard;
} }
} }

View File

@ -140,6 +140,7 @@ mpeg_codec_data *init_mpeg_custom_codec_data(STREAMFILE *streamFile, off_t start
case MPEG_EAL31: case MPEG_EAL31:
case MPEG_EAL32P: case MPEG_EAL32P:
case MPEG_EAL32S: ok = mpeg_custom_setup_init_ealayer3(streamFile, start_offset, data, coding_type); break; case MPEG_EAL32S: ok = mpeg_custom_setup_init_ealayer3(streamFile, start_offset, data, coding_type); break;
case MPEG_AWC: ok = mpeg_custom_setup_init_awc(streamFile, start_offset, data, coding_type); break;
default: ok = mpeg_custom_setup_init_default(streamFile, start_offset, data, coding_type); break; default: ok = mpeg_custom_setup_init_default(streamFile, start_offset, data, coding_type); break;
} }
if (!ok) if (!ok)
@ -393,6 +394,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data
case MPEG_EAL32P: case MPEG_EAL32P:
case MPEG_EAL32S: ok = mpeg_custom_parse_frame_ealayer3(stream, data, num_stream); break; case MPEG_EAL32S: ok = mpeg_custom_parse_frame_ealayer3(stream, data, num_stream); break;
case MPEG_AHX: ok = mpeg_custom_parse_frame_ahx(stream, data); break; case MPEG_AHX: ok = mpeg_custom_parse_frame_ahx(stream, data); break;
case MPEG_AWC: ok = mpeg_custom_parse_frame_awc(stream, data, num_stream); break;
default: ok = mpeg_custom_parse_frame_default(stream, data); break; default: ok = mpeg_custom_parse_frame_default(stream, data); break;
} }
if (!ok) { if (!ok) {
@ -440,16 +442,16 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data
} }
samples_filled = (bytes_done / sizeof(sample) / data->channels_per_frame); samples_filled = (bytes_done / sizeof(sample) / data->channels_per_frame);
/* for EALayer3, that discards decoded samples and writes PCM blocks instead */ /* discard for weird features (EALayer3 and PCM blocks, AWC and repeated frames) */
if (data->decode_to_discard) { if (ms->decode_to_discard) {
size_t bytes_to_discard = 0; size_t bytes_to_discard = 0;
size_t decode_to_discard = data->decode_to_discard; size_t decode_to_discard = ms->decode_to_discard;
if (decode_to_discard > samples_filled) if (decode_to_discard > samples_filled)
decode_to_discard = samples_filled; decode_to_discard = samples_filled;
bytes_to_discard = sizeof(sample)*decode_to_discard*data->channels_per_frame; bytes_to_discard = sizeof(sample)*decode_to_discard*data->channels_per_frame;
bytes_done -= bytes_to_discard; bytes_done -= bytes_to_discard;
data->decode_to_discard -= decode_to_discard; ms->decode_to_discard -= decode_to_discard;
ms->samples_used += decode_to_discard; ms->samples_used += decode_to_discard;
} }
@ -522,10 +524,10 @@ void reset_mpeg(VGMSTREAM *vgmstream) {
mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset);
data->streams[i]->samples_filled = 0; data->streams[i]->samples_filled = 0;
data->streams[i]->samples_used = 0; data->streams[i]->samples_used = 0;
data->streams[i]->decode_to_discard = 0;
} }
data->samples_to_discard = data->skip_samples; /* initial delay */ data->samples_to_discard = data->skip_samples; /* initial delay */
data->decode_to_discard = 0;
} }
} }
@ -546,6 +548,7 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset);
data->streams[i]->samples_filled = 0; data->streams[i]->samples_filled = 0;
data->streams[i]->samples_used = 0; data->streams[i]->samples_used = 0;
data->streams[i]->decode_to_discard = 0;
if (vgmstream->loop_ch) if (vgmstream->loop_ch)
vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset; vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset;
@ -558,7 +561,6 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
data->buffer_full = 0; data->buffer_full = 0;
data->buffer_used = 0; data->buffer_used = 0;
data->decode_to_discard = 0;
} }
/* resets mpg123 decoder and its internals (with mpg123_open_feed as mpg123_feedseek won't work) */ /* resets mpg123 decoder and its internals (with mpg123_open_feed as mpg123_feedseek won't work) */
@ -577,10 +579,10 @@ void flush_mpeg(mpeg_codec_data * data) {
mpg123_open_feed(data->streams[i]->m); mpg123_open_feed(data->streams[i]->m);
data->streams[i]->samples_filled = 0; data->streams[i]->samples_filled = 0;
data->streams[i]->samples_used = 0; data->streams[i]->samples_used = 0;
data->streams[i]->decode_to_discard = 0;
} }
data->samples_to_discard = data->skip_samples; /* initial delay */ data->samples_to_discard = data->skip_samples; /* initial delay */
data->decode_to_discard = 0;
} }
data->bytes_in_buffer = 0; data->bytes_in_buffer = 0;

View File

@ -19,10 +19,12 @@ int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_info *
int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type); int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type); int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data); int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data);
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data); int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data);
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream); int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
#endif/* VGM_USE_MPEG */ #endif/* VGM_USE_MPEG */

View File

@ -42,6 +42,7 @@ static const char* extension_list[] = {
"at3", "at3",
"aud", "aud",
"aus", "aus",
"awc",
"b1s", "b1s",
"baf", "baf",
@ -880,6 +881,7 @@ static const meta_info meta_info_list[] = {
{meta_STM, "Angel Studios/Rockstar San Diego STMA header"}, {meta_STM, "Angel Studios/Rockstar San Diego STMA header"},
{meta_BINK, "RAD Game Tools Bink header"}, {meta_BINK, "RAD Game Tools Bink header"},
{meta_EA_SNU, "Electronic Arts SNU header"}, {meta_EA_SNU, "Electronic Arts SNU header"},
{meta_AWC, "Rockstar AWC header"},
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
{meta_OGG_VORBIS, "Ogg Vorbis"}, {meta_OGG_VORBIS, "Ogg Vorbis"},

View File

@ -1398,6 +1398,10 @@
RelativePath=".\coding\mpeg_custom_utils_ahx.c" RelativePath=".\coding\mpeg_custom_utils_ahx.c"
> >
</File> </File>
<File
RelativePath=".\coding\mpeg_custom_utils_awc.c"
>
</File>
<File <File
RelativePath=".\coding\mpeg_custom_utils_ealayer3.c" RelativePath=".\coding\mpeg_custom_utils_ealayer3.c"
> >

View File

@ -427,6 +427,7 @@
<ClCompile Include="coding\mc3_decoder.c" /> <ClCompile Include="coding\mc3_decoder.c" />
<ClCompile Include="coding\mpeg_custom_utils.c" /> <ClCompile Include="coding\mpeg_custom_utils.c" />
<ClCompile Include="coding\mpeg_custom_utils_ahx.c" /> <ClCompile Include="coding\mpeg_custom_utils_ahx.c" />
<ClCompile Include="coding\mpeg_custom_utils_awc.c" />
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c" /> <ClCompile Include="coding\mpeg_custom_utils_ealayer3.c" />
<ClCompile Include="coding\mpeg_decoder.c" /> <ClCompile Include="coding\mpeg_decoder.c" />
<ClCompile Include="coding\msadpcm_decoder.c" /> <ClCompile Include="coding\msadpcm_decoder.c" />

View File

@ -817,6 +817,9 @@
<ClCompile Include="coding\mpeg_custom_utils_ahx.c"> <ClCompile Include="coding\mpeg_custom_utils_ahx.c">
<Filter>coding\Source Files</Filter> <Filter>coding\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="coding\mpeg_custom_utils_awc.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c"> <ClCompile Include="coding\mpeg_custom_utils_ealayer3.c">
<Filter>coding\Source Files</Filter> <Filter>coding\Source Files</Filter>
</ClCompile> </ClCompile>

272
src/meta/awc.c Normal file
View File

@ -0,0 +1,272 @@
#include "meta.h"
#include "../coding/coding.h"
typedef struct {
int big_endian;
int is_encrypted;
int is_music;
int total_streams;
int channel_count;
int sample_rate;
int codec;
int num_samples;
int block_chunk;
off_t stream_offset;
off_t stream_size;
} awc_header;
static int parse_awc_header(STREAMFILE* streamFile, awc_header* awc);
/* AWC - from RAGE (Rockstar Advanced Game Engine) audio (Red Dead Redemption, Max Payne 3, GTA5) */
VGMSTREAM * init_vgmstream_awc(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
awc_header awc;
/* check extension */
if (!check_extensions(streamFile,"awc"))
goto fail;
/* check header */
if (!parse_awc_header(streamFile, &awc))
goto fail;
if (awc.is_encrypted)
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(awc.channel_count, 0);
if (!vgmstream) goto fail;
vgmstream->sample_rate = awc.sample_rate;
vgmstream->num_samples = awc.num_samples;
vgmstream->num_streams = awc.total_streams;
vgmstream->meta_type = meta_AWC;
switch(awc.codec) {
//case 0x01: /* PCM (PC/PS3) */
// vgmstream->coding_type = coding_PCM!6;
// vgmstream->layout_type = awc.is_music ? layout_blocked_awc : layout_none;
// break;
//case 0x04: /* IMA (PC) */
// vgmstream->coding_type = coding_AWC_IMA;
// vgmstream->layout_type = awc.is_music ? layout_blocked_awc : layout_none;
// break;
#ifdef VGM_USE_MPEG
case 0x07: { /* MPEG (PS3) */
mpeg_custom_config cfg;
memset(&cfg, 0, sizeof(mpeg_custom_config));
cfg.chunk_size = awc.block_chunk;
cfg.big_endian = awc.big_endian;
vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
break;
}
#endif
case 0x05: /* XMA2 (X360) */
default:
VGM_LOG("AWC: unknown codec 0x%02x\n", awc.codec);
goto fail;
}
/* open files; channel offsets are updated below */
if (!vgmstream_open_stream(vgmstream,streamFile,awc.stream_offset))
goto fail;
//if (vgmstream->layout_type == layout_blocked_awc)
// update_
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
/* Parse Rockstar's AWC header (much info from LibertyV: https://github.com/koolkdev/libertyv).
* Made of entries for N streams, each with a number of tags pointing to chunks (header, data, events, etc). */
static int parse_awc_header(STREAMFILE* streamFile, awc_header* awc) {
int64_t (*read_64bit)(off_t,STREAMFILE*) = NULL;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
int i, ch, entries;
uint32_t flags, info_header, tag_count = 0, tags_skip = 0;
off_t off;
int target_stream = streamFile->stream_index;
memset(awc,0,sizeof(awc_header));
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x41444154 && /* "ADAT" (LE) */
read_32bitBE(0x00,streamFile) != 0x54414441) /* "TADA" (BE) */
goto fail;
awc->big_endian = read_32bitBE(0x00,streamFile) == 0x54414441;
if (awc->big_endian) {
read_64bit = read_64bitBE;
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
} else {
read_64bit = read_64bitLE;
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
}
flags = read_32bit(0x04,streamFile);
entries = read_32bit(0x08,streamFile);
//header_size = read_32bit(0x0c,streamFile); /* after to stream id/tags, not including chunks */
off = 0x10;
if ((flags & 0xFF00FFFF) != 0xFF000001 || (flags & 0x00F00000)) {
VGM_LOG("AWC: unknown flags 0x%08x\n", flags);
goto fail;
}
if (flags & 0x00010000) /* some kind of mini offset table */
off += 0x2 * entries;
//if (flags % 0x00020000) /* seems to indicate chunks are not ordered (ie. header may go after data) */
// ...
//if (flags % 0x00040000) /* music/multichannel flag? (GTA5, not seen in RDR) */
// awc->is_music = 1;
if (flags & 0x00080000) /* encrypted data chunk (most of GTA5 PC) */
awc->is_encrypted = 1;
/* Music when the first id is 0 (base/fake entry with info for all channels), sfx pack otherwise.
* sfx = N single streams, music = N-1 interleaved mono channels (even for MP3/XMA).
* Music seems layered (N-1/2 stereo pairs), maybe set with events? */
awc->is_music = (read_32bit(off + 0x00,streamFile) & 0x1FFFFFFF) == 0x00000000;
if (awc->is_music) { /* all streams except id 0 is a channel */
awc->total_streams = 1;
target_stream = 1; /* we only need id 0, though channels may have its own tags/chunks */
}
else { /* each stream is a single sound */
awc->total_streams = entries;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || target_stream > awc->total_streams || awc->total_streams < 1) goto fail;
}
/* get stream base info */
for (i = 0; i < entries; i++) {
info_header = read_32bit(off + 0x04*i, streamFile);
tag_count = (info_header >> 29) & 0x7; /* 3b */
//id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
if (target_stream-1 == i)
break;
tags_skip += tag_count; /* tags to skip to reach target's tags, in the next header */
}
off += 0x04*entries;
off += 0x08*tags_skip;
/* get stream tags */
for (i = 0; i < tag_count; i++) {
uint64_t tag_header, tag, size, offset;
tag_header = (uint64_t)read_64bit(off + 0x08*i,streamFile);
tag = (tag_header >> 56) & 0xFF; /* 8b */
size = (tag_header >> 28) & 0x0FFFFFFF; /* 28b */
offset = (tag_header >> 0) & 0x0FFFFFFF; /* 28b */
/* Tags are apparently part of a hash derived from a word ("data", "format", etc).
* If music + 1ch, the header and data chunks can repeat for no reason (sometimes not even pointed). */
switch(tag) {
case 0x55: /* data */
awc->stream_offset = offset;
awc->stream_size = size;
break;
case 0x48: /* music header */
if (!awc->is_music) {
VGM_LOG("AWC: music header found in sfx\n");
goto fail;
}
/* 0x00(32): unknown (some count?) */
awc->block_chunk = read_32bit(offset + 0x04,streamFile);
awc->channel_count = read_32bit(offset + 0x08,streamFile);
if (awc->channel_count != entries - 1) { /* not counting id-0 */
VGM_LOG("AWC: number of music channels doesn't match entries\n");
goto fail;
}
for (ch = 0; ch < awc->channel_count; ch++) {
int num_samples, sample_rate, codec;
/* 0x00(32): stream id (not always in the header entries order) */
/* 0x08(16): headroom?, 0x0d(8): round size?, 0x0e(16): unknown (zero?) */
num_samples = read_32bit(offset + 0x0c + 0x10*ch + 0x04,streamFile);
sample_rate = (uint16_t)read_16bit(offset + 0x0c + 0x10*ch + 0x0a,streamFile);
codec = read_8bit(offset + 0x0c + 0x10*ch + 0x0c, streamFile);
/* validate as all channels should repeat this */
if ((awc->num_samples && awc->num_samples != num_samples) ||
(awc->sample_rate && awc->sample_rate != sample_rate) ||
(awc->codec && awc->codec != codec)) {
VGM_LOG("AWC: found header diffs between channels\n"); /* can rarely happen in stereo pairs */
goto fail;
}
awc->num_samples = num_samples;
awc->sample_rate = sample_rate;
awc->codec = codec;
}
break;
case 0xFA: /* sfx header */
if (awc->is_music) {
VGM_LOG("AWC: sfx header found in music\n");
goto fail;
}
/* 0x04(32): -1?, 0x0a(16x4): unknown x4, 0x12: null? */
awc->num_samples = read_32bit(offset + 0x00,streamFile);
awc->sample_rate = (uint16_t)read_16bit(offset + 0x08,streamFile);
awc->codec = read_8bit(offset + 0x13, streamFile);
awc->channel_count = 1;
break;
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block) */
case 0xBD: /* events (32bx4): type_hash, params_hash, timestamp_ms, flags */
default: /* 0x5C=animation/RSC?, 0x68=midi?, 0x36/0x2B/0x5A/0xD9=? */
//VGM_LOG("AWC: ignoring unknown tag 0x%02x\n", tag);
break;
}
}
if (!awc->stream_offset) {
VGM_LOG("AWC: stream offset not found\n");
goto fail;
}
/* If music, data is divided into blocks of block_chunk size with padding.
* Each block has a header/seek table and interleaved data for all channels */
if (awc->is_music && read_32bit(awc->stream_offset, streamFile) != 0) {
VGM_LOG("AWC: music found, but block doesn't start with seek table\n");
goto fail;
}
return 1;
fail:
return 0;
}

View File

@ -684,4 +684,6 @@ VGMSTREAM * init_vgmstream_stm(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awc(STREAMFILE * streamFile);
#endif /*_META_H*/ #endif /*_META_H*/

View File

@ -367,6 +367,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
init_vgmstream_sk_aud, init_vgmstream_sk_aud,
init_vgmstream_stm, init_vgmstream_stm,
init_vgmstream_ea_snu, init_vgmstream_ea_snu,
init_vgmstream_awc,
init_vgmstream_txth, /* should go at the end (lower priority) */ init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG

View File

@ -621,6 +621,7 @@ typedef enum {
meta_STM, /* Angel Studios/Rockstar San Diego Games */ meta_STM, /* Angel Studios/Rockstar San Diego Games */
meta_BINK, /* RAD Game Tools BINK audio/video */ meta_BINK, /* RAD Game Tools BINK audio/video */
meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */ meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */
meta_AWC, /* Rockstar AWC (GTA5, RDR) */
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
meta_OGG_VORBIS, /* Ogg Vorbis */ meta_OGG_VORBIS, /* Ogg Vorbis */
@ -874,7 +875,7 @@ typedef enum {
MPEG_EAL32P, /* EALayer3 v2 "P" (PCM?), custom frames with v2 header */ MPEG_EAL32P, /* EALayer3 v2 "P" (PCM?), custom frames with v2 header */
MPEG_EAL32S, /* EALayer3 v2 "S" (Spike?), custom frames with v2 header */ MPEG_EAL32S, /* EALayer3 v2 "S" (Spike?), custom frames with v2 header */
MPEG_LYN, /* N streams of fixed interleave */ MPEG_LYN, /* N streams of fixed interleave */
MPEG_AWC /* N streams in absolute offsets (consecutive) */ MPEG_AWC /* N streams in block layout (music) or absolute offsets (sfx) */
} mpeg_custom_t; } mpeg_custom_t;
/* config for the above modes */ /* config for the above modes */
@ -884,6 +885,7 @@ typedef struct {
int chunk_size; /* size of a data portion */ int chunk_size; /* size of a data portion */
int interleave; /* size of stream interleave */ int interleave; /* size of stream interleave */
int encryption; /* encryption mode */ int encryption; /* encryption mode */
int big_endian;
/* for AHX */ /* for AHX */
int cri_type; int cri_type;
uint16_t cri_key1; uint16_t cri_key1;
@ -899,6 +901,11 @@ typedef struct {
size_t output_buffer_size; size_t output_buffer_size;
size_t samples_filled; /* data in the buffer (in samples) */ size_t samples_filled; /* data in the buffer (in samples) */
size_t samples_used; /* data extracted from the buffer */ size_t samples_used; /* data extracted from the buffer */
size_t current_size_count; /* data read (if the parser needs to know) */
size_t current_size_target; /* max data, until something happens */
size_t decode_to_discard; /* discard from this stream only (for EALayer3 or AWC) */
} mpeg_custom_stream; } mpeg_custom_stream;
typedef struct { typedef struct {
@ -924,7 +931,6 @@ typedef struct {
size_t skip_samples; /* base encoder delay */ size_t skip_samples; /* base encoder delay */
size_t samples_to_discard; /* for custom mpeg looping */ size_t samples_to_discard; /* for custom mpeg looping */
size_t decode_to_discard; /* for EALayer3, that discards decoded samples and writes PCM blocks in their place */
} mpeg_codec_data; } mpeg_codec_data;
#endif #endif