diff --git a/src/coding/atrac9_decoder.c b/src/coding/atrac9_decoder.c index 209c37fa..4db181c7 100644 --- a/src/coding/atrac9_decoder.c +++ b/src/coding/atrac9_decoder.c @@ -9,7 +9,7 @@ struct atrac9_codec_data { uint8_t *data_buffer; size_t data_buffer_size; - sample *sample_buffer; + sample_t *sample_buffer; size_t samples_filled; /* number of samples in the buffer */ size_t samples_used; /* number of samples extracted from the buffer */ @@ -39,7 +39,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) { status = Atrac9GetCodecInfo(data->handle, &data->info); if (status < 0) goto fail; - //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, info.superframeSize, info.framesInSuperframe, info.frameSamples); + //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples); if (cfg->channels && cfg->channels != data->info.channels) { VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels); @@ -50,7 +50,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) { /* must hold at least one superframe and its samples */ data->data_buffer_size = data->info.superframeSize; data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size); - data->sample_buffer = calloc(sizeof(sample), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); + data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); data->samples_to_discard = cfg->encoder_delay; @@ -63,7 +63,7 @@ fail: return NULL; } -void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { +void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; atrac9_codec_data * data = vgmstream->codec_data; int samples_done = 0; @@ -220,20 +220,20 @@ void free_atrac9(atrac9_codec_data *data) { } -size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { - return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); -} - -#if 0 //not needed (for now) -int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size) { +static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) { static const int sample_rate_table[16] = { 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 44100, 48000, 64000, 88200, 96000,128000,176400,192000 }; + static const int samples_power_table[16] = { + 6, 6, 7, 7, 7, 8, 8, 8, + 6, 6, 7, 7, 7, 8, 8, 8 + }; static const int channel_table[8] = { 1, 2, 2, 6, 8, 4, 0, 0 }; + int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe; uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */ uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */ uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */ @@ -242,6 +242,11 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */ /* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */ + superframe_size = ((frame_size+1) << superframe_index); + frames_per_superframe = (1 << superframe_index); + samples_per_frame = 1 << samples_power_table[sample_rate_index]; + samples_per_superframe = samples_per_frame * frames_per_superframe; + if (sync != 0xFE) goto fail; if (out_sample_rate) @@ -249,11 +254,23 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c if (out_channels) *out_channels = channel_table[channels_index]; if (out_frame_size) - *out_frame_size = (frame_size+1) * (1 << superframe_index); + *out_frame_size = superframe_size; + if (out_samples_per_frame) + *out_samples_per_frame = samples_per_superframe; return 1; fail: return 0; } -#endif + +size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { + return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); +} + +size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) { + size_t frame_size, samples_per_frame; + if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame)) + return 0; + return bytes / frame_size * samples_per_frame; +} #endif diff --git a/src/coding/coding.h b/src/coding/coding.h index 7868547d..3515db70 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -260,12 +260,12 @@ void free_at3plus(maiatrac3plus_codec_data *data); #ifdef VGM_USE_ATRAC9 /* atrac9_decoder */ atrac9_codec_data *init_atrac9(atrac9_config *cfg); -void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_atrac9(VGMSTREAM *vgmstream); void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample); void free_atrac9(atrac9_codec_data *data); size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data); -//int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size); +size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config); #endif #ifdef VGM_USE_CELT diff --git a/src/formats.c b/src/formats.c index 658713d7..80e6fce3 100644 --- a/src/formats.c +++ b/src/formats.c @@ -18,6 +18,7 @@ static const char* extension_list[] = { "2dx9", "2pfs", "800", + "9tav", //"aac", //common "aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) @@ -1177,6 +1178,7 @@ static const meta_info meta_info_list[] = { {meta_STRM_ABYLIGHT, "Abylight STRM header"}, {meta_MSF_KONAMI, "Konami MSF header"}, {meta_XWMA_KONAMI, "Konami XWMA header"}, + {meta_9TAV, "Konami 9TAV header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 1edd21f4..63c71836 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -224,6 +224,10 @@ RelativePath=".\meta\aax_utf.h" > + + @@ -312,6 +316,10 @@ RelativePath=".\meta\208.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 530114a6..2f40fb24 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -99,6 +99,7 @@ + @@ -207,6 +208,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 8a0b50fc..66dd3014 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -68,6 +68,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -196,6 +199,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/9tav.c b/src/meta/9tav.c new file mode 100644 index 00000000..a2a0d695 --- /dev/null +++ b/src/meta/9tav.c @@ -0,0 +1,118 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "9tav_streamfile.h" + +/* 9TAV - from Metal Gear Solid 2/3 HD (Vita) */ +VGMSTREAM * init_vgmstream_9tav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate, track_count; + int32_t num_samples, loop_start, loop_end; + size_t track_size; + uint32_t config_data; + int i, is_padded; + layered_layout_data * data = NULL; + STREAMFILE* temp_streamFile = NULL; + + + /* checks */ + /* .9tav: header id */ + if (!check_extensions(streamFile, "9tav")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x39544156) /* "9TAV" */ + goto fail; + + /* 0x04: always 0x09 */ + channel_count = read_16bitLE(0x08,streamFile); + track_count = read_16bitLE(0x0a,streamFile); /* MGS3 uses multitracks */ + sample_rate = read_32bitLE(0x0c,streamFile); + track_size = read_32bitLE(0x10,streamFile); /* without padding */ + //data_size = read_32bitLE(0x14,streamFile); /* without padding */ + num_samples = read_32bitLE(0x18,streamFile); + config_data = read_32bitBE(0x1c,streamFile); + + + if (read_32bitBE(0x20,streamFile) == 0x4D544146) { /* "MTAF" */ + /* MGS3 has a MTAF header (data size and stuff don't match, probably for track info) */ + loop_start = read_32bitLE(0x78, streamFile); + loop_end = read_32bitLE(0x7c, streamFile); + loop_flag = read_32bitLE(0x90, streamFile) & 1; + + is_padded = 1; /* data also has padding and other oddities */ + start_offset = 0x00; + } + else { + /* MGS2 doesn't */ + loop_start = 0; + loop_end = 0; + loop_flag = 0; + + is_padded = 0; + start_offset = 0x20; + } + + + /* init layout */ + data = init_layout_layered(track_count); + if (!data) goto fail; + + /* open each layer subfile */ + for (i = 0; i < data->layer_count; i++) { + data->layers[i] = allocate_vgmstream(channel_count, loop_flag); + if (!data->layers[i]) goto fail; + + data->layers[i]->meta_type = meta_9TAV; + data->layers[i]->sample_rate = sample_rate; + data->layers[i]->num_samples = num_samples; + data->layers[i]->loop_start_sample = loop_start; + data->layers[i]->loop_end_sample = loop_end; + +#ifdef VGM_USE_ATRAC9 + { + atrac9_config cfg = {0}; + cfg.channels = channel_count; + cfg.config_data = config_data; + cfg.encoder_delay = atrac9_bytes_to_samples_cfg(track_size, cfg.config_data) - num_samples; /* seems ok */ + if (cfg.encoder_delay > 4096) /* doesn't seem too normal */ + cfg.encoder_delay = 0; + + data->layers[i]->codec_data = init_atrac9(&cfg); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_ATRAC9; + data->layers[i]->layout_type = layout_none; + } +#else + goto fail; +#endif + + if (is_padded) { + temp_streamFile = setup_9tav_streamfile(streamFile, 0xFE4, track_size, i, track_count); + if (!temp_streamFile) goto fail; + } + + if (!vgmstream_open_stream(data->layers[i],temp_streamFile == NULL ? streamFile : temp_streamFile,start_offset)) + goto fail; + + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + + /* build the layered VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data); + if (!vgmstream) goto fail; + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_layered(data); + return NULL; +} diff --git a/src/meta/9tav_streamfile.h b/src/meta/9tav_streamfile.h new file mode 100644 index 00000000..9cc89135 --- /dev/null +++ b/src/meta/9tav_streamfile.h @@ -0,0 +1,182 @@ +#ifndef _9TAV_STREAMFILE_H_ +#define _9TAV_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + size_t track_size; + int track_number; + int track_count; + int skip_count; + int read_count; + size_t frame_size; + size_t interleave_count; + size_t interleave_last_count; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} ntav_io_data; + + +static size_t ntav_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ntav_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + data->skip_size = 0; + data->read_count = 0; + data->skip_count = data->interleave_count * data->track_number; + //VGM_LOG("0 o=%lx, sc=%i\n", data->physical_offset, data->skip_count); + } + + /* read blocks */ + while (length > 0) { + //VGM_LOG("1 of=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset); + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + //VGM_LOG("9 o=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset); + //VGM_LOG("eof\n"); + break; + } + + + /* process new block */ + if (data->data_size == 0) { + /* not very exact compared to real blocks but ok enough */ + if (read_32bitLE(data->physical_offset, streamfile) == 0x00) { + data->block_size = 0x10; + //VGM_LOG("1 o=%lx, lo=%lx skip\n", data->physical_offset, data->logical_offset); + } + else { + data->block_size = data->frame_size; + + //VGM_LOG("2 o=%lx, lo=%lx, skip=%i, read=%i\n", data->physical_offset, data->logical_offset, data->skip_count, data->read_count); + + /* each track interleaves NTAV_INTERLEAVE frames, but can contain padding in between, + * so must read one by one up to max */ + + if (data->skip_count == 0 && data->read_count == 0) { + data->read_count = data->interleave_count; + } + + if (data->skip_count) { + data->skip_count--; + } + + if (data->read_count) { + data->data_size = data->block_size; + data->read_count--; + + if (data->read_count == 0) { + if (data->logical_offset + data->interleave_count * data->frame_size > data->track_size) + data->skip_count = data->interleave_last_count * (data->track_count - 1); + else + data->skip_count = data->interleave_count * (data->track_count - 1); + } + } + + } + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t ntav_io_size(STREAMFILE *streamfile, ntav_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + ntav_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of 9TAV blocked streams. Unlike other games using .sdt, + * KCEJ blocks have a data_size field and rest is padding. Even after that all blocks start + * with 0 (skipped) and there are padding blocks that start with LE 0xDEADBEEF. + * This streamfile handles 9tav extracted like regular sdt and remove padding manually. */ +static STREAMFILE* setup_9tav_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t track_size, int track_number, int track_count) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + ntav_io_data io_data = {0}; + size_t io_data_size = sizeof(ntav_io_data); + size_t last_size; + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.track_size = track_size; + io_data.track_number = track_number; + io_data.track_count = track_count; + io_data.frame_size = 0x40; + io_data.interleave_count = 256; + last_size = track_size % (io_data.interleave_count * io_data.frame_size); + if (last_size) + io_data.interleave_last_count = last_size / io_data.frame_size; + io_data.logical_offset = -1; /* force state reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ntav_io_read,ntav_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _9TAV_STREAMFILE_H_ */ diff --git a/src/meta/meta.h b/src/meta/meta.h index 66b4b4a2..87cce325 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -845,4 +845,6 @@ VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index 45363a11..46091aaa 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -476,6 +476,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ea_schl_video, init_vgmstream_msf_konami, init_vgmstream_xwma_konami, + init_vgmstream_9tav, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ @@ -2343,20 +2344,20 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate; time_mm = (int)(seconds / 60.0); time_ss = seconds - time_mm * 60.0f; - snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%2.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss); + snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss); concatn(length,desc,temp); seconds = (double)vgmstream->loop_end_sample / vgmstream->sample_rate; time_mm = (int)(seconds / 60.0); time_ss = seconds - time_mm * 60.0f; - snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%2.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss); + snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss); concatn(length,desc,temp); } seconds = (double)vgmstream->num_samples / vgmstream->sample_rate; time_mm = (int)(seconds / 60.0); time_ss = seconds - time_mm * 60.0; - snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%2.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss); + snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss); concatn(length,desc,temp); snprintf(temp,TEMPSIZE, "encoding: "); diff --git a/src/vgmstream.h b/src/vgmstream.h index 395c9ba5..59a993c4 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -729,6 +729,7 @@ typedef enum { meta_STRM_ABYLIGHT, meta_MSF_KONAMI, meta_XWMA_KONAMI, + meta_9TAV, } meta_t;