diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 132a4e95e..02e1142f9 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -135,4 +135,6 @@ void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int channels); +void decode_hca(hca_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels); + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index ccfd93609..1a8510cfb 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -117,6 +117,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, endOfStream = data->endOfStream; endOfAudio = data->endOfAudio; + /* keep reading and decoding packets until the requested number of samples (in bytes) */ while (bytesRead < bytesToRead) { int planeSize; int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt); @@ -127,6 +128,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, if (dataSize < 0) dataSize = 0; + /* read packet */ while (readNextPacket && !endOfAudio) { if (!endOfStream) { av_packet_unref(lastReadPacket); @@ -138,7 +140,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, break; } if (lastReadPacket->stream_index != streamIndex) - continue; + continue; /* ignore non audio streams */ } if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) { @@ -150,6 +152,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, readNextPacket = 0; } + /* decode packet */ if (dataSize <= bytesConsumedFromDecodedFrame) { if (endOfStream && endOfAudio) break; @@ -178,21 +181,32 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead)); + /* discard packet if needed (fully or partially) */ if (data->samplesToDiscard) { + int samplesToConsume; int bytesPerFrame = ((data->bitsPerSample / 8) * channels); - int samplesToConsume = toConsume / bytesPerFrame; - if (data->samplesToDiscard >= samplesToConsume) { + + /* discard all if there are more samples to do than the packet's samples */ + if (data->samplesToDiscard >= dataSize / bytesPerFrame) { + samplesToConsume = dataSize / bytesPerFrame; + } + else { + samplesToConsume = toConsume / bytesPerFrame; + } + + if (data->samplesToDiscard >= samplesToConsume) { /* full discard: skip to next */ data->samplesToDiscard -= samplesToConsume; bytesConsumedFromDecodedFrame = dataSize; continue; } - else { + else { /* partial discard: copy below */ bytesConsumedFromDecodedFrame += data->samplesToDiscard * bytesPerFrame; toConsume -= data->samplesToDiscard * bytesPerFrame; data->samplesToDiscard = 0; } } - + + /* copy packet to buffer (mux channels if needed) */ if (!planar || channels == 1) { memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume); } @@ -210,6 +224,7 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, } } + /* consume */ bytesConsumedFromDecodedFrame += toConsume; bytesRead += toConsume; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx_header.c b/Frameworks/vgmstream/vgmstream/src/meta/adx_header.c index 223dac6ed..4bc59d8d6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/adx_header.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/adx_header.c @@ -43,7 +43,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) { if (xb3d_flag) { channel_count = read_32bitLE(0, streamFile); loop_flag = read_16bitLE(0x6e, streamFile); - channel_header_spacing = 0x34; + channel_header_spacing = 0x34; } else { /* get stream offset, check for CRI signature just before */ @@ -109,6 +109,16 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) { loop_end_sample = read_32bitBE(0x30,streamFile); //loop_end_offset = read_32bitBE(0x34,streamFile); } + + /* AINF header can also start after the loop points + * (may be inserted by CRI's tools but is rarely used) */ + /* ainf_magic = read_32bitBE(0x38,streamFile); */ /* 0x41494E46 */ + /* ainf_length = read_32bitBE(0x3c,streamFile); */ + /* ainf_str_id = read_string(0x40,streamFile); */ /* max size 0x10 */ + /* ainf_volume = read_16bitBE(0x50,streamFile); */ /* 0=base/max?, negative=reduce */ + /* ainf_pan_l = read_16bitBE(0x54,streamFile); */ /* 0=base, max +-128 */ + /* ainf_pan_r = read_16bitBE(0x56,streamFile); */ + } else if (version_signature == 0x0500) { /* found in some SFD : Buggy Heat, appears to have no loop */ header_type = meta_ADX_05; } else goto fail; /* not a known/supported version signature */ @@ -150,7 +160,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) { = read_32bitLE(0x34+i*channel_header_spacing, streamFile); if (!vgmstream->ch[i].streamfile) goto fail; } - + } else { vgmstream->num_samples = read_32bitBE(0xc,streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_file.c b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_file.c index 835779bb5..d29127f76 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_file.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis_file.c @@ -1,448 +1,455 @@ -#include "../vgmstream.h" - -#ifdef VGM_USE_VORBIS - -#include -#include -#include "meta.h" -#include "../util.h" -#include - - -#define DEFAULT_BITSTREAM 0 - -static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static size_t read_func_um3(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - /* first 0x800 bytes of um3 are xor'd with 0xff */ - if (ov_streamfile->offset < 0x800) { - int num_crypt = 0x800-ov_streamfile->offset; - int i; - - if (num_crypt > bytes_read) num_crypt=bytes_read; - for (i=0;ioffset += items_read * size; - - return items_read; -} - -static size_t read_func_kovs(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset+ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - /* first 0x100 bytes of KOVS are xor'd with offset */ - if (ov_streamfile->offset < 0x100) { - int i; - - for (i=ov_streamfile->offset;i<0x100;i++) - ((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i; - } - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - items_read = bytes_read / size; - - /* first bytes are xor'd with a constant byte */ - if (ov_streamfile->offset < ov_streamfile->scd_xor_len) { - int num_crypt = ov_streamfile->scd_xor_len-ov_streamfile->offset; - int i; - - if (num_crypt > bytes_read) num_crypt=bytes_read; - for (i=0;iscd_xor; - } - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource) -{ - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t items_read; - - size_t bytes_read; - size_t i; - - bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, - ov_streamfile->streamfile); - - /* add 0x23 ('#') */ - for (i=0;ioffset += items_read * size; - - return items_read; -} - -static int seek_func(void *datasource, ogg_int64_t offset, int whence) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - ogg_int64_t base_offset; - ogg_int64_t new_offset; - - switch (whence) { - case SEEK_SET: - base_offset = 0; - break; - case SEEK_CUR: - base_offset = ov_streamfile->offset; - break; - case SEEK_END: - base_offset = ov_streamfile->size - ov_streamfile->other_header_bytes; - break; - default: - return -1; - break; - } - - new_offset = base_offset + offset; - if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) { - return -1; - } else { - ov_streamfile->offset = new_offset; - return 0; - } -} - -static long tell_func(void * datasource) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - return ov_streamfile->offset; -} - -/* setting close_func in ov_callbacks to NULL doesn't seem to work */ -static int close_func(void * datasource) { - return 0; -} - -/* Ogg Vorbis, by way of libvorbisfile */ - -VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { - char filename[PATH_LIMIT]; - - ov_callbacks callbacks; - - off_t other_header_bytes = 0; - int um3_ogg = 0; - int kovs_ogg = 0; - int psych_ogg = 0; - - vgm_vorbis_info_t inf; - memset(&inf, 0, sizeof(inf)); - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - - /* It is only interesting to use oggs with vgmstream if they are looped. - To prevent such files from being played by other plugins and such they - may be renamed to .logg. This meta reader should still support .ogg, - though. */ - if (strcasecmp("logg",filename_extension(filename)) && - strcasecmp("ogg",filename_extension(filename))) { - if (!strcasecmp("um3",filename_extension(filename))) { - um3_ogg = 1; - } else if (!strcasecmp("kovs",filename_extension(filename))) { - kovs_ogg = 1; - } else { - goto fail; - } - } - - /* not all um3-ogg are crypted */ - if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) { - um3_ogg = 0; - } - - /* use KOVS header */ - if (kovs_ogg) { - if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */ - goto fail; - } - if (read_32bitLE(0x8,streamFile)!=0) { - inf.loop_start = read_32bitLE(0x8,streamFile); - inf.loop_flag = 1; - } - - other_header_bytes = 0x20; - } - - /* detect Psychic Software obfuscation (as seen in "Darkwind") */ - if (read_32bitBE(0x0,streamFile)==0x2c444430) { - psych_ogg = 1; - } - - if (um3_ogg) { - callbacks.read_func = read_func_um3; - } else if (kovs_ogg) { - callbacks.read_func = read_func_kovs; - } else if (psych_ogg) { - callbacks.read_func = read_func_psych; - } else { - callbacks.read_func = read_func; - } - callbacks.seek_func = seek_func; - callbacks.close_func = close_func; - callbacks.tell_func = tell_func; - - if (um3_ogg) { - inf.meta_type = meta_um3_ogg; - } else if (kovs_ogg) { - inf.meta_type = meta_KOVS_ogg; - } else if (psych_ogg) { - inf.meta_type = meta_psych_ogg; - } else { - inf.meta_type = meta_ogg_vorbis; - } - - inf.layout_type = layout_ogg_vorbis; - - return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf); - -fail: - return NULL; -} - -VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) { - VGMSTREAM * vgmstream = NULL; - - OggVorbis_File temp_ovf; - ogg_vorbis_streamfile temp_streamfile; - - ogg_vorbis_codec_data * data = NULL; - OggVorbis_File *ovf; - int inited_ovf = 0; - vorbis_info *info; - - int loop_flag = vgm_inf->loop_flag; - int32_t loop_start = vgm_inf->loop_start; - int loop_length_found = vgm_inf->loop_length_found; - int32_t loop_length = vgm_inf->loop_length; - int loop_end_found = vgm_inf->loop_end_found; - int32_t loop_end = vgm_inf->loop_end; - - ov_callbacks default_callbacks; - - if (!callbacks_p) { - default_callbacks.read_func = read_func; - default_callbacks.seek_func = seek_func; - default_callbacks.close_func = close_func; - default_callbacks.tell_func = tell_func; - - if (vgm_inf->scd_xor != 0) { - default_callbacks.read_func = read_func_scd; - } - - callbacks_p = &default_callbacks; - } - - temp_streamfile.streamfile = streamFile; - temp_streamfile.offset = 0; - temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); - temp_streamfile.other_header_bytes = other_header_bytes; - temp_streamfile.scd_xor = vgm_inf->scd_xor; - temp_streamfile.scd_xor_len = vgm_inf->scd_xor_len; - - /* can we open this as a proper ogg vorbis file? */ - memset(&temp_ovf, 0, sizeof(temp_ovf)); - if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, - 0, *callbacks_p)) goto fail; - - /* we have to close this as it has the init_vgmstream meta-reading - STREAMFILE */ - ov_clear(&temp_ovf); - - /* proceed to open a STREAMFILE just for this stream */ - data = calloc(1,sizeof(ogg_vorbis_codec_data)); - if (!data) goto fail; - - data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, - STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!data->ov_streamfile.streamfile) goto fail; - data->ov_streamfile.offset = 0; - data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile); - data->ov_streamfile.other_header_bytes = other_header_bytes; - data->ov_streamfile.scd_xor = vgm_inf->scd_xor; - data->ov_streamfile.scd_xor_len = vgm_inf->scd_xor_len; - - /* open the ogg vorbis file for real */ - if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, - 0, *callbacks_p)) goto fail; - ovf = &data->ogg_vorbis_file; - inited_ovf = 1; - - data->bitstream = DEFAULT_BITSTREAM; - - info = ov_info(ovf,DEFAULT_BITSTREAM); - - /* grab the comments */ - { - int i; - vorbis_comment *comment; - - comment = ov_comment(ovf,DEFAULT_BITSTREAM); - - /* search for a "loop_start" comment */ - for (i=0;icomments;i++) { - if (strstr(comment->user_comments[i],"loop_start=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOP_START=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOPSTART=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"um3.stream.looppoint.start=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LOOP_BEGIN=")== - comment->user_comments[i] || - strstr(comment->user_comments[i],"LoopStart=")== - comment->user_comments[i] - ) { - loop_start=atol(strrchr(comment->user_comments[i],'=')+1); - if (loop_start >= 0) - loop_flag=1; - } - else if (strstr(comment->user_comments[i],"LOOPLENGTH=")== - comment->user_comments[i]) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1); - loop_length_found=1; - } - else if (strstr(comment->user_comments[i],"title=-lps")== - comment->user_comments[i]) { - loop_start=atol(comment->user_comments[i]+10); - if (loop_start >= 0) - loop_flag=1; - } - else if (strstr(comment->user_comments[i],"album=-lpe")== - comment->user_comments[i]) { - loop_end=atol(comment->user_comments[i]+10); - loop_flag=1; - loop_end_found=1; - } - else if (strstr(comment->user_comments[i],"LoopEnd=")== - comment->user_comments[i]) { - if(loop_flag) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; - loop_length_found=1; - } - } - else if (strstr(comment->user_comments[i],"LOOP_END=")== - comment->user_comments[i]) { - if(loop_flag) { - loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; - loop_length_found=1; - } - } - else if (strstr(comment->user_comments[i],"lp=")== - comment->user_comments[i]) { - sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", - &loop_start,&loop_end); - loop_flag=1; - loop_end_found=1; - } - } - } - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(info->channels,loop_flag); - if (!vgmstream) goto fail; - - /* store our fun extra datas */ - vgmstream->codec_data = data; - - /* fill in the vital statistics */ - vgmstream->channels = info->channels; - vgmstream->sample_rate = info->rate; - - /* let's play the whole file */ - vgmstream->num_samples = ov_pcm_total(ovf,-1); - - if (loop_flag) { - vgmstream->loop_start_sample = loop_start; - if (loop_length_found) - vgmstream->loop_end_sample = loop_start+loop_length; - else if (loop_end_found) - vgmstream->loop_end_sample = loop_end; - else - vgmstream->loop_end_sample = vgmstream->num_samples; - vgmstream->loop_flag = loop_flag; - - if (vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - } - vgmstream->coding_type = coding_ogg_vorbis; - vgmstream->layout_type = vgm_inf->layout_type; - vgmstream->meta_type = vgm_inf->meta_type; - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (data) { - if (inited_ovf) - ov_clear(&data->ogg_vorbis_file); - if (data->ov_streamfile.streamfile) - close_streamfile(data->ov_streamfile.streamfile); - free(data); - } - if (vgmstream) { - vgmstream->codec_data = NULL; - close_vgmstream(vgmstream); - } - return NULL; -} - -#endif +#include "../vgmstream.h" + +#ifdef VGM_USE_VORBIS + +#include +#include +#include "meta.h" +#include "../util.h" +#include + + +#define DEFAULT_BITSTREAM 0 + +static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource) +{ + ogg_vorbis_streamfile * const ov_streamfile = datasource; + size_t items_read; + + size_t bytes_read; + + bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, + ov_streamfile->streamfile); + + items_read = bytes_read / size; + + ov_streamfile->offset += items_read * size; + + return items_read; +} + +static size_t read_func_um3(void *ptr, size_t size, size_t nmemb, void * datasource) +{ + ogg_vorbis_streamfile * const ov_streamfile = datasource; + size_t items_read; + + size_t bytes_read; + + bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, + ov_streamfile->streamfile); + + items_read = bytes_read / size; + + /* first 0x800 bytes of um3 are xor'd with 0xff */ + if (ov_streamfile->offset < 0x800) { + int num_crypt = 0x800-ov_streamfile->offset; + int i; + + if (num_crypt > bytes_read) num_crypt=bytes_read; + for (i=0;ioffset += items_read * size; + + return items_read; +} + +static size_t read_func_kovs(void *ptr, size_t size, size_t nmemb, void * datasource) +{ + ogg_vorbis_streamfile * const ov_streamfile = datasource; + size_t items_read; + + size_t bytes_read; + + bytes_read = read_streamfile(ptr, ov_streamfile->offset+ov_streamfile->other_header_bytes, size * nmemb, + ov_streamfile->streamfile); + + items_read = bytes_read / size; + + /* first 0x100 bytes of KOVS are xor'd with offset */ + if (ov_streamfile->offset < 0x100) { + int i; + + for (i=ov_streamfile->offset;i<0x100;i++) + ((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i; + } + + ov_streamfile->offset += items_read * size; + + return items_read; +} + +static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasource) +{ + ogg_vorbis_streamfile * const ov_streamfile = datasource; + size_t items_read; + + size_t bytes_read; + + bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, + ov_streamfile->streamfile); + + items_read = bytes_read / size; + + /* first bytes are xor'd with a constant byte */ + if (ov_streamfile->offset < ov_streamfile->scd_xor_len) { + int num_crypt = ov_streamfile->scd_xor_len-ov_streamfile->offset; + int i; + + if (num_crypt > bytes_read) num_crypt=bytes_read; + for (i=0;iscd_xor; + } + + ov_streamfile->offset += items_read * size; + + return items_read; +} + +static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource) +{ + ogg_vorbis_streamfile * const ov_streamfile = datasource; + size_t items_read; + + size_t bytes_read; + size_t i; + + bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, + ov_streamfile->streamfile); + + /* add 0x23 ('#') */ + for (i=0;ioffset += items_read * size; + + return items_read; +} + +static int seek_func(void *datasource, ogg_int64_t offset, int whence) { + ogg_vorbis_streamfile * const ov_streamfile = datasource; + ogg_int64_t base_offset; + ogg_int64_t new_offset; + + switch (whence) { + case SEEK_SET: + base_offset = 0; + break; + case SEEK_CUR: + base_offset = ov_streamfile->offset; + break; + case SEEK_END: + base_offset = ov_streamfile->size - ov_streamfile->other_header_bytes; + break; + default: + return -1; + break; + } + + new_offset = base_offset + offset; + if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) { + return -1; + } else { + ov_streamfile->offset = new_offset; + return 0; + } +} + +static long tell_func(void * datasource) { + ogg_vorbis_streamfile * const ov_streamfile = datasource; + return ov_streamfile->offset; +} + +/* setting close_func in ov_callbacks to NULL doesn't seem to work */ +static int close_func(void * datasource) { + return 0; +} + +/* Ogg Vorbis, by way of libvorbisfile */ + +VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { + char filename[PATH_LIMIT]; + + ov_callbacks callbacks; + + off_t other_header_bytes = 0; + int um3_ogg = 0; + int kovs_ogg = 0; + int psych_ogg = 0; + + vgm_vorbis_info_t inf; + memset(&inf, 0, sizeof(inf)); + + /* check extension, case insensitive */ + streamFile->get_name(streamFile,filename,sizeof(filename)); + + /* It is only interesting to use oggs with vgmstream if they are looped. + To prevent such files from being played by other plugins and such they + may be renamed to .logg. This meta reader should still support .ogg, + though. */ + if (strcasecmp("logg",filename_extension(filename)) && + strcasecmp("ogg",filename_extension(filename))) { + if (!strcasecmp("um3",filename_extension(filename))) { + um3_ogg = 1; + } else if (!strcasecmp("kovs",filename_extension(filename))) { + kovs_ogg = 1; + } else { + goto fail; + } + } + + /* not all um3-ogg are crypted */ + if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) { + um3_ogg = 0; + } + + /* use KOVS header */ + if (kovs_ogg) { + if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */ + goto fail; + } + if (read_32bitLE(0x8,streamFile)!=0) { + inf.loop_start = read_32bitLE(0x8,streamFile); + inf.loop_flag = 1; + } + + other_header_bytes = 0x20; + } + + /* detect Psychic Software obfuscation (as seen in "Darkwind") */ + if (read_32bitBE(0x0,streamFile)==0x2c444430) { + psych_ogg = 1; + } + + if (um3_ogg) { + callbacks.read_func = read_func_um3; + } else if (kovs_ogg) { + callbacks.read_func = read_func_kovs; + } else if (psych_ogg) { + callbacks.read_func = read_func_psych; + } else { + callbacks.read_func = read_func; + } + callbacks.seek_func = seek_func; + callbacks.close_func = close_func; + callbacks.tell_func = tell_func; + + if (um3_ogg) { + inf.meta_type = meta_um3_ogg; + } else if (kovs_ogg) { + inf.meta_type = meta_KOVS_ogg; + } else if (psych_ogg) { + inf.meta_type = meta_psych_ogg; + } else { + inf.meta_type = meta_ogg_vorbis; + } + + inf.layout_type = layout_ogg_vorbis; + + return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf); + +fail: + return NULL; +} + +VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) { + VGMSTREAM * vgmstream = NULL; + + OggVorbis_File temp_ovf; + ogg_vorbis_streamfile temp_streamfile; + + ogg_vorbis_codec_data * data = NULL; + OggVorbis_File *ovf; + int inited_ovf = 0; + vorbis_info *info; + + int loop_flag = vgm_inf->loop_flag; + int32_t loop_start = vgm_inf->loop_start; + int loop_length_found = vgm_inf->loop_length_found; + int32_t loop_length = vgm_inf->loop_length; + int loop_end_found = vgm_inf->loop_end_found; + int32_t loop_end = vgm_inf->loop_end; + + ov_callbacks default_callbacks; + + if (!callbacks_p) { + default_callbacks.read_func = read_func; + default_callbacks.seek_func = seek_func; + default_callbacks.close_func = close_func; + default_callbacks.tell_func = tell_func; + + if (vgm_inf->scd_xor != 0) { + default_callbacks.read_func = read_func_scd; + } + + callbacks_p = &default_callbacks; + } + + temp_streamfile.streamfile = streamFile; + temp_streamfile.offset = 0; + temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); + temp_streamfile.other_header_bytes = other_header_bytes; + temp_streamfile.scd_xor = vgm_inf->scd_xor; + temp_streamfile.scd_xor_len = vgm_inf->scd_xor_len; + + /* can we open this as a proper ogg vorbis file? */ + memset(&temp_ovf, 0, sizeof(temp_ovf)); + if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, + 0, *callbacks_p)) goto fail; + + /* we have to close this as it has the init_vgmstream meta-reading + STREAMFILE */ + ov_clear(&temp_ovf); + + /* proceed to open a STREAMFILE just for this stream */ + data = calloc(1,sizeof(ogg_vorbis_codec_data)); + if (!data) goto fail; + + data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, + STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!data->ov_streamfile.streamfile) goto fail; + data->ov_streamfile.offset = 0; + data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile); + data->ov_streamfile.other_header_bytes = other_header_bytes; + data->ov_streamfile.scd_xor = vgm_inf->scd_xor; + data->ov_streamfile.scd_xor_len = vgm_inf->scd_xor_len; + + /* open the ogg vorbis file for real */ + if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, + 0, *callbacks_p)) goto fail; + ovf = &data->ogg_vorbis_file; + inited_ovf = 1; + + data->bitstream = DEFAULT_BITSTREAM; + + info = ov_info(ovf,DEFAULT_BITSTREAM); + + /* grab the comments */ + { + int i; + vorbis_comment *comment; + + comment = ov_comment(ovf,DEFAULT_BITSTREAM); + + /* search for a "loop_start" comment */ + for (i=0;icomments;i++) { + if (strstr(comment->user_comments[i],"loop_start=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"LOOP_START=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"LOOPSTART=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"um3.stream.looppoint.start=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"LOOP_BEGIN=")== + comment->user_comments[i] || + strstr(comment->user_comments[i],"LoopStart=")== + comment->user_comments[i] + ) { + loop_start=atol(strrchr(comment->user_comments[i],'=')+1); + if (loop_start >= 0) + loop_flag=1; + } + else if (strstr(comment->user_comments[i],"LOOPLENGTH=")== + comment->user_comments[i]) { + loop_length=atol(strrchr(comment->user_comments[i],'=')+1); + loop_length_found=1; + } + else if (strstr(comment->user_comments[i],"title=-lps")== + comment->user_comments[i]) { + loop_start=atol(comment->user_comments[i]+10); + if (loop_start >= 0) + loop_flag=1; + } + else if (strstr(comment->user_comments[i],"album=-lpe")== + comment->user_comments[i]) { + loop_end=atol(comment->user_comments[i]+10); + loop_flag=1; + loop_end_found=1; + } + else if (strstr(comment->user_comments[i],"LoopEnd=")== + comment->user_comments[i]) { + if(loop_flag) { + loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; + loop_length_found=1; + } + } + else if (strstr(comment->user_comments[i],"LOOP_END=")== + comment->user_comments[i]) { + if(loop_flag) { + loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; + loop_length_found=1; + } + } + else if (strstr(comment->user_comments[i],"lp=")== + comment->user_comments[i]) { + sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", + &loop_start,&loop_end); + loop_flag=1; + loop_end_found=1; + } + else if (strstr(comment->user_comments[i],"COMMENT=loop(")== + comment->user_comments[i]) { + sscanf(strrchr(comment->user_comments[i],'(')+1,"%d,%d", + &loop_start,&loop_end); + loop_flag=1; + loop_end_found=1; + } + } + } + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(info->channels,loop_flag); + if (!vgmstream) goto fail; + + /* store our fun extra datas */ + vgmstream->codec_data = data; + + /* fill in the vital statistics */ + vgmstream->channels = info->channels; + vgmstream->sample_rate = info->rate; + + /* let's play the whole file */ + vgmstream->num_samples = ov_pcm_total(ovf,-1); + + if (loop_flag) { + vgmstream->loop_start_sample = loop_start; + if (loop_length_found) + vgmstream->loop_end_sample = loop_start+loop_length; + else if (loop_end_found) + vgmstream->loop_end_sample = loop_end; + else + vgmstream->loop_end_sample = vgmstream->num_samples; + vgmstream->loop_flag = loop_flag; + + if (vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + } + vgmstream->coding_type = coding_ogg_vorbis; + vgmstream->layout_type = vgm_inf->layout_type; + vgmstream->meta_type = vgm_inf->meta_type; + + return vgmstream; + + /* clean up anything we may have opened */ +fail: + if (data) { + if (inited_ovf) + ov_clear(&data->ogg_vorbis_file); + if (data->ov_streamfile.streamfile) + close_streamfile(data->ov_streamfile.streamfile); + free(data); + } + if (vgmstream) { + vgmstream->codec_data = NULL; + close_vgmstream(vgmstream); + } + return NULL; +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c index da154f3f9..24df33351 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c @@ -1,63 +1,96 @@ #include "meta.h" #include "../util.h" -/* 2PFS - - Mahoromatic: Moetto - KiraKira Maid-San (PS2) +/* 2PFS (Konami) + - Mahoromatic: Moetto - KiraKira Maid-San (PS2) [.2pfs (V1, 2003)] + - GANTZ The Game (PS2) [.sap (V2, 2005)] - + There are two versions of the format, though they use different extensions. + Implemented both versions here in case there are .2pfs with the V2 header out there. + Both loop correctly AFAIK (there is a truncated Mahoromatic rip around, beware). */ VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; - off_t start_offset; + off_t start_offset = 0x800; + int interleave = 0x1000; - int loop_flag = 0; + int loop_flag; int channel_count; + int version; /* v1=1, v2=2 */ + + int loop_start_block; /* block number where the loop starts */ + int loop_end_block; /* usually the last block */ + int loop_start_sample_adjust; /* loops start/end a few samples into the start/end block */ + int loop_end_sample_adjust; + /* check extension, case insensitive */ streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("2pfs",filename_extension(filename))) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x32504653) + if ( strcasecmp("2pfs",filename_extension(filename)) + && strcasecmp("sap",filename_extension(filename)) ) goto fail; - // channel count - channel_count = read_8bit(0x40,streamFile); + /* check header ("2PFS") */ + if (read_32bitBE(0x00,streamFile) != 0x32504653) + goto fail; - // header size - start_offset = 0x800; - - // loop flag - //if ((read_32bitLE(0x38, streamFile) != 0 || - // (read_32bitLE(0x34, streamFile) != 0))) - //{ - // loop_flag = 1; - //} + version = read_16bitLE(0x04,streamFile); + if ( version!=0x01 && version!=0x02 ) + goto fail; - // Loop info unknown right now - //if (loop_flag) - //{ - // vgmstream->loop_start_sample = read_32bitLE(0x38,streamFile)*28/16/channel_count; - // vgmstream->loop_end_sample = read_32bitLE(0x34,streamFile)*28/16/channel_count; - //} - /* build the VGMSTREAM */ + channel_count = read_8bit(0x40,streamFile); + loop_flag = read_8bit(0x41,streamFile); + /* other header values + * 0x06 (4): unknown, v1=0x0004 v2=0x0001 + * 0x08 (32): unique file id + * 0x0c (32): base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size) + * 0x10-0x30: unknown (v1 differs from v2) + * 0x38-0x40: unknown (v1 same as v2) + * 0x4c (32) in V2: unknown, some kind of total samples? + */ + + + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - /* fill in the vital statistics */ - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitLE(0x44,streamFile); + /* fill in the vital statistics */ + vgmstream->channels = channel_count; vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = read_32bitLE(0x0C,streamFile)*28/16/channel_count; - + vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x1000; + vgmstream->interleave_block_size = interleave; vgmstream->meta_type = meta_PS2_2PFS; + if ( version==0x01 ) { + vgmstream->sample_rate = read_32bitLE(0x44,streamFile); + loop_start_sample_adjust = read_16bitLE(0x42,streamFile); + loop_start_block = read_32bitLE(0x48,streamFile); + loop_end_block = read_32bitLE(0x4c,streamFile); + } else { + vgmstream->sample_rate = read_32bitLE(0x48,streamFile); + loop_start_sample_adjust = read_32bitLE(0x44,streamFile); + loop_start_block = read_32bitLE(0x50,streamFile); + loop_end_block = read_32bitLE(0x54,streamFile); + } + loop_end_sample_adjust = interleave; /* loops end after all samples in the end_block AFAIK */ + + if ( loop_flag ) { + /* block to offset > offset to sample + adjust (number of samples into the block) */ + vgmstream->loop_start_sample = ((loop_start_block * channel_count * interleave) + * 28 / 16 / channel_count) + + (loop_start_sample_adjust * 28 / 16); + vgmstream->loop_end_sample = ((loop_end_block * channel_count * interleave) + * 28 / 16 / channel_count) + + (loop_end_sample_adjust * 28 / 16); + } + + + /* open the file for reading */ { int i; @@ -69,11 +102,10 @@ VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) { vgmstream->ch[i].streamfile = file; - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset + (vgmstream->interleave_block_size * i); - + vgmstream->ch[i].channel_start_offset = + vgmstream->ch[i].offset = + start_offset + (vgmstream->interleave_block_size * i); } - } return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index e1f3018e3..bd7829766 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -173,15 +173,17 @@ int read_fmt(int big_endian, fmt->interleave = 8; break; #ifdef VGM_USE_FFMPEG - case 0x270: - case 0xFFFE: + case 0x270: /* ATRAC3 */ +#if defined(VGM_USE_FFMPEG) && !defined(VGM_USE_MAIATRAC3PLUS) + case 0xFFFE: /* WAVEFORMATEXTENSIBLE / ATRAC3plus */ +#endif /* defined */ fmt->coding_type = coding_FFmpeg; fmt->block_size = 2048; fmt->interleave = 0; break; -#endif +#endif /* VGM_USE_FFMPEG */ #ifdef VGM_USE_MAIATRAC3PLUS - case 0xFFFE: /* WAVEFORMATEXTENSIBLE */ + case 0xFFFE: /* WAVEFORMATEXTENSIBLE / ATRAC3plus */ if (read_32bit(current_chunk+0x20,streamFile) == 0xE923AABF && read_16bit(current_chunk+0x24,streamFile) == (int16_t)0xCB58 && read_16bit(current_chunk+0x26,streamFile) == 0x4471 && diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index af9fe6e50..53967b98f 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -359,6 +359,13 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) { if (vgmstream) { /* these are little hacky checks */ + /* fail if there is nothing to play + * (without this check vgmstream can generate empty files) */ + if ( vgmstream->num_samples==0 ) { + close_vgmstream(vgmstream); + continue; + } + /* everything should have a reasonable sample rate * (a verification of the metadata) */ if (!check_sample_rate(vgmstream->sample_rate)) { @@ -1783,6 +1790,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { if (vgmstream->coding_type==coding_FFmpeg) { ffmpeg_codec_data *data = (ffmpeg_codec_data *)(vgmstream->codec_data); int64_t ts; + + /* Seek to loop start by timestamp (closest frame) + adjust skipping some samples */ + /* FFmpeg seeks by ts by design (since not all containers can accurately skip to a frame). */ + /* TODO: this seems to be off by +-1 frames in some cases */ ts = vgmstream->loop_start_sample; if (ts >= data->sampleRate * 2) { data->samplesToDiscard = data->sampleRate * 2; @@ -1794,14 +1805,26 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { } data->framesRead = (int)ts; ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames; + + +#ifdef VGM_USE_FFMPEG_ACCURATE_LOOPING + /* Start from 0 and discard samples until loop_start for accurate looping (slower but not too noticeable) */ + /* We could also seek by offset (AVSEEK_FLAG_BYTE) to the frame closest to the loop then discard + * some samples, which is fast but would need calculations per format / when frame size is not constant */ + data->samplesToDiscard = vgmstream->loop_start_sample; + data->framesRead = 0; + ts = 0; +#endif /* VGM_USE_FFMPEG_ACCURATE_LOOPING */ + avformat_seek_file(data->formatCtx, -1, ts - 1000, ts, ts, AVSEEK_FLAG_ANY); avcodec_flush_buffers(data->codecCtx); + data->readNextPacket = 1; data->bytesConsumedFromDecodedFrame = INT_MAX; data->endOfStream = 0; data->endOfAudio = 0; } -#endif +#endif /* VGM_USE_FFMPEG */ #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) if (vgmstream->coding_type==coding_MP4_AAC) { mp4_aac_codec_data *data = (mp4_aac_codec_data *)(vgmstream->codec_data);