mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-17 23:36:41 +01:00
Add AWC MP3 [Red Dead Redemption (PS3), GTA5 (PS3)]
This commit is contained in:
parent
c4a6e6e194
commit
953022b983
@ -49,6 +49,7 @@ VGMSTREAM_DECLARE_FILE_TYPE("AST", ast);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("ATRAC3plus", at3);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("AUD", aud);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("AUS", aus);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("AWC", awc);
|
||||
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("B1S", b1s);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("BAF", baf);
|
||||
|
@ -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,
|
||||
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 ---
|
||||
Every should be installed automatically by the .fb2k-component bundle.
|
||||
|
@ -54,7 +54,6 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m
|
||||
break;
|
||||
|
||||
case MPEG_LYN:
|
||||
case MPEG_AWC:
|
||||
goto fail; /* not fully implemented */
|
||||
|
||||
case MPEG_STANDARD:
|
||||
|
138
src/coding/mpeg_custom_utils_awc.c
Normal file
138
src/coding/mpeg_custom_utils_awc.c
Normal 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
|
@ -579,7 +579,7 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
|
||||
if (decode_to_discard == 576)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,7 @@ mpeg_codec_data *init_mpeg_custom_codec_data(STREAMFILE *streamFile, off_t start
|
||||
case MPEG_EAL31:
|
||||
case MPEG_EAL32P:
|
||||
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;
|
||||
}
|
||||
if (!ok)
|
||||
@ -393,6 +394,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data
|
||||
case MPEG_EAL32P:
|
||||
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_AWC: ok = mpeg_custom_parse_frame_awc(stream, data, num_stream); break;
|
||||
default: ok = mpeg_custom_parse_frame_default(stream, data); break;
|
||||
}
|
||||
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);
|
||||
|
||||
/* for EALayer3, that discards decoded samples and writes PCM blocks instead */
|
||||
if (data->decode_to_discard) {
|
||||
/* discard for weird features (EALayer3 and PCM blocks, AWC and repeated frames) */
|
||||
if (ms->decode_to_discard) {
|
||||
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)
|
||||
decode_to_discard = samples_filled;
|
||||
bytes_to_discard = sizeof(sample)*decode_to_discard*data->channels_per_frame;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -522,10 +524,10 @@ void reset_mpeg(VGMSTREAM *vgmstream) {
|
||||
mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset);
|
||||
data->streams[i]->samples_filled = 0;
|
||||
data->streams[i]->samples_used = 0;
|
||||
data->streams[i]->decode_to_discard = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
data->streams[i]->samples_filled = 0;
|
||||
data->streams[i]->samples_used = 0;
|
||||
data->streams[i]->decode_to_discard = 0;
|
||||
|
||||
if (vgmstream->loop_ch)
|
||||
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_used = 0;
|
||||
data->decode_to_discard = 0;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
data->streams[i]->samples_filled = 0;
|
||||
data->streams[i]->samples_used = 0;
|
||||
data->streams[i]->decode_to_discard = 0;
|
||||
}
|
||||
|
||||
data->samples_to_discard = data->skip_samples; /* initial delay */
|
||||
data->decode_to_discard = 0;
|
||||
}
|
||||
|
||||
data->bytes_in_buffer = 0;
|
||||
|
@ -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_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_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_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||
|
||||
#endif/* VGM_USE_MPEG */
|
||||
|
||||
|
@ -42,6 +42,7 @@ static const char* extension_list[] = {
|
||||
"at3",
|
||||
"aud",
|
||||
"aus",
|
||||
"awc",
|
||||
|
||||
"b1s",
|
||||
"baf",
|
||||
@ -880,6 +881,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_STM, "Angel Studios/Rockstar San Diego STMA header"},
|
||||
{meta_BINK, "RAD Game Tools Bink header"},
|
||||
{meta_EA_SNU, "Electronic Arts SNU header"},
|
||||
{meta_AWC, "Rockstar AWC header"},
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{meta_OGG_VORBIS, "Ogg Vorbis"},
|
||||
|
@ -1398,6 +1398,10 @@
|
||||
RelativePath=".\coding\mpeg_custom_utils_ahx.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\mpeg_custom_utils_awc.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\mpeg_custom_utils_ealayer3.c"
|
||||
>
|
||||
|
@ -427,6 +427,7 @@
|
||||
<ClCompile Include="coding\mc3_decoder.c" />
|
||||
<ClCompile Include="coding\mpeg_custom_utils.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_decoder.c" />
|
||||
<ClCompile Include="coding\msadpcm_decoder.c" />
|
||||
|
@ -817,6 +817,9 @@
|
||||
<ClCompile Include="coding\mpeg_custom_utils_ahx.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\mpeg_custom_utils_awc.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
272
src/meta/awc.c
Normal file
272
src/meta/awc.c
Normal 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;
|
||||
}
|
@ -684,4 +684,6 @@ VGMSTREAM * init_vgmstream_stm(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_awc(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -367,6 +367,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_sk_aud,
|
||||
init_vgmstream_stm,
|
||||
init_vgmstream_ea_snu,
|
||||
init_vgmstream_awc,
|
||||
|
||||
init_vgmstream_txth, /* should go at the end (lower priority) */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
@ -621,6 +621,7 @@ typedef enum {
|
||||
meta_STM, /* Angel Studios/Rockstar San Diego Games */
|
||||
meta_BINK, /* RAD Game Tools BINK audio/video */
|
||||
meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */
|
||||
meta_AWC, /* Rockstar AWC (GTA5, RDR) */
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
meta_OGG_VORBIS, /* Ogg Vorbis */
|
||||
@ -874,7 +875,7 @@ typedef enum {
|
||||
MPEG_EAL32P, /* EALayer3 v2 "P" (PCM?), custom frames with v2 header */
|
||||
MPEG_EAL32S, /* EALayer3 v2 "S" (Spike?), custom frames with v2 header */
|
||||
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;
|
||||
|
||||
/* config for the above modes */
|
||||
@ -884,6 +885,7 @@ typedef struct {
|
||||
int chunk_size; /* size of a data portion */
|
||||
int interleave; /* size of stream interleave */
|
||||
int encryption; /* encryption mode */
|
||||
int big_endian;
|
||||
/* for AHX */
|
||||
int cri_type;
|
||||
uint16_t cri_key1;
|
||||
@ -899,6 +901,11 @@ typedef struct {
|
||||
size_t output_buffer_size;
|
||||
size_t samples_filled; /* data in the buffer (in samples) */
|
||||
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;
|
||||
|
||||
typedef struct {
|
||||
@ -924,7 +931,6 @@ typedef struct {
|
||||
|
||||
size_t skip_samples; /* base encoder delay */
|
||||
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;
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user