From 4dfbc3cf6ad1e8b0c3926c902aff11226996a971 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 16 Nov 2017 19:47:42 +0100 Subject: [PATCH] Improve CD-XA detection and RIFF-less support; minor XA code cleanup --- src/coding/coding.h | 3 +- src/coding/xa_decoder.c | 24 +++-- src/layout/xa_blocked.c | 93 +++++++++++-------- src/meta/psx_cdxa.c | 193 ++++++++++++++++++---------------------- src/vgmstream.h | 16 ++-- 5 files changed, 171 insertions(+), 158 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 2fbb7479..a21b17d2 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -80,7 +80,8 @@ size_t ps_bytes_to_samples(size_t bytes, int channels); /* xa_decoder */ void decode_xa(VGMSTREAM * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void init_get_high_nibble(VGMSTREAM * vgmstream); +void xa_init_get_high_nibble(VGMSTREAM * vgmstream); +size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked); /* ea_xa_decoder */ void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); diff --git a/src/coding/xa_decoder.c b/src/coding/xa_decoder.c index 12b61e48..deeada36 100644 --- a/src/coding/xa_decoder.c +++ b/src/coding/xa_decoder.c @@ -20,8 +20,8 @@ static int CLAMP(int value, int Minim, int Maxim) return value; } -void init_get_high_nibble(VGMSTREAM *vgmstream) { - vgmstream->get_high_nibble=1; +void xa_init_get_high_nibble(VGMSTREAM *vgmstream) { + vgmstream->xa_get_high_nibble=1; } void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { @@ -41,18 +41,18 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32 first_sample = first_sample % 28; - vgmstream->get_high_nibble=!vgmstream->get_high_nibble; + vgmstream->xa_get_high_nibble=!vgmstream->xa_get_high_nibble; if((first_sample) && (channelspacing==1)) - vgmstream->get_high_nibble=!vgmstream->get_high_nibble; + vgmstream->xa_get_high_nibble=!vgmstream->xa_get_high_nibble; - predict_nr = read_8bit(stream->offset+HeadTable[framesin]+vgmstream->get_high_nibble,stream->streamfile) >> 4; - shift_factor = read_8bit(stream->offset+HeadTable[framesin]+vgmstream->get_high_nibble,stream->streamfile) & 0xf; + predict_nr = read_8bit(stream->offset+HeadTable[framesin]+vgmstream->xa_get_high_nibble,stream->streamfile) >> 4; + shift_factor = read_8bit(stream->offset+HeadTable[framesin]+vgmstream->xa_get_high_nibble,stream->streamfile) & 0xf; for (i=first_sample,sample_count=0; ioffset+16+framesin+(i*4),stream->streamfile); - scale = ((vgmstream->get_high_nibble ? + scale = ((vgmstream->xa_get_high_nibble ? sample_byte >> 4 : sample_byte & 0x0f)<<12); @@ -70,3 +70,13 @@ void decode_xa(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32 stream->adpcm_history1_32=hist1; stream->adpcm_history2_32=hist2; } + +size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked) { + if (is_blocked) { + //todo with -0x10 misses the last sector, not sure if bug or feature + return ((bytes - 0x10) / 0x930) * (0x900 - 18*0x10) * 2 / channels; + } + else { + return ((bytes / 0x80)*0xE0) / 2; + } +} diff --git a/src/layout/xa_blocked.c b/src/layout/xa_blocked.c index e84cb15b..b8bdd4f1 100644 --- a/src/layout/xa_blocked.c +++ b/src/layout/xa_blocked.c @@ -4,48 +4,69 @@ /* set up for the block at the given offset */ void xa_block_update(off_t block_offset, VGMSTREAM * vgmstream) { + int i; + int8_t currentChannel=0; + int8_t subAudio=0; - int i; - int8_t currentChannel=0; - int8_t subAudio=0; - - init_get_high_nibble(vgmstream); + xa_init_get_high_nibble(vgmstream); - if(vgmstream->samples_into_block!=0) - // don't change this variable in the init process - vgmstream->xa_sector_length+=128; + /* don't change this variable in the init process */ + if (vgmstream->samples_into_block != 0) + vgmstream->xa_sector_length += 0x80; - // We get to the end of a sector ? - if(vgmstream->xa_sector_length==(18*128)) { - vgmstream->xa_sector_length=0; + /* XA mode2/form2 sector + * 0x00: sync word + * 0x0c: header = minute, second, sector, mode (always 0x02) + * 0x10: subheader = file, channel (marker), submode flags, xa header + * 0x14: subheader again + * 0x18: data + * 0x918: unused + * 0x92c: EDC/checksum or null + * 0x930: end + */ - // 0x30 of unused bytes/sector :( - if (!vgmstream->xa_headerless) { - block_offset+=0x30; + /* submode flags (typical audio value = 0x64) + * - 7: end of file + * - 6: real time mode + * - 5: sector form (0=form1, 1=form2) + * - 4: trigger (for application) + * - 3: data sector + * - 2: audio sector + * - 1: video sector + * - 0: end of audio + */ + + // We get to the end of a sector ? + if (vgmstream->xa_sector_length == (18*0x80)) { + vgmstream->xa_sector_length = 0; + + // 0x30 of unused bytes/sector :( + if (!vgmstream->xa_headerless) { + block_offset += 0x30; begin: - // Search for selected channel & valid audio - currentChannel=read_8bit(block_offset-7,vgmstream->ch[0].streamfile); - subAudio=read_8bit(block_offset-6,vgmstream->ch[0].streamfile); + // Search for selected channel & valid audio + currentChannel = read_8bit(block_offset-0x07,vgmstream->ch[0].streamfile); + subAudio = read_8bit(block_offset-0x06,vgmstream->ch[0].streamfile); - // audio is coded as 0x64 - if(!((subAudio==0x64) && (currentChannel==vgmstream->xa_channel))) { - // go to next sector - block_offset+=2352; - if(currentChannel!=-1) goto begin; - } - } - } + // audio is coded as 0x64 + if (!((subAudio==0x64) && (currentChannel==vgmstream->xa_channel))) { + // go to next sector + block_offset += 0x930; + if (currentChannel!=-1) goto begin; + } + } + } - vgmstream->current_block_offset = block_offset; + vgmstream->current_block_offset = block_offset; - // Quid : how to stop the current channel ??? - // i set up 0 to current_block_size to make vgmstream not playing bad samples - // another way to do it ??? - // (as the number of samples can be false in cd-xa due to multi-channels) - vgmstream->current_block_size = (currentChannel==-1?0:112); - - vgmstream->next_block_offset = vgmstream->current_block_offset+128; - for (i=0;ichannels;i++) { - vgmstream->ch[i].offset = vgmstream->current_block_offset; - } + // Quid : how to stop the current channel ??? + // i set up 0 to current_block_size to make vgmstream not playing bad samples + // another way to do it ??? + // (as the number of samples can be false in cd-xa due to multi-channels) + vgmstream->current_block_size = (currentChannel==-1 ? 0 : 0x70); + + vgmstream->next_block_offset = vgmstream->current_block_offset + 0x80; + for (i=0;ichannels;i++) { + vgmstream->ch[i].offset = vgmstream->current_block_offset; + } } diff --git a/src/meta/psx_cdxa.c b/src/meta/psx_cdxa.c index c6ab39ca..6e612c09 100644 --- a/src/meta/psx_cdxa.c +++ b/src/meta/psx_cdxa.c @@ -1,135 +1,118 @@ #include "meta.h" #include "../layout/layout.h" -#include "../util.h" - -/* Sony PSX CD-XA */ -/* No looped file ! */ - -static off_t init_xa_channel(int *channel,STREAMFILE *streamFile); - -static uint8_t AUDIO_CODING_GET_STEREO(uint8_t value) { - return (uint8_t)(value & 3); -} - -static uint8_t AUDIO_CODING_GET_FREQ(uint8_t value) { - return (uint8_t)((value >> 2) & 3); -} +#include "../coding/coding.h" +/* CD-XA - from Sony PS1 CDs */ VGMSTREAM * init_vgmstream_cdxa(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; + off_t start_offset; + int loop_flag = 0, channel_count, sample_rate; + int xa_channel=0; + int is_blocked; + size_t file_size = get_streamfile_size(streamFile); - int channel_count; - int headerless=0; - int xa_channel=0; - uint8_t bCoding; - off_t start_offset; + /* check extension (.xa: common, .str: sometimes used) */ + if ( !check_extensions(streamFile,"xa,str") ) + goto fail; - int i; + /* 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. */ - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("xa",filename_extension(filename))) goto fail; + /* 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 " */ + is_blocked = 1; + start_offset = 0x2c; /* after "data" (chunk size tends to be a bit off) */ + } + else { + /* sector sync word = raw */ + if (read_32bitBE(0x00,streamFile) == 0x00FFFFFF && + read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && + read_32bitBE(0x08,streamFile) == 0xFFFFFF00) { + is_blocked = 1; + start_offset = 0x00; + } + else { /* headerless */ + is_blocked = 0; + start_offset = 0x00; + } + } - /* check RIFF Header */ - if (!((read_32bitBE(0x00,streamFile) == 0x52494646) && - (read_32bitBE(0x08,streamFile) == 0x43445841) && - (read_32bitBE(0x0C,streamFile) == 0x666D7420))) - headerless=1; + /* test first block (except when RIFF) */ + if (start_offset == 0) { + int i, j; - /* don't misdetect Reflections' XA ("XA30" / "04SW") */ - if (read_32bitBE(0x00,streamFile) == 0x58413330 || read_32bitBE(0x00,streamFile) == 0x30345357) goto fail; - /* don't misdetect Maxis XA ("XAI\0" / "XAJ\0") */ - if (read_32bitBE(0x00,streamFile) == 0x58414900 || read_32bitBE(0x00,streamFile) == 0x58414A00) goto fail; + /* 0x80 frames for 1 sector (max ~0x800 for ISO mode) */ + for (i = 0; i < (0x800/0x80); i++) { + off_t test_offset = start_offset + (is_blocked ? 0x18 : 0x00) + 0x80*i; - /* First init to have the correct info of the channel */ - if (!headerless) { - start_offset=init_xa_channel(&xa_channel,streamFile); + /* ADPCM predictors should be 0..3 index */ + for (j = 0; j < 16; j++) { + uint8_t header = read_8bit(test_offset + i, streamFile); + if (((header >> 4) & 0xF) > 3) + goto fail; + } + } + } - /* No sound ? */ - if(start_offset==0) - goto fail; - bCoding = read_8bit(start_offset-5,streamFile); + /* data is ok: parse header */ + if (is_blocked) { + uint8_t xa_header; - switch (AUDIO_CODING_GET_STEREO(bCoding)) { - case 0: channel_count = 1; break; - case 1: channel_count = 2; break; - default: channel_count = 0; break; - } + /* parse 0x18 sector header (also see xa_blocked.c) */ + xa_channel = read_8bit(start_offset + 0x11,streamFile); + xa_header = read_8bit(start_offset + 0x13,streamFile); - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,0); - if (!vgmstream) goto fail; + switch((xa_header >> 0) & 3) { /* 0..1: stereo */ + case 0: channel_count = 1; break; + case 1: channel_count = 2; break; + default: goto fail; + } + switch((xa_header >> 2) & 3) { /* 2..3: sample rate */ + case 0: sample_rate = 37800; break; + case 1: sample_rate = 18900; break; + default: goto fail; + } + VGM_ASSERT(((xa_header >> 4) & 3) == 1, /* 4..5: bits per sample (0=4, 1=8) */ + "XA: 8 bits per sample mode found\n"); /* spec only? */ + /* 6: emphasis (applies a filter but apparently not used by games) + * XA is also filtered when resampled to 44100 during output, differently from PS-ADPCM */ + /* 7: reserved */ + } + else { + /* headerless, probably will go wrong */ + channel_count = 2; + sample_rate = 44100; /* not 37800? */ + } - /* fill in the vital statistics */ - vgmstream->channels = channel_count; - vgmstream->xa_channel = xa_channel; - switch (AUDIO_CODING_GET_FREQ(bCoding)) { - case 0: vgmstream->sample_rate = 37800; break; - case 1: vgmstream->sample_rate = 18900; break; - default: vgmstream->sample_rate = 0; break; - } + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; - /* Check for Compression Scheme */ - vgmstream->num_samples = (int32_t)((((get_streamfile_size(streamFile) - 0x3C)/2352)*0x1F80)/(2*channel_count)); - } else - { - channel_count=2; - vgmstream = allocate_vgmstream(2,0); - if (!vgmstream) goto fail; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = xa_bytes_to_samples(file_size - start_offset, channel_count, is_blocked); + vgmstream->xa_headerless = !is_blocked; + vgmstream->xa_channel = xa_channel; - vgmstream->xa_headerless=1; - vgmstream->sample_rate=44100; - vgmstream->channels=2; - vgmstream->num_samples = (int32_t)(((get_streamfile_size(streamFile)/ 0x80)*0xE0)/2); - start_offset=0; - } - - vgmstream->coding_type = coding_XA; + vgmstream->coding_type = coding_XA; vgmstream->layout_type = layout_xa_blocked; vgmstream->meta_type = meta_PSX_XA; - /* open the file for reading by each channel */ - { - STREAMFILE *chstreamfile; - chstreamfile = streamFile->open(streamFile,filename,2352); + if (is_blocked) + start_offset += 0x18; /* move to first frame (hack for xa_blocked.c) */ - if (!chstreamfile) goto fail; + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; - for (i=0;ich[i].streamfile = chstreamfile; - } - } - - xa_block_update(start_offset,vgmstream); + xa_block_update(start_offset,vgmstream); - return vgmstream; + return vgmstream; - /* clean up anything we may have opened */ fail: if (vgmstream) close_vgmstream(vgmstream); return NULL; } - -off_t init_xa_channel(int* channel,STREAMFILE* streamFile) { - - off_t block_offset=0x44; - size_t filelength=get_streamfile_size(streamFile); - - int8_t currentChannel; - - // 0 can't be a correct value - if(block_offset>=(off_t)filelength) - return 0; - - currentChannel=read_8bit(block_offset-7,streamFile); - //subAudio=read_8bit(block_offset-6,streamFile); - *channel=currentChannel; - //if (!((currentChannel==channel) && (subAudio==0x64))) { - // block_offset+=2352; - // goto begin; - //} - return block_offset; -} diff --git a/src/vgmstream.h b/src/vgmstream.h index 9ea12de3..f5bc9c32 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -715,10 +715,10 @@ typedef struct { layout_t layout_type; /* type of layout for data */ meta_t meta_type; /* how we know the metadata */ - /* streams (info only) */ - int num_streams; /* for multi-stream formats (0=not set/one, 1=one stream) */ - int stream_index; /* current stream */ - char stream_name[STREAM_NAME_SIZE]; /* name of the current stream, if the file stores it and it's filled */ + /* subsongs */ + int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ + int stream_index; /* selected stream (also 1-based) */ + char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */ /* looping */ int loop_flag; /* is this stream looped? */ @@ -766,12 +766,10 @@ typedef struct { uint8_t xa_channel; /* XA ADPCM: selected channel */ int32_t xa_sector_length; /* XA ADPCM: XA block */ - uint8_t xa_headerless; /* XA ADPCM: headerless XA block */ + uint8_t xa_headerless; /* XA ADPCM: headerless XA */ + int8_t xa_get_high_nibble; /* XA ADPCM: mono/stereo nibble selection (XA state could be simplified) */ - int8_t get_high_nibble; /* ADPCM: which nibble (XA, IMA, EA) */ - - uint8_t ea_big_endian; /* EA ADPCM stuff */ - uint8_t ea_platform; + uint8_t ea_platform; /* EA block */ int32_t ws_output_size; /* WS ADPCM: output bytes for this block */