mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-15 02:57:38 +01:00
commit
cce259d4e0
25
cli/vrts.bat
25
cli/vrts.bat
@ -28,8 +28,10 @@ REM # -nd: don't delete compared files
|
||||
set OP_NODELETE=
|
||||
REM # -nc: don't report correct files
|
||||
set OP_NOCORRECT=
|
||||
REM # -p: performance test (decode with new exe and no comparison done)
|
||||
REM # -P: performance test (same but also don't write file)
|
||||
REM # -p: performance test new (decode with new exe and no comparison done)
|
||||
REM # -P: performance test new (same but also don't write file)
|
||||
REM # -po: performance test old (decode with new old and no comparison done)
|
||||
REM # -Po: performance test old (same but also don't write file)
|
||||
set OP_PERFORMANCE=
|
||||
REM # -fc <exe>: file comparer (Windows's FC is slow)
|
||||
set OP_CMD_FC=fc /a /b
|
||||
@ -46,6 +48,8 @@ if "%~1"=="-nd" set OP_NODELETE=true
|
||||
if "%~1"=="-nc" set OP_NOCORRECT=true
|
||||
if "%~1"=="-p" set OP_PERFORMANCE=1
|
||||
if "%~1"=="-P" set OP_PERFORMANCE=2
|
||||
if "%~1"=="-po" set OP_PERFORMANCE=3
|
||||
if "%~1"=="-Po" set OP_PERFORMANCE=4
|
||||
if "%~1"=="-fc" set OP_CMD_FC=%2
|
||||
shift
|
||||
goto set_options
|
||||
@ -201,15 +205,20 @@ REM # ########################################################################
|
||||
REM echo VTRS: file %CMD_FILE%
|
||||
|
||||
set WAV_NEW=%CMD_FILE%.test.wav
|
||||
if "%OP_PERFORMANCE%" == "1" (
|
||||
set CMD_VGM="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%"
|
||||
)
|
||||
if "%OP_PERFORMANCE%" == "2" (
|
||||
REM # don't actually write file
|
||||
set CMD_VGM_NEW="%OP_CMD_NEW%" -O "%CMD_FILE%"
|
||||
) else (
|
||||
REM # new temp output
|
||||
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%"
|
||||
set CMD_VGM="%OP_CMD_NEW%" -O "%CMD_FILE%"
|
||||
)
|
||||
if "%OP_PERFORMANCE%" == "3" (
|
||||
set CMD_VGM="%OP_CMD_OLD%" -o "%WAV_OLD%" "%CMD_FILE%"
|
||||
)
|
||||
if "%OP_PERFORMANCE%" == "4" (
|
||||
set CMD_VGM="%OP_CMD_OLD%" -O "%CMD_FILE%"
|
||||
)
|
||||
|
||||
%CMD_VGM_NEW% 1> nul 2>&1 & REM || goto error
|
||||
%CMD_VGM% 1> nul 2>&1 & REM || goto error
|
||||
|
||||
call :echo_color %C_O% "%CMD_FILE%" "done"
|
||||
|
||||
|
@ -93,7 +93,7 @@ void decode_hevag(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspaci
|
||||
|
||||
/* xa_decoder */
|
||||
void decode_xa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked);
|
||||
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2);
|
||||
|
||||
/* ea_xa_decoder */
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
@ -403,7 +403,6 @@ typedef struct {
|
||||
uint8_t * buf; /* buffer to read/write*/
|
||||
size_t bufsize; /* max size of the buffer */
|
||||
off_t b_off; /* current offset in bits inside the buffer */
|
||||
off_t info_offset; /* for logging */
|
||||
vgm_bitstream_t mode; /* read/write modes */
|
||||
} vgm_bitstream;
|
||||
|
||||
|
@ -132,7 +132,7 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
|
||||
if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info))
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
|
||||
|
@ -7,42 +7,45 @@ static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config);
|
||||
|
||||
/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
|
||||
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
/* 0xFFF5E0C0 header: frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
size_t current_data_size = 0;
|
||||
size_t file_size = get_streamfile_size(stream->streamfile);
|
||||
|
||||
/* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
|
||||
|
||||
/* read supposed frame size first (to minimize reads) */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile);
|
||||
|
||||
/* find actual frame size by looking for the next frame header */
|
||||
{
|
||||
uint32_t current_header = (uint32_t)read_32bitBE(stream->offset, stream->streamfile);
|
||||
off_t next_offset = 0x04;
|
||||
uint32_t current_header = get_u32be(ms->buffer);
|
||||
int next_pos = 0x04;
|
||||
|
||||
while (next_offset <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
uint32_t next_header = (uint32_t)read_32bitBE(stream->offset + next_offset, stream->streamfile);
|
||||
while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
uint32_t next_header = get_u32be(ms->buffer + next_pos);
|
||||
|
||||
if (current_header == next_header) {
|
||||
current_data_size = next_offset;
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
/* AHXs end in a 0x0c footer (0x41485845 0x28632943 0x52490000 / "AHXE" "(c)C" "RI\0\0") */
|
||||
if (stream->offset + next_offset + 0x0c >= file_size) {
|
||||
current_data_size = next_offset;
|
||||
/* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */
|
||||
if (stream->offset + next_pos + 0x0c >= file_size) {
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
next_offset++;
|
||||
next_pos++;
|
||||
}
|
||||
}
|
||||
if (!current_data_size || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
|
||||
if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* 0-fill up to expected size to keep mpg123 happy */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset,current_data_size,stream->streamfile);
|
||||
memset(ms->buffer + ms->bytes_in_buffer,0, MPEG_AHX_EXPECTED_FRAME_SIZE - ms->bytes_in_buffer);
|
||||
memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size);
|
||||
ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
|
||||
|
||||
|
||||
@ -60,7 +63,6 @@ int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data,
|
||||
if (stream->offset + 0x0c >= file_size)
|
||||
stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
@ -79,7 +81,7 @@ static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) {
|
||||
|
||||
/* read 2b from a bitstream offset to decrypt, and use it as an index to get the key.
|
||||
* AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */
|
||||
value = (uint32_t)get_32bitBE(buffer + 0x0d);
|
||||
value = get_u32be(buffer + 0x0d);
|
||||
index = (value >> (32-3-2)) & 0x03;
|
||||
switch(index) {
|
||||
case 0: current_key = 0; break;
|
||||
|
@ -21,10 +21,19 @@
|
||||
/* DEFS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
#define EALAYER3_EA_FRAME_BUFFER_SIZE 0x1000*4 /* enough for one EA-frame */
|
||||
#define EALAYER3_MAX_EA_FRAME_SIZE 0x1000*2 /* enough for one EA-frame without PCM block */
|
||||
#define EALAYER3_MAX_GRANULES 2
|
||||
#define EALAYER3_MAX_CHANNELS 2
|
||||
|
||||
/* helper to pass around */
|
||||
typedef struct {
|
||||
STREAMFILE *sf;
|
||||
off_t offset;
|
||||
vgm_bitstream is;
|
||||
uint8_t buf[EALAYER3_MAX_EA_FRAME_SIZE];
|
||||
int leftover_bits;
|
||||
} ealayer3_buffer_t;
|
||||
|
||||
/* parsed info from a single EALayer3 frame */
|
||||
typedef struct {
|
||||
/* EALayer3 v1 header */
|
||||
@ -70,41 +79,40 @@ typedef struct {
|
||||
int channels;
|
||||
int sample_rate;
|
||||
|
||||
} ealayer3_frame_info;
|
||||
} ealayer3_frame_t;
|
||||
|
||||
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, vgm_bitstream *is, ealayer3_frame_info * eaf);
|
||||
static int ealayer3_parse_frame_v1(vgm_bitstream *is, ealayer3_frame_info * eaf, int channels_per_frame, int is_v1b);
|
||||
static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf);
|
||||
static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info * eaf);
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info* eaf_0, vgm_bitstream* is_1, ealayer3_frame_info* eaf_1, vgm_bitstream* os);
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_info * eaf);
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b);
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream *os);
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf);
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||
static int ealayer3_is_empty_frame(vgm_bitstream *is);
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset);
|
||||
|
||||
/* **************************************************************************** */
|
||||
/* EXTERNAL API */
|
||||
/* **************************************************************************** */
|
||||
|
||||
/* init codec from an EALayer3 frame */
|
||||
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 ok;
|
||||
ealayer3_frame_info eaf;
|
||||
vgm_bitstream is = {0};
|
||||
uint8_t ibuf[EALAYER3_EA_FRAME_BUFFER_SIZE];
|
||||
ealayer3_buffer_t ib = {0};
|
||||
ealayer3_frame_t eaf;
|
||||
|
||||
|
||||
//;VGM_LOG("init at %lx\n", start_offset);
|
||||
|
||||
/* get first frame for info */
|
||||
{
|
||||
is.buf = ibuf;
|
||||
is.bufsize = read_streamfile(ibuf,start_offset,EALAYER3_EA_FRAME_BUFFER_SIZE, streamFile); /* reads less at EOF */;
|
||||
is.b_off = 0;
|
||||
ib.sf = streamfile;
|
||||
ib.offset = start_offset;
|
||||
ib.is.buf = ib.buf;
|
||||
|
||||
ok = ealayer3_parse_frame(data, -1, &is, &eaf);
|
||||
ok = ealayer3_parse_frame(data, -1, &ib, &eaf);
|
||||
if (!ok) goto fail;
|
||||
}
|
||||
//;VGM_ASSERT(!eaf.mpeg1, "MPEG EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
|
||||
//;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
|
||||
|
||||
*coding_type = coding_MPEG_ealayer3;
|
||||
data->channels_per_frame = eaf.channels;
|
||||
@ -121,11 +129,8 @@ fail:
|
||||
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
int ok, granule_found;
|
||||
off_t info_offset = stream->offset;
|
||||
|
||||
ealayer3_frame_info eaf_0, eaf_1;
|
||||
vgm_bitstream is_0 = {0}, is_1 = {0}, os = {0};
|
||||
uint8_t ibuf_0[EALAYER3_EA_FRAME_BUFFER_SIZE], ibuf_1[EALAYER3_EA_FRAME_BUFFER_SIZE];
|
||||
ealayer3_buffer_t ib_0 = {0}, ib_1 = {0};
|
||||
ealayer3_frame_t eaf_0, eaf_1;
|
||||
|
||||
|
||||
/* read first frame/granule, or PCM-only frame (found alone at the end of SCHl streams) */
|
||||
@ -134,11 +139,11 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
if (!ealayer3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
|
||||
is_0.buf = ibuf_0;
|
||||
is_0.bufsize = read_streamfile(ibuf_0,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */
|
||||
is_0.b_off = 0;
|
||||
ib_0.sf = stream->streamfile;
|
||||
ib_0.offset = stream->offset;
|
||||
ib_0.is.buf = ib_0.buf;
|
||||
|
||||
ok = ealayer3_parse_frame(data, num_stream, &is_0, &eaf_0);
|
||||
ok = ealayer3_parse_frame(data, num_stream, &ib_0, &eaf_0);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_0);
|
||||
@ -166,22 +171,22 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
if (!ealayer3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
|
||||
is_1.buf = ibuf_1;
|
||||
is_1.bufsize = read_streamfile(ibuf_1,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */
|
||||
is_1.b_off = 0;
|
||||
|
||||
/* detect end granule0 (which may be before stream end in multichannel) and create a usable last granule1 */
|
||||
if (data->type == MPEG_EAL32P && eaf_0.mpeg1 && ealayer3_is_empty_frame(&is_1)) {
|
||||
;VGM_LOG("EAL3: fake granule1 needed\n");
|
||||
if (data->type == MPEG_EAL32P && eaf_0.mpeg1 && ealayer3_is_empty_frame_v2p(stream->streamfile, stream->offset)) {
|
||||
//;VGM_LOG("EAL3: fake granule1 needed\n");
|
||||
/* memcpy/clone for now as I'm not sure now to create a valid empty granule1, but
|
||||
* probably doesn't matter since num_samples should end before its samples */
|
||||
* probably doesn't matter since num_samples should end before reaching granule1 samples (<=576) */
|
||||
eaf_1 = eaf_0;
|
||||
is_1 = is_0;
|
||||
ib_1 = ib_0;
|
||||
eaf_1.granule_index = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
ok = ealayer3_parse_frame(data, num_stream, &is_1, &eaf_1);
|
||||
ib_1.sf = stream->streamfile;
|
||||
ib_1.offset = stream->offset;
|
||||
ib_1.is.buf = ib_1.buf;
|
||||
|
||||
ok = ealayer3_parse_frame(data, num_stream, &ib_1, &eaf_1);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ok = ealayer3_write_pcm_block(stream, data, num_stream, &eaf_1);
|
||||
@ -200,12 +205,12 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
|
||||
|
||||
/* rebuild EALayer3 frame to MPEG frame */
|
||||
{
|
||||
vgm_bitstream os = {0};
|
||||
|
||||
os.buf = ms->buffer;
|
||||
os.bufsize = ms->buffer_size;
|
||||
os.b_off = 0;
|
||||
os.info_offset = info_offset;
|
||||
|
||||
ok = ealayer3_rebuild_mpeg_frame(&is_0, &eaf_0, &is_1, &eaf_1, &os);
|
||||
ok = ealayer3_rebuild_mpeg_frame(&ib_0.is, &eaf_0, &ib_1.is, &eaf_1, &os);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = os.b_off / 8; /* wrote full MPEG frame, hopefully */
|
||||
@ -221,7 +226,40 @@ fail:
|
||||
/* INTERNAL HELPERS */
|
||||
/* **************************************************************************** */
|
||||
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, vgm_bitstream *is, ealayer3_frame_info * eaf) {
|
||||
/* Read at most N bits from streamfile. This makes more smaller reads (not good) but
|
||||
* allows exact frame size reading (good), as reading over a frame then reading back
|
||||
* is expensive in EALayer3 since it uses custom streamfiles. */
|
||||
static void fill_buf(ealayer3_buffer_t *ib, int bits) {
|
||||
size_t read_size, bytes_size;
|
||||
int mod;
|
||||
|
||||
/* count leftover bits since we can only read 8 bits at a time:
|
||||
* - fill 6b: read 1 byte (8b) > leftover 2b
|
||||
* - fill 20b: remove leftover 2b (=fill 18b), read 3 bytes (16+8b) > leftover 6b
|
||||
* - fill 4b: remove leftover 6b > no need to read > lefover 2b
|
||||
* - etc
|
||||
*/
|
||||
|
||||
//;VGM_LOG("fill: %i + (l=%i)\n", bits, bits);
|
||||
|
||||
if (ib->leftover_bits >= bits) {
|
||||
ib->leftover_bits -= bits;
|
||||
return;
|
||||
}
|
||||
|
||||
bits -= ib->leftover_bits;
|
||||
mod = (bits % 8);
|
||||
bytes_size = (bits / 8) + (mod > 0 ? 0x01 : 0);
|
||||
|
||||
//;VGM_LOG("filled: %lx + %x (b=%i, m=%i)\n", ib->offset, bytes_size, bits, (mod > 0 ? 8 - mod : 0));
|
||||
|
||||
read_size = read_streamfile(ib->buf + ib->is.bufsize, ib->offset, bytes_size, ib->sf);
|
||||
ib->is.bufsize += read_size;
|
||||
ib->offset += read_size;
|
||||
ib->leftover_bits = (mod > 0 ? 8 - mod : 0);
|
||||
}
|
||||
|
||||
static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
int ok;
|
||||
|
||||
/* We must pass this from state, as not all EA-frames have channel info.
|
||||
@ -232,13 +270,13 @@ static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, vgm_bitst
|
||||
}
|
||||
|
||||
/* make sure as there is re-parsing in loops */
|
||||
memset(eaf, 0, sizeof(ealayer3_frame_info));
|
||||
memset(eaf, 0, sizeof(ealayer3_frame_t));
|
||||
|
||||
switch(data->type) {
|
||||
case MPEG_EAL31: ok = ealayer3_parse_frame_v1(is, eaf, channels_per_frame, 0); break;
|
||||
case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(is, eaf, channels_per_frame, 1); break;
|
||||
case MPEG_EAL31: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 0); break;
|
||||
case MPEG_EAL31b: ok = ealayer3_parse_frame_v1(ib, eaf, channels_per_frame, 1); break;
|
||||
case MPEG_EAL32P:
|
||||
case MPEG_EAL32S: ok = ealayer3_parse_frame_v2(is, eaf); break;
|
||||
case MPEG_EAL32S: ok = ealayer3_parse_frame_v2(ib, eaf); break;
|
||||
default: goto fail;
|
||||
}
|
||||
if (!ok) goto fail;
|
||||
@ -250,31 +288,35 @@ fail:
|
||||
|
||||
|
||||
/* read V1"a" (in SCHl) and V1"b" (in SNS) EALayer3 frame */
|
||||
static int ealayer3_parse_frame_v1(vgm_bitstream *is, ealayer3_frame_info * eaf, int channels_per_frame, int is_v1b) {
|
||||
static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b) {
|
||||
int ok;
|
||||
vgm_bitstream *is = &ib->is;
|
||||
|
||||
|
||||
/* read EA-frame V1 header */
|
||||
fill_buf(ib, 8);
|
||||
r_bits(is, 8,&eaf->v1_pcm_flag);
|
||||
|
||||
eaf->pre_size = 1; /* 8b */
|
||||
|
||||
if (eaf->v1_pcm_flag != 0x00 && eaf->v1_pcm_flag != 0xEE) {
|
||||
VGM_LOG("MPEG EAL3 v1: header not 0x00 or 0xEE\n");
|
||||
VGM_LOG("EAL3 v1: header not 0x00 or 0xEE\n");
|
||||
goto fail; /* wrong offset? */
|
||||
}
|
||||
if (eaf->v1_pcm_flag == 0xEE && !channels_per_frame) {
|
||||
VGM_LOG("MPEG EAL3 v1: PCM block in first frame\n");
|
||||
VGM_LOG("EAL3 v1: PCM block in first frame\n");
|
||||
goto fail; /* must know from a prev frame (can't use eaf->channels for V1a) */
|
||||
}
|
||||
|
||||
/* read EA-frame common header (V1a PCM blocks don't have EA-frames, while V1b do) */
|
||||
if (is_v1b || eaf->v1_pcm_flag == 0x00) {
|
||||
ok = ealayer3_parse_frame_common(is, eaf);
|
||||
ok = ealayer3_parse_frame_common(ib, eaf);
|
||||
if (!ok) goto fail;
|
||||
}
|
||||
|
||||
/* check PCM block */
|
||||
if (eaf->v1_pcm_flag == 0xEE) {
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 16,&eaf->v1_offset_samples); /* samples to discard of the next decoded (not PCM block) samples */
|
||||
r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */
|
||||
|
||||
@ -282,8 +324,11 @@ static int ealayer3_parse_frame_v1(vgm_bitstream *is, ealayer3_frame_info * eaf,
|
||||
eaf->pcm_size = (2*eaf->v1_pcm_samples * channels_per_frame);
|
||||
|
||||
if (is_v1b) { /* extra 32b in v1b */
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 32,&eaf->v1_pcm_unknown);
|
||||
|
||||
eaf->pre_size += 4; /* 32b */
|
||||
|
||||
VGM_ASSERT(eaf->v1_pcm_unknown != 0, "EA EAL3 v1: v1_pcm_unknown not 0\n");
|
||||
}
|
||||
}
|
||||
@ -297,10 +342,13 @@ fail:
|
||||
|
||||
/* read V2"PCM" and V2"Spike" EALayer3 frame (exactly the same but V2P seems to have bigger
|
||||
* PCM blocks and maybe smaller frames) */
|
||||
static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf) {
|
||||
static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
int ok;
|
||||
vgm_bitstream *is = &ib->is;
|
||||
|
||||
|
||||
/* read EA-frame V2 header */
|
||||
fill_buf(ib, 16);
|
||||
r_bits(is, 1,&eaf->v2_extended_flag);
|
||||
r_bits(is, 1,&eaf->v2_stereo_flag);
|
||||
r_bits(is, 2,&eaf->v2_reserved);
|
||||
@ -309,6 +357,7 @@ static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf)
|
||||
eaf->pre_size = 2; /* 16b */
|
||||
|
||||
if (eaf->v2_extended_flag) {
|
||||
fill_buf(ib, 32);
|
||||
r_bits(is, 2,&eaf->v2_offset_mode);
|
||||
r_bits(is, 10,&eaf->v2_offset_samples);
|
||||
r_bits(is, 10,&eaf->v2_pcm_samples);
|
||||
@ -319,7 +368,7 @@ static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf)
|
||||
|
||||
/* read EA-frame common header */
|
||||
if (!eaf->v2_extended_flag || (eaf->v2_extended_flag && eaf->v2_common_size)) {
|
||||
ok = ealayer3_parse_frame_common(is, eaf);
|
||||
ok = ealayer3_parse_frame_common(ib, eaf);
|
||||
if (!ok) goto fail;
|
||||
}
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
|
||||
@ -331,7 +380,7 @@ static int ealayer3_parse_frame_v2(vgm_bitstream *is, ealayer3_frame_info * eaf)
|
||||
eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size;
|
||||
|
||||
if (eaf->v2_frame_size != eaf->eaframe_size) {
|
||||
VGM_LOG("MPEG EAL3: different v2 frame size vs calculated (0x%x vs 0x%x)\n", eaf->v2_frame_size, eaf->eaframe_size);
|
||||
VGM_LOG("EAL3: different v2 frame size vs calculated (0x%x vs 0x%x)\n", eaf->v2_frame_size, eaf->eaframe_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -343,7 +392,7 @@ fail:
|
||||
|
||||
|
||||
/* parses an EALayer3 frame (common part) */
|
||||
static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info * eaf) {
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) {
|
||||
/* index tables */
|
||||
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int sample_rates[4][4] = { /* [version_index][sample rate index] */
|
||||
@ -354,24 +403,29 @@ static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info *
|
||||
};
|
||||
static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */
|
||||
|
||||
vgm_bitstream *is = &ib->is;
|
||||
off_t start_b_off = is->b_off;
|
||||
int i;
|
||||
int i, fill_bits, others_2_bits;
|
||||
|
||||
|
||||
/* read main header */
|
||||
fill_buf(ib, 8);
|
||||
r_bits(is, 2,&eaf->version_index);
|
||||
r_bits(is, 2,&eaf->sample_rate_index);
|
||||
r_bits(is, 2,&eaf->channel_mode);
|
||||
r_bits(is, 2,&eaf->mode_extension);
|
||||
|
||||
|
||||
/* check empty frame */
|
||||
if (eaf->version_index == 0 &&
|
||||
eaf->sample_rate_index == 0 &&
|
||||
eaf->channel_mode == 0 &&
|
||||
eaf->mode_extension == 0) {
|
||||
VGM_LOG("MPEG EAL3: empty frame\n");
|
||||
VGM_LOG("EAL3: empty frame\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* derived */
|
||||
eaf->version = versions[eaf->version_index];
|
||||
eaf->channels = channels[eaf->channel_mode];
|
||||
@ -379,14 +433,20 @@ static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info *
|
||||
eaf->mpeg1 = (eaf->version == 1);
|
||||
|
||||
if (eaf->version == -1 || eaf->sample_rate == -1) {
|
||||
VGM_LOG("MPEG EAL3: illegal header values\n");
|
||||
VGM_LOG("EAL3: illegal header values\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
others_2_bits = eaf->mpeg1 ? 47-32 : 51-32;
|
||||
|
||||
/* read side info */
|
||||
fill_buf(ib, 1);
|
||||
r_bits(is, 1,&eaf->granule_index);
|
||||
|
||||
fill_bits = (eaf->mpeg1 && eaf->granule_index == 1) ? 4 * eaf->channels : 0;
|
||||
fill_bits = fill_bits + (12 + 32 + others_2_bits) * eaf->channels;
|
||||
fill_buf(ib, fill_bits);
|
||||
|
||||
if (eaf->mpeg1 && eaf->granule_index == 1) {
|
||||
for (i = 0; i < eaf->channels; i++) {
|
||||
r_bits(is, 4,&eaf->scfsi[i]);
|
||||
@ -394,8 +454,6 @@ static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info *
|
||||
}
|
||||
|
||||
for (i = 0; i < eaf->channels; i++) {
|
||||
int others_2_bits = eaf->mpeg1 ? 47-32 : 51-32;
|
||||
|
||||
r_bits(is, 12,&eaf->main_data_size[i]);
|
||||
/* divided in 47b=32+15 (MPEG1) or 51b=32+19 (MPEG2), arbitrarily */
|
||||
r_bits(is, 32,&eaf->others_1[i]);
|
||||
@ -404,22 +462,19 @@ static int ealayer3_parse_frame_common(vgm_bitstream *is, ealayer3_frame_info *
|
||||
|
||||
|
||||
/* derived */
|
||||
eaf->data_offset_b = is->b_off;
|
||||
|
||||
eaf->base_size_b = (is->b_off - start_b_off); /* header + size info size */
|
||||
|
||||
for (i = 0; i < eaf->channels; i++) { /* data size (can be 0, meaning a micro EA-frame) */
|
||||
eaf->data_size_b += eaf->main_data_size[i];
|
||||
eaf->data_offset_b = is->b_off; /* header size + above size */
|
||||
eaf->base_size_b = (is->b_off - start_b_off); /* above size without header */
|
||||
for (i = 0; i < eaf->channels; i++) {
|
||||
eaf->data_size_b += eaf->main_data_size[i]; /* can be 0, meaning a micro EA-frame */
|
||||
}
|
||||
is->b_off += eaf->data_size_b;
|
||||
|
||||
if ((eaf->base_size_b+eaf->data_size_b) % 8) /* aligned to closest 8b */
|
||||
if ((eaf->base_size_b + eaf->data_size_b) % 8) /* aligned to closest 8b */
|
||||
eaf->padding_size_b = 8 - ((eaf->base_size_b+eaf->data_size_b) % 8);
|
||||
is->b_off += eaf->padding_size_b;
|
||||
|
||||
fill_buf(ib, eaf->data_size_b + eaf->padding_size_b); /* read MPEG data (not PCM block) */
|
||||
is->b_off += eaf->data_size_b + eaf->padding_size_b;
|
||||
|
||||
eaf->common_size = (eaf->base_size_b + eaf->data_size_b + eaf->padding_size_b)/8;
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
@ -427,11 +482,12 @@ fail:
|
||||
|
||||
|
||||
/* converts an EALAYER3 frame to a standard MPEG frame from pre-parsed info */
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info* eaf_0, vgm_bitstream* is_1, ealayer3_frame_info* eaf_1, vgm_bitstream* os) {
|
||||
static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream* os) {
|
||||
uint32_t c = 0;
|
||||
int i,j;
|
||||
int i, j;
|
||||
int expected_bitrate_index, expected_frame_size;
|
||||
|
||||
|
||||
/* ignore PCM-only frames */
|
||||
if (!eaf_0->common_size)
|
||||
return 1;
|
||||
@ -442,7 +498,7 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info*
|
||||
|| eaf_0->version != eaf_1->version
|
||||
|| eaf_0->granule_index == eaf_1->granule_index
|
||||
|| !eaf_0->common_size || !eaf_1->common_size)) {
|
||||
VGM_LOG("MPEG EAL3: EA-frames for MPEG1 don't match\n");
|
||||
VGM_LOG("EAL3: EA-frames for MPEG1 don't match\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -551,7 +607,7 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info*
|
||||
|
||||
if (os->b_off/8 > expected_frame_size) {
|
||||
/* bit reservoir! shouldn't happen with free bitrate, otherwise it's hard to fix as needs complex buffering/calcs */
|
||||
VGM_LOG("MPEG EAL3: written 0x%x but expected less than 0x%x at 0x%x\n", (uint32_t)(os->b_off/8), expected_frame_size, (uint32_t)os->info_offset);
|
||||
VGM_LOG("EAL3: written 0x%x but expected less than 0x%x\n", (uint32_t)(os->b_off/8), expected_frame_size);
|
||||
}
|
||||
else {
|
||||
/* fill ancillary data (should be ignored, but 0x00 seems to improve mpg123's free bitrate detection) */
|
||||
@ -566,34 +622,50 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE * streamfile) {
|
||||
static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE *sf) {
|
||||
int i, ch;
|
||||
uint8_t pcm_block[1152 * 2 * 2]; /* assumed max: 1 MPEG frame samples * 16b * max channels */
|
||||
size_t pcm_size = pcm_number * 2 * channels_per_frame;
|
||||
size_t bytes;
|
||||
|
||||
if (pcm_number == 0)
|
||||
return;
|
||||
|
||||
if (pcm_number > 1152) {
|
||||
VGM_LOG("EAL3: big PCM block of %i samples\n", pcm_number);
|
||||
return;
|
||||
}
|
||||
|
||||
bytes = read_streamfile(pcm_block, pcm_offset, pcm_size, sf);
|
||||
if (bytes != pcm_size) {
|
||||
VGM_LOG("EAL3: incorrect pcm_number %i at %lx\n", pcm_number, pcm_offset);
|
||||
return;
|
||||
}
|
||||
|
||||
//;VGM_LOG("copy PCM at %lx + %i\n", pcm_offset, pcm_number);
|
||||
|
||||
/* read + write PCM block samples (always BE) */
|
||||
if (is_packed) {
|
||||
/* ch0+ch1 packed together */
|
||||
off_t put_offset = 0;
|
||||
int pos = 0;
|
||||
for (i = 0; i < pcm_number * channels_per_frame; i++) {
|
||||
int16_t pcm_sample = read_16bitBE(pcm_offset,streamfile);
|
||||
put_16bitLE(outbuf + put_offset, pcm_sample);
|
||||
int16_t pcm_sample = get_s16be(pcm_block + pos);
|
||||
put_16bitLE(outbuf + pos, pcm_sample);
|
||||
|
||||
pcm_offset += sizeof(sample);
|
||||
put_offset += sizeof(sample);
|
||||
pos += sizeof(sample);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* all of ch0 first, then all of ch1 (EAL3 v1b only) */
|
||||
int get_pos = 0;
|
||||
for (ch = 0; ch < channels_per_frame; ch++) {
|
||||
off_t put_offset = sizeof(sample)*ch;
|
||||
int put_pos = sizeof(sample) * ch;
|
||||
for (i = 0; i < pcm_number; i++) {
|
||||
int16_t pcm_sample = read_16bitBE(pcm_offset,streamfile);
|
||||
put_16bitLE(outbuf + put_offset, pcm_sample);
|
||||
int16_t pcm_sample = get_s16be(pcm_block + get_pos);
|
||||
put_16bitLE(outbuf + put_pos, pcm_sample);
|
||||
|
||||
pcm_offset += sizeof(sample);
|
||||
put_offset += sizeof(sample)*channels_per_frame;
|
||||
get_pos += sizeof(sample);
|
||||
put_pos += sizeof(sample) * channels_per_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -602,7 +674,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
|
||||
/* write PCM block directly to sample buffer and setup decode discard (EALayer3 seems to use this as a prefetch of sorts).
|
||||
* Meant to be written inmediatedly, as those PCM are parts that can be found after 1 decoded frame.
|
||||
* (ex. EA-frame_gr0, PCM-frame_0, EA-frame_gr1, PCM-frame_1 actually writes PCM-frame_0+1, decode of EA-frame_gr0+1 + discard part */
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_info * eaf) {
|
||||
static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
int channels_per_frame = ms->channels_per_frame;
|
||||
size_t bytes_filled;
|
||||
@ -610,12 +682,12 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
|
||||
|
||||
bytes_filled = sizeof(sample) * ms->samples_filled * channels_per_frame;
|
||||
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||
VGM_LOG("MPEG EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
VGM_LOG("EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (eaf->v1_pcm_samples && !eaf->pcm_size) {
|
||||
VGM_LOG("MPEG EAL3: pcm_size without pcm_samples\n");
|
||||
VGM_LOG("EAL3: pcm_size without pcm_samples\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -623,15 +695,15 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
|
||||
uint8_t* outbuf = ms->output_buffer + bytes_filled;
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size;
|
||||
|
||||
VGM_ASSERT(eaf->v1_offset_samples > 576, "MPEG EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "MPEG EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */
|
||||
|
||||
//;VGM_LOG("EA EAL3 v1: off=%lx, discard=%x, pcm=%i, pcm_o=%lx\n",
|
||||
// stream->offset, eaf->v1_offset_samples, eaf->v1_pcm_samples, pcm_offset);
|
||||
|
||||
/* V1 usually discards + copies samples at the same time
|
||||
* V1b PCM block is interleaved/'planar' format (ex. NFS:U PS3) */
|
||||
* V1b PCM block is in 'planar' format (ex. NFS:U PS3) */
|
||||
ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v1_pcm_samples, channels_per_frame, (data->type == MPEG_EAL31), stream->streamfile);
|
||||
ms->samples_filled += eaf->v1_pcm_samples;
|
||||
|
||||
@ -733,9 +805,8 @@ fail:
|
||||
*/
|
||||
static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||
int ok, i;
|
||||
ealayer3_frame_info eaf;
|
||||
vgm_bitstream is = {0};
|
||||
uint8_t ibuf[EALAYER3_EA_FRAME_BUFFER_SIZE];
|
||||
ealayer3_buffer_t ib = {0};
|
||||
ealayer3_frame_t eaf;
|
||||
int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
|
||||
|
||||
/* v1 does multichannel with set offsets */
|
||||
@ -743,11 +814,11 @@ static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, i
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < skips; i++) {
|
||||
is.buf = ibuf;
|
||||
is.bufsize = read_streamfile(ibuf,stream->offset,EALAYER3_EA_FRAME_BUFFER_SIZE, stream->streamfile); /* reads less at EOF */
|
||||
is.b_off = 0;
|
||||
ib.sf = stream->streamfile;
|
||||
ib.offset = stream->offset;
|
||||
ib.is.buf = ib.buf;
|
||||
|
||||
ok = ealayer3_parse_frame(data, num_stream, &is, &eaf);
|
||||
ok = ealayer3_parse_frame(data, num_stream, &ib, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
stream->offset += eaf.eaframe_size;
|
||||
@ -760,15 +831,10 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ealayer3_is_empty_frame(vgm_bitstream *is) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < is->bufsize; i++) {
|
||||
if (is->buf[i] != 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset) {
|
||||
/* V2P frame header should contain a valid frame size (lower 12b) */
|
||||
uint16_t v2_header = read_u16be(offset, sf);
|
||||
return (v2_header % 0xFFF) == 0 || v2_header == 0xFFFF;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -71,10 +71,11 @@ int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *dat
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||
|
||||
ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||
stream->offset += eaf.frame_size;
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 0))
|
||||
|
@ -152,9 +152,9 @@ void decode_xa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing,
|
||||
stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
||||
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked) {
|
||||
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_form2) {
|
||||
if (is_blocked) {
|
||||
return (bytes / 0x930) * (28*8/ channels) * 18;
|
||||
return (bytes / 0x930) * (28*8/ channels) * (is_form2 ? 18 : 16);
|
||||
}
|
||||
else {
|
||||
return (bytes / 0x80) * (28*8 / channels);
|
||||
|
@ -335,7 +335,7 @@ static const char* extension_list[] = {
|
||||
"oto", //txth/reserved [Vampire Savior (SAT)]
|
||||
"ovb",
|
||||
|
||||
"p04", //txth/reserved [Psychic Force 2012 (DC)]
|
||||
"p04", //txth/reserved [Psychic Force 2012 (DC), Skies of Arcadia (DC)]
|
||||
"p16", //txth/reserved [Astal (SAT)]
|
||||
"p1d", //txth/reserved [Farming Simulator 18 (3DS)]
|
||||
"p2a", //txth/reserved [Thunderhawk Operation Phoenix (PS2)]
|
||||
|
@ -7,7 +7,8 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
int i, is_audio;
|
||||
size_t block_samples;
|
||||
uint8_t xa_target, xa_submode, config_target;
|
||||
uint16_t xa_config, target_config;
|
||||
uint8_t xa_submode;
|
||||
|
||||
|
||||
/* XA mode2/form2 sector, size 0x930
|
||||
@ -21,37 +22,42 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
* 0x930: end
|
||||
* Sectors with no data may exist near other with data
|
||||
*/
|
||||
xa_target = (uint8_t)read_16bitBE(block_offset + 0x10, streamFile);
|
||||
config_target = vgmstream->codec_config;
|
||||
xa_config = read_u16be(block_offset + 0x10, streamFile);
|
||||
target_config = vgmstream->codec_config;
|
||||
|
||||
/* Sector subheader's file+channel markers are used to interleave streams (music/sfx/voices)
|
||||
* by reading one target file+channel while ignoring the rest. This is needed to adjust
|
||||
* CD drive spinning <> decoding speed (data is read faster otherwise, so can't have 2
|
||||
* sectors of the same channel), Normally N channels = N streams (usually 8/16/32 depending
|
||||
* on sample rate/stereo), though channels can be empty or contain video (like 7 or 15 video
|
||||
* sectors + 1 audio frame). Normally interleaved channels use with the same file ID, but some
|
||||
* games change ID too. Extractors deinterleave and split .xa using file + channel + EOF flags. */
|
||||
* sectors of the same channel).
|
||||
*
|
||||
* Normally N channels = N streams (usually 8/16/32 depending on sample rate/stereo or even higher),
|
||||
* though channels can be empty or contain video (like 7 or 15 video sectors + 1 audio frame).
|
||||
* Usually nterleaved channels use with the same file ID, but some games change ID too. Channels
|
||||
* don't always follow an order (so 00,01,02,08,06,00 is possible though uncommon, ex. Spyro 2)
|
||||
*
|
||||
* Extractors deinterleave and split .xa using file + channel + EOF flags.
|
||||
* 'Channel' here doesn't mean "audio channel", just a fancy name for substreams (mono or stereo).
|
||||
* Files can go up to 255, normally file 0=sequential, 1+=interleaved */
|
||||
|
||||
|
||||
/* submode flag bits (typical audio value = 0x64 01100100)
|
||||
* - 7 (0x80 10000000): end of file (usually at data end, not per subheader's file)
|
||||
* - 6 (0x40 01000000): real time mode
|
||||
* - 7 (0x80 10000000): end of file (usually at last sector of a channel or at data end)
|
||||
* - 6 (0x40 01000000): real time sector (special control flag)
|
||||
* - 5 (0x20 00100000): sector form (0=form1, 1=form2)
|
||||
* - 4 (0x10 00010000): trigger (for application)
|
||||
* - 4 (0x10 00010000): trigger (generates interrupt for the application)
|
||||
* - 3 (0x08 00001000): data sector
|
||||
* - 2 (0x04 00000100): audio sector
|
||||
* - 1 (0x02 00000010): video sector
|
||||
* - 0 (0x01 00000001): end of audio
|
||||
* - 0 (0x01 00000001): end of audio (optional for non-real time XAs)
|
||||
* Empty sectors with no flags may exist interleaved with other with audio/data.
|
||||
*/
|
||||
xa_submode = (uint8_t)read_8bit(block_offset + 0x12,streamFile);
|
||||
xa_submode = read_u8(block_offset + 0x12,streamFile);
|
||||
|
||||
/* audio sector must set/not set certain flags, as per spec */
|
||||
/* audio sector must set/not set certain flags, as per spec (in theory form2 only too) */
|
||||
is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
|
||||
|
||||
if (xa_target != config_target) {
|
||||
//;VGM_LOG("XA block: ignored block at %x\n", (uint32_t)block_offset);
|
||||
if (xa_config != target_config) {
|
||||
block_samples = 0; /* not a target sector */
|
||||
}
|
||||
else if (is_audio) {
|
||||
@ -65,8 +71,6 @@ void block_update_xa(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
;VGM_ASSERT_ONCE(block_offset < get_streamfile_size(streamFile),
|
||||
"XA block: non audio block found at %x\n", (uint32_t)block_offset);
|
||||
block_samples = 0; /* not an audio sector */
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset,
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
//;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
@ -243,10 +244,9 @@ static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) {
|
||||
* - EATrax: ATRAC9 frames can be split between blooks
|
||||
* - EAOpus: multiple Opus packets of frame size + Opus data per block
|
||||
*/
|
||||
static STREAMFILE* setup_eaac_streamfile(STREAMFILE *streamFile, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
static STREAMFILE* setup_eaac_streamfile(STREAMFILE *sf, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
eaac_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(eaac_io_data);
|
||||
|
||||
io_data.version = version;
|
||||
io_data.codec = codec;
|
||||
@ -255,26 +255,14 @@ static STREAMFILE* setup_eaac_streamfile(STREAMFILE *streamFile, int version, in
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = eaac_io_size(streamFile, &io_data); /* force init */
|
||||
io_data.logical_size = eaac_io_size(sf, &io_data); /* force init */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, eaac_io_read,eaac_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;
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size);
|
||||
if (codec == 0x03) /* EA-XMA only since logical data is bigger */
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _EA_EAAC_STREAMFILE_H_ */
|
||||
|
387
src/meta/xa.c
387
src/meta/xa.c
@ -2,164 +2,72 @@
|
||||
#include "../layout/layout.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* CD-XA - from Sony PS1 and Philips CD-i CD audio, also Saturn streams */
|
||||
|
||||
static int xa_read_subsongs(STREAMFILE *sf, int target_subsong, off_t start, uint16_t *p_stream_config, off_t *p_stream_offset, size_t *p_stream_size, int *p_form2);
|
||||
static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked);
|
||||
|
||||
/* XA - from Sony PS1 and Philips CD-i CD audio, also Saturn streams */
|
||||
VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate;
|
||||
int is_riff = 0, is_blocked = 0;
|
||||
size_t file_size, stream_size;
|
||||
int total_subsongs, /*target_subsong = streamFile->stream_index,*/ target_config;
|
||||
int is_riff = 0, is_blocked = 0, is_form2 = 0;
|
||||
size_t stream_size = 0;
|
||||
int total_subsongs = 0, target_subsong = streamFile->stream_index;
|
||||
uint16_t target_config = 0;
|
||||
|
||||
|
||||
/* checks
|
||||
* .xa: common
|
||||
* .str: often (but not always) videos
|
||||
* .adp: Phantasy Star Collection (SAT) raw XA */
|
||||
if ( !check_extensions(streamFile,"xa,str,adp") )
|
||||
|
||||
/* checks */
|
||||
/* .xa: common
|
||||
* .str: often videos and sometimes speech/music
|
||||
* .adp: Phantasy Star Collection (SAT) raw XA
|
||||
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
|
||||
if (!check_extensions(streamFile,"xa,str,adp,"))
|
||||
goto fail;
|
||||
|
||||
|
||||
file_size = get_streamfile_size(streamFile);
|
||||
|
||||
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
|
||||
* This also has minimal support for headerless (ISO 2048 mode1/data) mode. */
|
||||
* Also has minimal support for headerless (ISO 2048 mode1/data) mode. */
|
||||
|
||||
/* check RIFF header = raw (optional, added when ripping and not part of the CD data) */
|
||||
if (read_32bitBE(0x00,streamFile) == 0x52494646 && /* "RIFF" */
|
||||
read_32bitBE(0x08,streamFile) == 0x43445841 && /* "CDXA" */
|
||||
read_32bitBE(0x0C,streamFile) == 0x666D7420) { /* "fmt " */
|
||||
if (read_u32be(0x00,streamFile) == 0x52494646 && /* "RIFF" */
|
||||
read_u32be(0x08,streamFile) == 0x43445841 && /* "CDXA" */
|
||||
read_u32be(0x0C,streamFile) == 0x666D7420) { /* "fmt " */
|
||||
is_blocked = 1;
|
||||
is_riff = 1;
|
||||
start_offset = 0x2c; /* after "data" (chunk size tends to be a bit off) */
|
||||
start_offset = 0x2c; /* after "data", ignore RIFF values as often are wrong */
|
||||
}
|
||||
else {
|
||||
/* sector sync word = raw */
|
||||
if (read_32bitBE(0x00,streamFile) == 0x00FFFFFF &&
|
||||
read_32bitBE(0x04,streamFile) == 0xFFFFFFFF &&
|
||||
read_32bitBE(0x08,streamFile) == 0xFFFFFF00) {
|
||||
if (read_u32be(0x00,streamFile) == 0x00FFFFFF &&
|
||||
read_u32be(0x04,streamFile) == 0xFFFFFFFF &&
|
||||
read_u32be(0x08,streamFile) == 0xFFFFFF00) {
|
||||
is_blocked = 1;
|
||||
start_offset = 0x00;
|
||||
}
|
||||
else { /* headerless and possibly incorrectly ripped */
|
||||
else {
|
||||
/* headerless or possibly incorrectly ripped */
|
||||
start_offset = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
/* test some blocks (except when RIFF) since other .XA/STR may start blank */
|
||||
if (!is_riff) {
|
||||
int i, j, block = 0, miss = 0;
|
||||
off_t test_offset = start_offset;
|
||||
const size_t sector_size = (is_blocked ? 0x900 : 0x800);
|
||||
const size_t block_size = 0x80;
|
||||
const int block_max = 3;
|
||||
const int miss_max = 25;
|
||||
|
||||
while (block < block_max) {
|
||||
uint8_t xa_submode = read_u8(test_offset + 0x12, streamFile);
|
||||
int is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
|
||||
if (is_blocked && !is_audio) {
|
||||
miss++;
|
||||
if (block == 0 && miss > miss_max) /* no a single audio block found */
|
||||
goto fail;
|
||||
test_offset += sector_size + (is_blocked ? 0x18 + 0x18 : 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
test_offset += (is_blocked ? 0x18 : 0x00); /* header */
|
||||
|
||||
for (i = 0; i < (sector_size / block_size); i++) {
|
||||
/* XA headers checks: filter indexes should be 0..3, and shifts 0..C */
|
||||
for (j = 0; j < 16; j++) {
|
||||
uint8_t header = (uint8_t)read_8bit(test_offset + j, streamFile);
|
||||
if (((header >> 4) & 0xF) > 0x03)
|
||||
goto fail;
|
||||
if (((header >> 0) & 0xF) > 0x0c)
|
||||
goto fail;
|
||||
}
|
||||
/* XA headers pairs are repeated */
|
||||
if (read_32bitBE(test_offset+0x00,streamFile) != read_32bitBE(test_offset+0x04,streamFile) ||
|
||||
read_32bitBE(test_offset+0x08,streamFile) != read_32bitBE(test_offset+0x0c,streamFile))
|
||||
goto fail;
|
||||
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
|
||||
if (read_32bitBE(test_offset+0x00,streamFile) == 0 &&
|
||||
read_32bitBE(test_offset+0x04,streamFile) == 0 &&
|
||||
read_32bitBE(test_offset+0x08,streamFile) == 0 &&
|
||||
read_32bitBE(test_offset+0x0c,streamFile) == 0)
|
||||
goto fail;
|
||||
|
||||
test_offset += 0x80;
|
||||
}
|
||||
|
||||
test_offset += (is_blocked ? 0x18 : 0x00); /* footer */
|
||||
block++;
|
||||
}
|
||||
}
|
||||
/* test for XA data, since format is raw-ish (with RIFF it's assumed to be ok) */
|
||||
if (!is_riff && !xa_check_format(streamFile, start_offset, is_blocked))
|
||||
goto fail;
|
||||
|
||||
/* find subsongs as XA can interleave sectors using 'file' and 'channel' makers (see blocked_xa.c) */
|
||||
if (is_blocked) {
|
||||
off_t offset;
|
||||
STREAMFILE *sf_test = NULL;
|
||||
|
||||
/* mini buffer to speed up by reading headers only (not sure if O.S. has buffer though */
|
||||
sf_test = reopen_streamfile(streamFile, 0x10);
|
||||
if (!sf_test) goto fail;
|
||||
|
||||
//TODO add subsongs (optimized for giant xa)
|
||||
// - only do if first sector and next sector have different channels?
|
||||
// N sectors of the same channel then N sectors of another should't happen, but
|
||||
// we need to read all sectors to count samples anyway.
|
||||
// - read all sectors
|
||||
// - skip non-audio sectors
|
||||
// - find first actual stream start
|
||||
// - detect file+channel change + register new subsong (or detect file end flags too)
|
||||
// - save total sectors subsong_sectors[subsong] = N for quick sample calcs + stream size
|
||||
// (block_update is much slower since it buffers all data)
|
||||
total_subsongs = 0;
|
||||
|
||||
target_config = -1;
|
||||
|
||||
offset = start_offset;
|
||||
while (offset < file_size) {
|
||||
uint16_t xa_subheader = read_u16be(offset + 0x10, sf_test);
|
||||
uint8_t xa_submode = read_u8 (offset + 0x12, sf_test);
|
||||
int is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
|
||||
if (!is_audio) {
|
||||
offset += 0x900 + 0x18 + 0x18;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//if target_subsong ..
|
||||
//total_subsongs++
|
||||
//...
|
||||
target_config = xa_subheader;
|
||||
start_offset = offset; //stream_offset
|
||||
break;
|
||||
}
|
||||
|
||||
close_streamfile(sf_test);
|
||||
|
||||
stream_size = file_size;
|
||||
//stream_size = ...;
|
||||
|
||||
//if (target_subsong == 0) target_subsong = 1;
|
||||
//if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
/* file has no audio */
|
||||
if (target_config < 0) {
|
||||
VGM_LOG("XA: no audio found");
|
||||
goto fail;
|
||||
}
|
||||
if (/*!is_riff &&*/ is_blocked) {
|
||||
total_subsongs = xa_read_subsongs(streamFile, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2);
|
||||
if (total_subsongs <= 0) goto fail;
|
||||
}
|
||||
else {
|
||||
stream_size = get_streamfile_size(streamFile) - start_offset;
|
||||
}
|
||||
|
||||
|
||||
/* data is ok: parse header */
|
||||
if (is_blocked) {
|
||||
/* parse 0x18 sector header (also see blocked_xa.c) */
|
||||
uint8_t xa_header = (uint8_t)read_8bit(start_offset + 0x13,streamFile);
|
||||
uint8_t xa_header = read_u8(start_offset + 0x13,streamFile);
|
||||
|
||||
switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */
|
||||
case 0: channel_count = 1; break;
|
||||
@ -181,13 +89,13 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
|
||||
case 0: break;
|
||||
default: /* shouldn't be used by games */
|
||||
VGM_LOG("XA: unknown emphasis found\n");
|
||||
break;
|
||||
break;
|
||||
}
|
||||
switch((xa_header >> 7) & 1) { /* 7: reserved */
|
||||
case 0: break;
|
||||
default:
|
||||
VGM_LOG("XA: unknown reserved bit found\n");
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -221,39 +129,220 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
|
||||
vgmstream->meta_type = meta_XA;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->coding_type = coding_XA;
|
||||
vgmstream->layout_type = is_blocked ? layout_blocked_xa : layout_none;
|
||||
|
||||
if (is_blocked) {
|
||||
vgmstream->codec_config = target_config;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
if (total_subsongs > 1) {
|
||||
/* useful at times if game uses many file+channel */
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%04x", target_config);
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->num_samples = xa_bytes_to_samples(stream_size, channel_count, is_blocked, is_form2);
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
|
||||
if (is_blocked) {
|
||||
/* calc num_samples as blocks may be empty or smaller than usual depending on flags */
|
||||
vgmstream->next_block_offset = start_offset;
|
||||
do {
|
||||
block_update(vgmstream->next_block_offset,vgmstream);
|
||||
vgmstream->num_samples += vgmstream->current_block_samples;
|
||||
}
|
||||
while (vgmstream->next_block_offset < file_size);
|
||||
block_update(start_offset,vgmstream);
|
||||
}
|
||||
else {
|
||||
vgmstream->num_samples = xa_bytes_to_samples(file_size - start_offset, channel_count, is_blocked);
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
|
||||
int i, j, sector = 0, skip = 0;
|
||||
off_t test_offset = offset;
|
||||
const size_t sector_size = (is_blocked ? 0x900 : 0x800);
|
||||
const size_t extra_size = (is_blocked ? 0x18 : 0x00);
|
||||
const size_t frame_size = 0x80;
|
||||
const int sector_max = 3;
|
||||
const int skip_max = 32; /* videos interleave 7 or 15 sectors + 1 audio sector, maybe 31 too */
|
||||
|
||||
/* test frames inside CD sectors */
|
||||
while (sector < sector_max) {
|
||||
uint8_t xa_submode = read_u8(test_offset + 0x12, sf);
|
||||
int is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
|
||||
if (is_blocked && !is_audio) {
|
||||
skip++;
|
||||
if (sector == 0 && skip > skip_max) /* no a single audio sector found */
|
||||
goto fail;
|
||||
test_offset += sector_size + extra_size + extra_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
test_offset += extra_size; /* header */
|
||||
|
||||
for (i = 0; i < (sector_size / frame_size); i++) {
|
||||
/* XA frame checks: filter indexes should be 0..3, and shifts 0..C */
|
||||
for (j = 0; j < 16; j++) {
|
||||
uint8_t header = read_u8(test_offset + j, sf);
|
||||
if (((header >> 4) & 0xF) > 0x03)
|
||||
goto fail;
|
||||
if (((header >> 0) & 0xF) > 0x0c)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* XA headers pairs are repeated */
|
||||
if (read_u32be(test_offset+0x00, sf) != read_u32be(test_offset+0x04, sf) ||
|
||||
read_u32be(test_offset+0x08, sf) != read_u32be(test_offset+0x0c, sf))
|
||||
goto fail;
|
||||
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
|
||||
if (read_u32be(test_offset+0x00, sf) == 0 &&
|
||||
read_u32be(test_offset+0x04, sf) == 0 &&
|
||||
read_u32be(test_offset+0x08, sf) == 0 &&
|
||||
read_u32be(test_offset+0x0c, sf) == 0)
|
||||
goto fail;
|
||||
|
||||
test_offset += 0x80;
|
||||
}
|
||||
|
||||
test_offset += extra_size; /* footer */
|
||||
sector++;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#define XA_SUBSONG_MAX 1024 /* +500 found in bigfiles like Castlevania SOTN */
|
||||
|
||||
typedef struct xa_subsong_t {
|
||||
uint16_t config;
|
||||
off_t start;
|
||||
int form2;
|
||||
int sectors;
|
||||
int end_flag;
|
||||
} xa_subsong_t;
|
||||
|
||||
/* Get subsong info, as real XA data interleaves N sectors/subsongs (often 8/16). Extractors deinterleave
|
||||
* but we parse interleaved too for completeness. Even if we have a single deint'd XA this is useful to get
|
||||
* usable sectors for bytes-to-samples.
|
||||
*
|
||||
* Bigfiles that paste tons of XA together are slow to parse since we need to read every sector to
|
||||
* count totals, but XA subsong handling is mainly for educational purposes. */
|
||||
static int xa_read_subsongs(STREAMFILE *sf, int target_subsong, off_t start, uint16_t *p_stream_config, off_t *p_stream_offset, size_t *p_stream_size, int *p_form2) {
|
||||
xa_subsong_t *cur_subsong = NULL;
|
||||
xa_subsong_t subsongs[XA_SUBSONG_MAX] = {0};
|
||||
const size_t sector_size = 0x930;
|
||||
uint16_t prev_config;
|
||||
int i, subsongs_count = 0;
|
||||
size_t file_size;
|
||||
off_t offset;
|
||||
STREAMFILE *sf_test = NULL;
|
||||
uint8_t header[4];
|
||||
|
||||
|
||||
/* buffer to speed up header reading; bigger (+0x8000) is actually faster than very small (~0x10),
|
||||
* even though we only need sector headers and will end up reading the whole file that way */
|
||||
sf_test = reopen_streamfile(sf, 0x10000);
|
||||
if (!sf_test) goto fail;
|
||||
|
||||
prev_config = 0xFFFFu;
|
||||
file_size = get_streamfile_size(sf);
|
||||
offset = start;
|
||||
|
||||
/* read XA sectors */
|
||||
while (offset < file_size) {
|
||||
uint16_t xa_config;
|
||||
uint8_t xa_submode;
|
||||
int is_audio, is_eof;
|
||||
|
||||
read_streamfile(header, offset + 0x10, sizeof(header), sf_test);
|
||||
xa_config = get_u16be(header + 0x00); /* file+channel markers */
|
||||
xa_submode = get_u8 (header + 0x02); /* flags */
|
||||
is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
is_eof = (xa_submode & 0x80);
|
||||
|
||||
VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %lx\n", offset); /* used? */
|
||||
//;VGM_ASSERT(is_eof, "XA: eof at %lx\n", offset);
|
||||
//;VGM_ASSERT(!is_audio, "XA: not audio at %lx\n", offset);
|
||||
|
||||
if (!is_audio) {
|
||||
offset += sector_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* detect file+channel change = sector of new subsong or existing subsong
|
||||
* (happens on every sector for interleaved XAs but only once for deint'd or video XAs) */
|
||||
if (xa_config != prev_config)
|
||||
{
|
||||
/* find if this sector/config belongs to known subsong that hasn't ended
|
||||
* (unsure if repeated configs+end flag is possible but probably in giant XAs) */
|
||||
cur_subsong = NULL;
|
||||
for (i = 0; i < subsongs_count; i++) { /* search algo could be improved, meh */
|
||||
if (subsongs[i].config == xa_config && !subsongs[i].end_flag) {
|
||||
cur_subsong = &subsongs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* old subsong not found = add new to list */
|
||||
if (cur_subsong == NULL) {
|
||||
uint8_t xa_channel = get_u8(header + 0x01);
|
||||
|
||||
/* when file+channel changes mark prev subsong of the same channel as finished
|
||||
* (this ensures reused ids in bigfiles are handled properly, ex.: 0101... 0201... 0101...) */
|
||||
for (i = subsongs_count - 1; i >= 0; i--) {
|
||||
uint16_t subsong_config = subsongs[i].config;
|
||||
uint8_t subsong_channel = subsong_config & 0xFF;
|
||||
if (xa_config != subsong_config && xa_channel == subsong_channel) {
|
||||
subsongs[i].end_flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cur_subsong = &subsongs[subsongs_count];
|
||||
subsongs_count++;
|
||||
if (subsongs_count >= XA_SUBSONG_MAX) goto fail;
|
||||
|
||||
cur_subsong->config = xa_config;
|
||||
cur_subsong->form2 = (xa_submode & 0x20);
|
||||
cur_subsong->start = offset;
|
||||
//cur_subsong->sectors = 0;
|
||||
//cur_subsong->end_flag = 0;
|
||||
|
||||
//;VGM_LOG("XA: new subsong %i with config %04x at %lx\n", subsongs_count, xa_config, offset);
|
||||
}
|
||||
|
||||
prev_config = xa_config;
|
||||
}
|
||||
else {
|
||||
if (cur_subsong == NULL) goto fail;
|
||||
}
|
||||
|
||||
cur_subsong->sectors++;
|
||||
if (is_eof)
|
||||
cur_subsong->end_flag = 1;
|
||||
|
||||
offset += sector_size;
|
||||
}
|
||||
|
||||
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n");
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > subsongs_count || subsongs_count < 1) goto fail;
|
||||
|
||||
cur_subsong = &subsongs[target_subsong - 1];
|
||||
*p_stream_config = cur_subsong->config;
|
||||
*p_stream_offset = cur_subsong->start;
|
||||
*p_stream_size = cur_subsong->sectors * sector_size;
|
||||
*p_form2 = cur_subsong->form2;
|
||||
|
||||
//;VGM_LOG("XA: subsong config=%x, offset=%lx, size=%x, form2=%i\n", *p_stream_config, *p_stream_offset, *p_stream_size, *p_form2);
|
||||
|
||||
close_streamfile(sf_test);
|
||||
return subsongs_count;
|
||||
fail:
|
||||
close_streamfile(sf_test);
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user