From ff46687597da9f1c1793924c47a4fb9e15a2221c Mon Sep 17 00:00:00 2001 From: Chris Moeller Date: Sat, 16 Jul 2016 23:03:44 -0700 Subject: [PATCH] Updated VGMStream with FFmpeg support, and moved the supported formats over to that plug-in. --- .../vgmstream.xcodeproj/project.pbxproj | 72 +++- .../vgmstream/vgmstream/src/coding/coding.h | 5 + .../vgmstream/src/coding/ffmpeg_decoder.c | 225 ++++++++++++ .../vgmstream/vgmstream/src/layout/nolayout.c | 5 + .../vgmstream/vgmstream/src/meta/ffmpeg.c | 344 ++++++++++++++++++ .../vgmstream/vgmstream/src/meta/meta.h | 11 + .../vgmstream/vgmstream/src/meta/riff.c | 39 +- .../vgmstream/vgmstream/src/meta/sqex_scd.c | 28 +- .../vgmstream/vgmstream/src/vgmstream.c | 121 ++++++ .../vgmstream/vgmstream/src/vgmstream.h | 58 +++ Plugins/FFMPEG/FFMPEGDecoder.m | 15 +- .../vgmstream.xcodeproj/project.pbxproj | 2 + Plugins/vgmstream/vgmstream/VGMDecoder.m | 13 +- 13 files changed, 928 insertions(+), 10 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c diff --git a/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj index b516c5e73..44b19f8ae 100644 --- a/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/vgmstream.xcodeproj/project.pbxproj @@ -328,6 +328,21 @@ 836F705718BDC2190095E648 /* util.h in Headers */ = {isa = PBXBuildFile; fileRef = 836F6F1B18BDC2190095E648 /* util.h */; }; 836F705818BDC2190095E648 /* vgmstream.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6F1C18BDC2190095E648 /* vgmstream.c */; }; 836F705918BDC2190095E648 /* vgmstream.h in Headers */ = {isa = PBXBuildFile; fileRef = 836F6F1D18BDC2190095E648 /* vgmstream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 838BDB641D3AF08C0022CA6F /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB611D3AF08C0022CA6F /* libavcodec.a */; }; + 838BDB651D3AF08C0022CA6F /* libavformat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB621D3AF08C0022CA6F /* libavformat.a */; }; + 838BDB661D3AF08C0022CA6F /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB631D3AF08C0022CA6F /* libavutil.a */; }; + 838BDB681D3AF70D0022CA6F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB671D3AF70D0022CA6F /* libz.tbd */; }; + 838BDB6A1D3AF7140022CA6F /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB691D3AF7140022CA6F /* libiconv.tbd */; }; + 838BDB6C1D3AFAB10022CA6F /* ffmpeg_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 838BDB6B1D3AFAB10022CA6F /* ffmpeg_decoder.c */; }; + 838BDB6E1D3B043C0022CA6F /* ffmpeg.c in Sources */ = {isa = PBXBuildFile; fileRef = 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */; }; + 838BDB711D3B1F990022CA6F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB701D3B1F990022CA6F /* CoreFoundation.framework */; }; + 838BDB731D3B1FA50022CA6F /* VideoDecodeAcceleration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB721D3B1FA50022CA6F /* VideoDecodeAcceleration.framework */; }; + 838BDB751D3B1FAD0022CA6F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB741D3B1FAD0022CA6F /* QuartzCore.framework */; }; + 838BDB771D3B1FB60022CA6F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB761D3B1FB60022CA6F /* Security.framework */; }; + 838BDB791D3B1FBE0022CA6F /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB781D3B1FBE0022CA6F /* VideoToolbox.framework */; }; + 838BDB7B1D3B1FC20022CA6F /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB7A1D3B1FC20022CA6F /* CoreMedia.framework */; }; + 838BDB7D1D3B1FCC0022CA6F /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB7C1D3B1FCC0022CA6F /* CoreVideo.framework */; }; + 838BDB7F1D3B1FD10022CA6F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838BDB7E1D3B1FD10022CA6F /* Cocoa.framework */; }; 83A5F75F198DF021009AF94C /* bfwav.c in Sources */ = {isa = PBXBuildFile; fileRef = 83A5F75E198DF021009AF94C /* bfwav.c */; }; 83BAFB6C19F45EB3005DAB60 /* bfstm.c in Sources */ = {isa = PBXBuildFile; fileRef = 83BAFB6B19F45EB3005DAB60 /* bfstm.c */; }; 83D731101A7394BF00CA1366 /* g7221.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83D730EB1A738EB300CA1366 /* g7221.framework */; }; @@ -770,6 +785,21 @@ 836F6F1B18BDC2190095E648 /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = ""; }; 836F6F1C18BDC2190095E648 /* vgmstream.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vgmstream.c; sourceTree = ""; }; 836F6F1D18BDC2190095E648 /* vgmstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vgmstream.h; sourceTree = ""; }; + 838BDB611D3AF08C0022CA6F /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavcodec.a; path = ../../ThirdParty/ffmpeg/lib/libavcodec.a; sourceTree = ""; }; + 838BDB621D3AF08C0022CA6F /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavformat.a; path = ../../ThirdParty/ffmpeg/lib/libavformat.a; sourceTree = ""; }; + 838BDB631D3AF08C0022CA6F /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = ../../ThirdParty/ffmpeg/lib/libavutil.a; sourceTree = ""; }; + 838BDB671D3AF70D0022CA6F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 838BDB691D3AF7140022CA6F /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + 838BDB6B1D3AFAB10022CA6F /* ffmpeg_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffmpeg_decoder.c; sourceTree = ""; }; + 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffmpeg.c; sourceTree = ""; }; + 838BDB701D3B1F990022CA6F /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 838BDB721D3B1FA50022CA6F /* VideoDecodeAcceleration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoDecodeAcceleration.framework; path = System/Library/Frameworks/VideoDecodeAcceleration.framework; sourceTree = SDKROOT; }; + 838BDB741D3B1FAD0022CA6F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 838BDB761D3B1FB60022CA6F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 838BDB781D3B1FBE0022CA6F /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; + 838BDB7A1D3B1FC20022CA6F /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + 838BDB7C1D3B1FCC0022CA6F /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; + 838BDB7E1D3B1FD10022CA6F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 83A5F75E198DF021009AF94C /* bfwav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bfwav.c; sourceTree = ""; }; 83BAFB6B19F45EB3005DAB60 /* bfstm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bfstm.c; sourceTree = ""; }; 83D730E51A738EB200CA1366 /* g7221.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = g7221.xcodeproj; path = ../g7221/g7221.xcodeproj; sourceTree = ""; }; @@ -785,8 +815,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 838BDB7F1D3B1FD10022CA6F /* Cocoa.framework in Frameworks */, + 838BDB7D1D3B1FCC0022CA6F /* CoreVideo.framework in Frameworks */, + 838BDB7B1D3B1FC20022CA6F /* CoreMedia.framework in Frameworks */, + 838BDB791D3B1FBE0022CA6F /* VideoToolbox.framework in Frameworks */, + 838BDB771D3B1FB60022CA6F /* Security.framework in Frameworks */, + 838BDB751D3B1FAD0022CA6F /* QuartzCore.framework in Frameworks */, + 838BDB731D3B1FA50022CA6F /* VideoDecodeAcceleration.framework in Frameworks */, + 838BDB711D3B1F990022CA6F /* CoreFoundation.framework in Frameworks */, + 838BDB6A1D3AF7140022CA6F /* libiconv.tbd in Frameworks */, + 838BDB681D3AF70D0022CA6F /* libz.tbd in Frameworks */, 83D731891A749D1500CA1366 /* g719.framework in Frameworks */, 83D731101A7394BF00CA1366 /* g7221.framework in Frameworks */, + 838BDB641D3AF08C0022CA6F /* libavcodec.a in Frameworks */, + 838BDB651D3AF08C0022CA6F /* libavformat.a in Frameworks */, + 838BDB661D3AF08C0022CA6F /* libavutil.a in Frameworks */, 8313E3E61902020400B4B6F1 /* mpg123.framework in Frameworks */, 830F885C19C9124C00420FB0 /* Vorbis.framework in Frameworks */, ); @@ -844,6 +887,16 @@ 836F6B3B18BDB8880095E648 /* Frameworks */ = { isa = PBXGroup; children = ( + 838BDB7E1D3B1FD10022CA6F /* Cocoa.framework */, + 838BDB7C1D3B1FCC0022CA6F /* CoreVideo.framework */, + 838BDB7A1D3B1FC20022CA6F /* CoreMedia.framework */, + 838BDB781D3B1FBE0022CA6F /* VideoToolbox.framework */, + 838BDB761D3B1FB60022CA6F /* Security.framework */, + 838BDB741D3B1FAD0022CA6F /* QuartzCore.framework */, + 838BDB721D3B1FA50022CA6F /* VideoDecodeAcceleration.framework */, + 838BDB701D3B1F990022CA6F /* CoreFoundation.framework */, + 838BDB691D3AF7140022CA6F /* libiconv.tbd */, + 838BDB671D3AF70D0022CA6F /* libz.tbd */, 836F6B3E18BDB8880095E648 /* Other Frameworks */, ); name = Frameworks; @@ -852,6 +905,9 @@ 836F6B3E18BDB8880095E648 /* Other Frameworks */ = { isa = PBXGroup; children = ( + 838BDB611D3AF08C0022CA6F /* libavcodec.a */, + 838BDB621D3AF08C0022CA6F /* libavformat.a */, + 838BDB631D3AF08C0022CA6F /* libavutil.a */, 8315231718BDECA1009AE289 /* VorbisNoDot.xcodeproj */, 8313E33D1901FBDC00B4B6F1 /* mpg123.xcodeproj */, 83D730E51A738EB200CA1366 /* g7221.xcodeproj */, @@ -899,6 +955,7 @@ 836F6DDF18BDC2180095E648 /* coding */ = { isa = PBXGroup; children = ( + 838BDB6B1D3AFAB10022CA6F /* ffmpeg_decoder.c */, 832389511D224C0800482226 /* hca_decoder.c */, 83D7318B1A749EEE00CA1366 /* g719_decoder.c */, 836F6DE018BDC2180095E648 /* acm_decoder.c */, @@ -980,6 +1037,7 @@ 836F6E2718BDC2180095E648 /* meta */ = { isa = PBXGroup; children = ( + 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */, 8323894F1D2246C300482226 /* hca.c */, 83EDE5D61A70951A005F5D84 /* mca.c */, 83EDE5D71A70951A005F5D84 /* btsnd.c */, @@ -1605,6 +1663,7 @@ 836F6F4A18BDC2190095E648 /* interleave.c in Sources */, 836F6FFD18BDC2190095E648 /* ps2_stm.c in Sources */, 83EDE5D81A70951A005F5D84 /* mca.c in Sources */, + 838BDB6C1D3AFAB10022CA6F /* ffmpeg_decoder.c in Sources */, 836F6FA518BDC2190095E648 /* nds_rrds.c in Sources */, 836F702F18BDC2190095E648 /* spt_spd.c in Sources */, 836F704618BDC2190095E648 /* x360_tra.c in Sources */, @@ -1697,6 +1756,7 @@ 836F6F4118BDC2190095E648 /* blocked.c in Sources */, 836F6F3B18BDC2190095E648 /* ws_decoder.c in Sources */, 836F6F4518BDC2190095E648 /* emff_blocked.c in Sources */, + 838BDB6E1D3B043C0022CA6F /* ffmpeg.c in Sources */, 836F6F5D18BDC2190095E648 /* xa_blocked.c in Sources */, 836F6F3418BDC2190095E648 /* nwa_decoder.c in Sources */, 836F6F6918BDC2190095E648 /* adx_header.c in Sources */, @@ -1790,8 +1850,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = "$(SRCROOT)/vgmstream/ext_libs"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/vgmstream/ext_libs", + ../../ThirdParty/ffmpeg/include, + ); INSTALL_PATH = "@loader_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ../../ThirdParty/ffmpeg/lib; MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -1830,8 +1894,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = "$(SRCROOT)/vgmstream/ext_libs"; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/vgmstream/ext_libs", + ../../ThirdParty/ffmpeg/include, + ); INSTALL_PATH = "@loader_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ../../ThirdParty/ffmpeg/lib; MACOSX_DEPLOYMENT_TARGET = 10.7; SDKROOT = macosx; SKIP_INSTALL = YES; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 1502d57ea..132a4e95e 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -108,6 +108,11 @@ void decode_at3plus(VGMSTREAM *vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel); #endif +#ifdef VGM_USE_FFMPEG +void decode_ffmpeg(VGMSTREAM *stream, + sample * outbuf, int32_t samples_to_do, int channels); +#endif + void decode_acm(ACMStream * acm, sample * outbuf, int32_t samples_to_do, int channelspacing); diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c new file mode 100644 index 000000000..1e5f6b44f --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -0,0 +1,225 @@ +#include "../vgmstream.h" + +#ifdef VGM_USE_FFMPEG + +static void convert_audio(sample *outbuf, const uint8_t *inbuf, int sampleCount, int bitsPerSample, int floatingPoint) { + int s; + switch (bitsPerSample) { + case 8: + { + for (s = 0; s < sampleCount; ++s) { + *outbuf++ = ((int)(*(inbuf++))-0x80) << 8; + } + } + break; + case 16: + { + int16_t *s16 = (int16_t *)inbuf; + for (s = 0; s < sampleCount; ++s) { + *outbuf++ = *(s16++); + } + } + break; + case 32: + { + if (!floatingPoint) { + int32_t *s32 = (int32_t *)inbuf; + for (s = 0; s < sampleCount; ++s) { + *outbuf++ = (*(s32++)) >> 16; + } + } + else { + float *s32 = (float *)inbuf; + for (s = 0; s < sampleCount; ++s) { + float sample = *s32++; + int s16 = (int)(sample * 32768.0f); + if ((unsigned)(s16 + 0x8000) & 0xFFFF0000) { + s16 = (s16 >> 31) ^ 0x7FFF; + } + *outbuf++ = s16; + } + } + } + break; + case 64: + { + if (floatingPoint) { + double *s64 = (double *)inbuf; + for (s = 0; s < sampleCount; ++s) { + double sample = *s64++; + int s16 = (int)(sample * 32768.0f); + if ((unsigned)(s16 + 0x8000) & 0xFFFF0000) { + s16 = (s16 >> 31) ^ 0x7FFF; + } + *outbuf++ = s16; + } + } + } + break; + } +} + +void decode_ffmpeg(VGMSTREAM *vgmstream, + sample * outbuf, int32_t samples_to_do, int channels) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + + int frameSize; + int dataSize; + + int bytesToRead; + int bytesRead; + + int errcode; + + uint8_t *targetBuf; + + AVFormatContext *formatCtx; + AVCodecContext *codecCtx; + AVPacket *lastReadPacket; + AVFrame *lastDecodedFrame; + + int streamIndex; + + int bytesConsumedFromDecodedFrame; + + int readNextPacket; + int endOfStream; + int endOfAudio; + + int toConsume; + + int framesReadNow; + + if (data->totalFrames && data->framesRead >= data->totalFrames) { + memset(outbuf, 0, samples_to_do * channels * sizeof(sample)); + return; + } + + frameSize = data->channels * (data->bitsPerSample / 8); + dataSize = 0; + + bytesToRead = samples_to_do * frameSize; + bytesRead = 0; + + targetBuf = data->sampleBuffer; + memset(targetBuf, 0, bytesToRead); + + formatCtx = data->formatCtx; + codecCtx = data->codecCtx; + lastReadPacket = data->lastReadPacket; + lastDecodedFrame = data->lastDecodedFrame; + + streamIndex = data->streamIndex; + + bytesConsumedFromDecodedFrame = data->bytesConsumedFromDecodedFrame; + + readNextPacket = data->readNextPacket; + endOfStream = data->endOfStream; + endOfAudio = data->endOfAudio; + + while (bytesRead < bytesToRead) { + int planeSize; + int planar = av_sample_fmt_is_planar(codecCtx->sample_fmt); + dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, + lastDecodedFrame->nb_samples, + codecCtx->sample_fmt, 1); + + if (dataSize < 0) + dataSize = 0; + + while (readNextPacket && !endOfAudio) { + if (!endOfStream) { + av_packet_unref(lastReadPacket); + if ((errcode = av_read_frame(formatCtx, lastReadPacket)) < 0) { + if (errcode == AVERROR_EOF) { + endOfStream = 1; + } + if (formatCtx->pb && formatCtx->pb->error) + break; + } + if (lastReadPacket->stream_index != streamIndex) + continue; + } + + if ((errcode = avcodec_send_packet(codecCtx, endOfStream ? NULL : lastReadPacket)) < 0) { + if (errcode != AVERROR(EAGAIN)) { + goto end; + } + } + + readNextPacket = 0; + } + + if (dataSize <= bytesConsumedFromDecodedFrame) { + if (endOfStream && endOfAudio) + break; + + bytesConsumedFromDecodedFrame = 0; + + if ((errcode = avcodec_receive_frame(codecCtx, lastDecodedFrame)) < 0) { + if (errcode == AVERROR_EOF) { + endOfAudio = 1; + break; + } + else if (errcode == AVERROR(EAGAIN)) { + readNextPacket = 1; + continue; + } + else { + goto end; + } + } + + dataSize = av_samples_get_buffer_size(&planeSize, codecCtx->channels, lastDecodedFrame->nb_samples, codecCtx->sample_fmt, 1); + + if (dataSize < 0) + dataSize = 0; + } + + toConsume = FFMIN((dataSize - bytesConsumedFromDecodedFrame), (bytesToRead - bytesRead)); + + if (!planar || channels == 1) { + memmove(targetBuf + bytesRead, (lastDecodedFrame->data[0] + bytesConsumedFromDecodedFrame), toConsume); + } + else { + uint8_t * out = (uint8_t *) targetBuf + bytesRead; + int bytesPerSample = data->bitsPerSample / 8; + int bytesConsumedPerPlane = bytesConsumedFromDecodedFrame / channels; + int toConsumePerPlane = toConsume / channels; + int s, ch; + for (s = 0; s < toConsumePerPlane; s += bytesPerSample) { + for (ch = 0; ch < channels; ++ch) { + memcpy(out, lastDecodedFrame->extended_data[ch] + bytesConsumedPerPlane + s, bytesPerSample); + out += bytesPerSample; + } + } + } + + bytesConsumedFromDecodedFrame += toConsume; + bytesRead += toConsume; + } + +end: + framesReadNow = bytesRead / frameSize; + if (data->totalFrames && (data->framesRead + framesReadNow > data->totalFrames)) { + framesReadNow = (int)(data->totalFrames - data->framesRead); + } + + data->framesRead += framesReadNow; + + // Convert the audio + convert_audio(outbuf, data->sampleBuffer, framesReadNow * channels, data->bitsPerSample, data->floatingPoint); + + // Output the state back to the structure + data->bytesConsumedFromDecodedFrame = bytesConsumedFromDecodedFrame; + data->readNextPacket = readNextPacket; + data->endOfStream = endOfStream; + data->endOfAudio = endOfAudio; + + // And pad if we ran short (end of file) + if (framesReadNow < samples_to_do) { + memset(outbuf + framesReadNow * channels, 0, sizeof(sample) * channels * (samples_to_do - framesReadNow)); + } +} + +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/layout/nolayout.c b/Frameworks/vgmstream/vgmstream/src/layout/nolayout.c index dd36976fb..f481a034e 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/nolayout.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/nolayout.c @@ -18,6 +18,11 @@ void render_vgmstream_nolayout(sample * buffer, int32_t sample_count, VGMSTREAM if (samples_written+samples_to_do > sample_count) samples_to_do=sample_count-samples_written; + + if (!samples_to_do) { + memset(buffer + samples_written * vgmstream->channels, 0, sizeof(sample) * vgmstream->channels * (sample_count - samples_written)); + return; + } decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c new file mode 100644 index 000000000..ec509ac32 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c @@ -0,0 +1,344 @@ +#include "../vgmstream.h" +#include "meta.h" +#include "../util.h" + +#ifdef VGM_USE_FFMPEG + +static volatile int g_ffmpeg_initialized = 0; + +static void g_init_ffmpeg() +{ + if (g_ffmpeg_initialized == 1) + { + while (g_ffmpeg_initialized < 2); + } + else if (g_ffmpeg_initialized == 0) + { + g_ffmpeg_initialized = 1; + av_log_set_flags(AV_LOG_SKIP_REPEATED); + av_log_set_level(AV_LOG_ERROR); + av_register_all(); + g_ffmpeg_initialized = 2; + } +} + +ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t stream_offset, uint64_t stream_size, int fmt_big_endian); +ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { + return init_ffmpeg_faux_riff(streamFile, -1, start, size, 0); +} + +VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); + +VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) { + return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) ); +} + +void free_ffmpeg(ffmpeg_codec_data *data); + +VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { + VGMSTREAM *vgmstream = NULL; + + ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size); + + if (!data) return NULL; + + vgmstream = allocate_vgmstream(data->channels, 0); + if (!vgmstream) goto fail; + + vgmstream->loop_flag = 0; + vgmstream->codec_data = data; + vgmstream->channels = data->channels; + vgmstream->sample_rate = data->sampleRate; + vgmstream->num_samples = data->totalFrames; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_FFmpeg; + + return vgmstream; + +fail: + free_ffmpeg(data); + + return NULL; +} + +static int ffmpeg_read(void *opaque, uint8_t *buf, int buf_size) +{ + ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque; + uint64_t offset = data->offset; + int max_to_copy; + int ret; + if (data->header_insert_block) { + max_to_copy = 0; + if (offset < data->header_size) { + max_to_copy = (int)(data->header_size - offset); + if (max_to_copy > buf_size) { + max_to_copy = buf_size; + } + memcpy(buf, data->header_insert_block + offset, max_to_copy); + buf += max_to_copy; + buf_size -= max_to_copy; + offset += max_to_copy; + if (!buf_size) { + data->offset = offset; + return max_to_copy; + } + } + offset -= data->header_size; + } + ret = read_streamfile(buf, offset + data->start, buf_size, data->streamfile); + if (ret > 0) { + offset += ret; + if (data->header_insert_block) { + ret += max_to_copy; + } + } + if (data->header_insert_block) { + offset += data->header_size; + } + data->offset = offset; + return ret; +} + +static int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size) +{ + return -1; +} + +static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) +{ + ffmpeg_codec_data *data = (ffmpeg_codec_data *) opaque; + if (whence & AVSEEK_SIZE) { + return data->size + data->header_size; + } + whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE); + switch (whence) { + case SEEK_CUR: + offset += data->offset; + break; + + case SEEK_END: + offset += data->size; + if (data->header_insert_block) + offset += data->header_size; + break; + } + if (offset > data->size + data->header_size) + offset = data->size + data->header_size; + return data->offset = offset; +} + +ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t start, uint64_t size, int big_endian) { + char filename[PATH_LIMIT]; + + ffmpeg_codec_data * data; + + int errcode, i; + + int streamIndex; + AVCodecParameters *codecPar; + + AVRational tb; + + g_init_ffmpeg(); + + data = ( ffmpeg_codec_data * ) calloc(1, sizeof(ffmpeg_codec_data)); + + if (!data) return 0; + + streamFile->get_name( streamFile, filename, sizeof(filename) ); + + data->streamfile = streamFile->open(streamFile, filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!data->streamfile) goto fail; + + data->start = start; + data->size = size; + + if (fmt_offset > 0) { + int max_header_size = (int)(start - fmt_offset); + uint8_t *p; + if (max_header_size < 18) goto fail; + data->header_insert_block = p = av_malloc(max_header_size + 8 + 4 + 8 + 8); + if (!data->header_insert_block) goto fail; + if (read_streamfile(p + 8 + 4 + 8, fmt_offset, max_header_size, streamFile) != max_header_size) goto fail; + if (big_endian) { + p += 8 + 4 + 8; + put_16bitLE(p, get_16bitBE(p)); + put_16bitLE(p + 2, get_16bitBE(p + 2)); + put_32bitLE(p + 4, get_32bitBE(p + 4)); + put_32bitLE(p + 8, get_32bitBE(p + 8)); + put_16bitLE(p + 12, get_16bitBE(p + 12)); + put_16bitLE(p + 14, get_16bitBE(p + 14)); + put_16bitLE(p + 16, get_16bitBE(p + 16)); + p -= 8 + 4 + 8; + } + data->header_size = 8 + 4 + 8 + 8 + 18 + get_16bitLE(p + 8 + 4 + 8 + 16); + // Meh, dunno how to handle swapping the extra data + // FFmpeg doesn't need most of this data anyway + if ((unsigned)(get_16bitLE(p + 8 + 4 + 8) - 0x165) < 2) + memset(p + 8 + 4 + 8 + 18, 0, 34); + + // Fill out the RIFF structure + memcpy(p, "RIFF", 4); + put_32bitLE(p + 4, data->header_size + size - 8); + memcpy(p + 8, "WAVE", 4); + memcpy(p + 12, "fmt ", 4); + put_32bitLE(p + 16, 18 + get_16bitLE(p + 8 + 4 + 8 + 16)); + memcpy(p + data->header_size - 8, "data", 4); + put_32bitLE(p + data->header_size - 4, size); + } + + data->buffer = av_malloc(128 * 1024); + if (!data->buffer) goto fail; + + data->ioCtx = avio_alloc_context(data->buffer, 128 * 1024, 0, data, ffmpeg_read, ffmpeg_write, ffmpeg_seek); + if (!data->ioCtx) goto fail; + + data->formatCtx = avformat_alloc_context(); + if (!data->formatCtx) goto fail; + + data->formatCtx->pb = data->ioCtx; + + if ((errcode = avformat_open_input(&data->formatCtx, "", NULL, NULL)) < 0) goto fail; + + if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail; + + streamIndex = -1; + + for (i = 0; i < data->formatCtx->nb_streams; ++i) { + codecPar = data->formatCtx->streams[i]->codecpar; + if (codecPar->codec_type == AVMEDIA_TYPE_AUDIO) { + streamIndex = i; + break; + } + } + + if (streamIndex < 0) goto fail; + + data->streamIndex = streamIndex; + + data->codecCtx = avcodec_alloc_context3(NULL); + if (!data->codecCtx) goto fail; + + if ((errcode = avcodec_parameters_to_context(data->codecCtx, codecPar)) < 0) goto fail; + + av_codec_set_pkt_timebase(data->codecCtx, data->formatCtx->streams[streamIndex]->time_base); + + data->codec = avcodec_find_decoder(data->codecCtx->codec_id); + if (!data->codec) goto fail; + + if ((errcode = avcodec_open2(data->codecCtx, data->codec, NULL)) < 0) goto fail; + + data->lastDecodedFrame = av_frame_alloc(); + if (!data->lastDecodedFrame) goto fail; + av_frame_unref(data->lastDecodedFrame); + data->lastReadPacket = malloc(sizeof(AVPacket)); + if (!data->lastReadPacket) goto fail; + av_new_packet(data->lastReadPacket, 0); + data->readNextPacket = 1; + data->bytesConsumedFromDecodedFrame = INT_MAX; + + data->sampleRate = data->codecCtx->sample_rate; + data->channels = data->codecCtx->channels; + data->floatingPoint = 0; + + switch (data->codecCtx->sample_fmt) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + data->bitsPerSample = 8; + break; + + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + data->bitsPerSample = 16; + break; + + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + data->bitsPerSample = 32; + break; + + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + data->bitsPerSample = 32; + data->floatingPoint = 1; + break; + + case AV_SAMPLE_FMT_DBL: + case AV_SAMPLE_FMT_DBLP: + data->bitsPerSample = 64; + data->floatingPoint = 1; + break; + + default: + goto fail; + } + + tb.num = 1; tb.den = data->codecCtx->sample_rate; + + data->totalFrames = av_rescale_q(data->formatCtx->streams[streamIndex]->duration, data->formatCtx->streams[streamIndex]->time_base, tb); + data->bitrate = (int)((data->codecCtx->bit_rate) / 1000); + data->framesRead = 0; + data->endOfStream = 0; + data->endOfAudio = 0; + + if (data->totalFrames < 0) + data->totalFrames = 0; + + data->sampleBuffer = av_malloc( 2048 * (data->bitsPerSample / 8) * data->channels ); + if (!data->sampleBuffer) + goto fail; + + return data; + +fail: + free_ffmpeg(data); + + return NULL; +} + +void free_ffmpeg(ffmpeg_codec_data *data) { + if (data->lastReadPacket) { + av_packet_unref(data->lastReadPacket); + free(data->lastReadPacket); + data->lastReadPacket = NULL; + } + if (data->lastDecodedFrame) { + av_free(data->lastDecodedFrame); + data->lastDecodedFrame = NULL; + } + if (data->codecCtx) { + avcodec_close(data->codecCtx); + avcodec_free_context(&(data->codecCtx)); + data->codecCtx = NULL; + } + if (data->formatCtx) { + avformat_close_input(&(data->formatCtx)); + data->formatCtx = NULL; + } + if (data->ioCtx) { + // buffer passed in is occasionally freed and replaced. + // the replacement must be freed as well. + data->buffer = data->ioCtx->buffer; + av_free(data->ioCtx); + data->ioCtx = NULL; + } + if (data->buffer) { + av_free(data->buffer); + data->buffer = NULL; + } + if (data->sampleBuffer) { + av_free(data->sampleBuffer); + data->sampleBuffer = NULL; + } + if (data->header_insert_block) { + av_free(data->header_insert_block); + data->header_insert_block = NULL; + } + if (data->streamfile) { + close_streamfile(data->streamfile); + data->streamfile = NULL; + } + free(data); +} +#endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 34f8fd99b..5fa0338f1 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -115,6 +115,17 @@ VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_hca_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); +#ifdef VGM_USE_FFMPEG +ffmpeg_codec_data * init_ffmpeg_faux_riff(STREAMFILE *streamFile, int64_t fmt_offset, uint64_t stream_offset, uint64_t stream_size, int fmt_big_endian); +ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); + +void free_ffmpeg(ffmpeg_codec_data *); + +VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); + +VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile); +#endif + #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE * streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 2b85d589b..e9deee105 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -172,6 +172,14 @@ int read_fmt(int big_endian, fmt->coding_type = coding_NGC_DSP; fmt->interleave = 8; break; +#ifdef VGM_USE_FFMPEG + case 0x270: + case 0xFFFE: + fmt->coding_type = coding_FFmpeg; + fmt->block_size = 2048; + fmt->interleave = 0; + break; +#endif #ifdef VGM_USE_MAIATRAC3PLUS case 0xFFFE: /* WAVEFORMATEXTENSIBLE */ if (read_32bit(current_chunk+0x20,streamFile) == 0xE923AABF && @@ -203,6 +211,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { struct riff_fmt_chunk fmt; +#ifdef VGM_USE_FFMPEG + ffmpeg_codec_data *ffmpeg_data = NULL; +#endif + off_t file_size = -1; int sample_count = 0; int fact_sample_count = -1; @@ -231,7 +243,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { streamFile->get_name(streamFile,filename,sizeof(filename)); if (strcasecmp("wav",filename_extension(filename)) && strcasecmp("lwav",filename_extension(filename)) -#ifdef VGM_USE_MAIATRAC3PLUS +#if defined(VGM_USE_MAIATRAC3PLUS) || defined(VGM_USE_FFMPEG) && strcasecmp("at3",filename_extension(filename)) #endif ) @@ -367,6 +379,16 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { break; case coding_NGC_DSP: break; +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: + { + ffmpeg_data = init_ffmpeg_offset(streamFile, 0, streamFile->get_size(streamFile) ); + if ( !ffmpeg_data ) goto fail; + + sample_count = ffmpeg_data->totalFrames; + } + break; +#endif #ifdef VGM_USE_MAIATRAC3PLUS case coding_AT3plus: sample_count = (data_size / fmt.block_size) * 2048 * fmt.channel_count; @@ -400,6 +422,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { case coding_PCM8_U_int: case coding_MS_IMA: case coding_MSADPCM: +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: +#endif #ifdef VGM_USE_MAIATRAC3PLUS case coding_AT3plus: #endif @@ -415,6 +440,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { switch (fmt.coding_type) { case coding_MSADPCM: case coding_MS_IMA: +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: +#endif #ifdef VGM_USE_MAIATRAC3PLUS case coding_AT3plus: #endif @@ -426,6 +454,12 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { break; } +#ifdef VGM_USE_FFMPEG + if (fmt.coding_type == coding_FFmpeg) { + vgmstream->codec_data = ffmpeg_data; + } +#endif + #ifdef VGM_USE_MAIATRAC3PLUS if (fmt.coding_type == coding_AT3plus) { maiatrac3plus_codec_data *data = malloc(sizeof(maiatrac3plus_codec_data)); @@ -527,6 +561,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { /* clean up anything we may have opened */ fail: +#ifdef VGM_USE_FFMPEG + if (ffmpeg_data) free_ffmpeg(ffmpeg_data); +#endif if (vgmstream) close_vgmstream(vgmstream); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c index 525870ac7..2eca1b1bf 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd.c @@ -280,6 +280,32 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { } break; +#ifdef VGM_USE_FFMPEG + case 0xB: + /* XMA1/XMA2 */ + { + uint16_t codec_id = read_16bit(post_meta_offset, streamFile); + if (codec_id == 0x165 || codec_id == 0x166) + { + ffmpeg_codec_data *ffmpeg_data = init_ffmpeg_faux_riff(streamFile, post_meta_offset, start_offset, streamFile->get_size(streamFile) - start_offset, read_32bit == read_32bitBE); + if (!ffmpeg_data) goto fail; + + vgmstream->codec_data = ffmpeg_data; + + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = ffmpeg_data->totalFrames; + + if (loop_flag) { + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + } + } + else goto fail; + } + break; +#endif default: goto fail; } @@ -287,7 +313,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { vgmstream->meta_type = meta_SQEX_SCD; /* open the file for reading */ - if (vgmstream->layout_type != layout_scd_int) + if (vgmstream->layout_type != layout_scd_int && vgmstream->coding_type != coding_FFmpeg) { int i; STREAMFILE * file; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 6896dcf55..972130324 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -339,6 +339,9 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = { init_vgmstream_3ds_idsp, init_vgmstream_g1l, init_vgmstream_hca, +#ifdef VGM_USE_FFMPEG + init_vgmstream_ffmpeg, +#endif }; #define INIT_VGMSTREAM_FCNS (sizeof(init_vgmstream_fcns)/sizeof(init_vgmstream_fcns[0])) @@ -362,6 +365,13 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) { close_vgmstream(vgmstream); continue; } + + /* Sanify loops! */ + if (vgmstream->loop_flag) { + if ((vgmstream->loop_end_sample <= vgmstream->loop_start_sample) || + (vgmstream->loop_end_sample > vgmstream->num_samples)) + vgmstream->loop_flag = 0; + } /* dual file stereo */ if (do_dfs && ( @@ -494,6 +504,24 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { data->samples_discard = 0; } #endif + +#ifdef VGM_USE_FFMPEG + if (vgmstream->coding_type==coding_FFmpeg) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + + if (data->formatCtx) { + avformat_seek_file(data->formatCtx, -1, 0, 0, 0, AVSEEK_FLAG_ANY); + } + if (data->codecCtx) { + avcodec_flush_buffers(data->codecCtx); + } + data->readNextPacket = 1; + data->bytesConsumedFromDecodedFrame = INT_MAX; + data->framesRead = 0; + data->endOfStream = 0; + data->endOfAudio = 0; + } +#endif if (vgmstream->coding_type==coding_ACM) { mus_acm_codec_data *data = vgmstream->codec_data; @@ -642,6 +670,16 @@ void close_vgmstream(VGMSTREAM * vgmstream) { vgmstream->codec_data = NULL; } } + +#ifdef VGM_USE_FFMPEG + if (vgmstream->coding_type==coding_FFmpeg) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + if (vgmstream->codec_data) { + free_ffmpeg(data); + vgmstream->codec_data = NULL; + } + } +#endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) if (vgmstream->coding_type==coding_MP4_AAC) { @@ -1032,6 +1070,18 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { #ifdef VGM_USE_G719 case coding_G719: return 48000/50; +#endif +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: + { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + if (vgmstream->codec_data) { + int64_t samplesRemain = data->totalFrames - data->framesRead; + return samplesRemain > 2048 ? 2048 : samplesRemain; + } + return 0; + } + break; #endif case coding_LSF: return 54; @@ -1147,6 +1197,9 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { #endif #ifdef VGM_USE_MAIATRAC3PLUS case coding_AT3plus: +#endif +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: #endif case coding_MSADPCM: case coding_MTAF: @@ -1425,6 +1478,14 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to buffer+samples_written*vgmstream->channels,samples_to_do, vgmstream->channels); break; +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: + decode_ffmpeg(vgmstream, + buffer+samples_written*vgmstream->channels, + samples_to_do, + vgmstream->channels); + break; +#endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) case coding_MP4_AAC: decode_mp4_aac(vgmstream->codec_data, @@ -1717,6 +1778,20 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { data->sample_ptr = clHCA_samplesPerBlock; data->samples_discard = 0; } +#ifdef VGM_USE_FFMPEG + if (vgmstream->coding_type==coding_FFmpeg) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *)(vgmstream->codec_data); + int64_t ts; + data->framesRead = vgmstream->loop_start_sample; + ts = data->framesRead * (data->formatCtx->duration) / data->totalFrames; + 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 #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); @@ -2012,6 +2087,25 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { snprintf(temp,TEMPSIZE,"ATRAC3plus"); break; #endif +#ifdef VGM_USE_FFMPEG + case coding_FFmpeg: + { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + if (vgmstream->codec_data) { + if (data->codec) { + snprintf(temp,TEMPSIZE,data->codec->long_name); + } + else { + snprintf(temp,TEMPSIZE,"FFmpeg"); + } + } + else { + snprintf(temp,TEMPSIZE,"FFmpeg"); + } + } + break; +#endif + case coding_ACM: snprintf(temp,TEMPSIZE,"InterPlay ACM"); break; @@ -3183,6 +3277,14 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { case meta_XB3D_ADX: snprintf(temp, TEMPSIZE,"Xenoblade 3D ADX Header"); break; + case meta_HCA: + snprintf(temp, TEMPSIZE,"CRI MiddleWare HCA Header"); + break; +#ifdef VGM_USE_FFMPEG + case meta_FFmpeg: + snprintf(temp, TEMPSIZE,"FFmpeg supported file format"); + break; +#endif default: snprintf(temp,TEMPSIZE,"THEY SHOULD HAVE SENT A POET"); } @@ -3376,6 +3478,18 @@ static int get_vgmstream_channel_count(VGMSTREAM * vgmstream) return 0; } } +#ifdef VGM_USE_FFMPEG + if (vgmstream->coding_type==coding_FFmpeg) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + + if (data) { + return 1; + } + else { + return 0; + } + } +#endif #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; @@ -3408,6 +3522,13 @@ static STREAMFILE * get_vgmstream_streamfile(VGMSTREAM * vgmstream, int channel) return data->streamfile; } +#ifdef VGM_USE_FFMPEG + if (vgmstream->coding_type==coding_FFmpeg) { + ffmpeg_codec_data *data = (ffmpeg_codec_data *) vgmstream->codec_data; + + return data->streamfile; + } +#endif #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; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index c0db60c44..0742b9f0f 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -17,6 +17,7 @@ enum { PATH_LIMIT = 32768 }; /* disabled by default, defined for builds that support it */ #define VGM_USE_G7221 #define VGM_USE_G719 +#define VGM_USE_FFMPEG #include "streamfile.h" #ifdef BUILD_VGMSTREAM @@ -55,6 +56,11 @@ enum { PATH_LIMIT = 32768 }; #include "clHCA.h" +#ifdef VGM_USE_FFMPEG +#include +#include +#endif + #ifdef BUILD_VGMSTREAM #include "coding/acm_decoder.h" #include "coding/nwa_decoder.h" @@ -165,6 +171,10 @@ typedef enum { coding_MTAF, /* Konami IMA-derived MTAF ADPCM */ coding_CRI_HCA, /* CRI High Compression Audio */ + +#ifdef VGM_USE_FFMPEG + coding_FFmpeg, +#endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) coding_MP4_AAC, @@ -598,6 +608,9 @@ typedef enum { meta_MCA, // Capcom MCA "MADP" meta_XB3D_ADX, // Xenoblade Chronicles 3D ADX meta_HCA, +#ifdef VGM_USE_FFMPEG + meta_FFmpeg, +#endif #ifdef VGM_USE_MP4V2 meta_MP4, #endif @@ -842,6 +855,51 @@ typedef struct { // clHCA exists here } hca_codec_data; +#ifdef VGM_USE_FFMPEG +typedef struct { + STREAMFILE *streamfile; + + // offset and total size of raw stream data + uint64_t start; + uint64_t size; + + // offset into stream, includes header_size if header exists + uint64_t offset; + + // inserted header, ie. fake RIFF header + uint8_t *header_insert_block; + uint64_t header_size; + + // stream info + int channels; + int bitsPerSample; + int floatingPoint; + int sampleRate; + int64_t totalFrames; + int64_t framesRead; + int bitrate; + + // Intermediate buffer + uint8_t *sampleBuffer; + + // FFmpeg context used for metadata + AVCodec *codec; + + // FFmpeg decoder state + unsigned char *buffer; + AVIOContext *ioCtx; + int streamIndex; + AVFormatContext *formatCtx; + AVCodecContext *codecCtx; + AVFrame *lastDecodedFrame; + AVPacket *lastReadPacket; + int bytesConsumedFromDecodedFrame; + int readNextPacket; + int endOfStream; + int endOfAudio; +} ffmpeg_codec_data; +#endif + #ifdef VGM_USE_MP4V2 typedef struct { STREAMFILE *streamfile; diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index 398d998ba..836d13d9b 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -29,6 +29,19 @@ int ffmpeg_write(void *opaque, uint8_t *buf, int buf_size) int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) { id source = (__bridge id) opaque; + if (whence & AVSEEK_SIZE) + { + if ([source seekable]) + { + int64_t curOffset = [source tell]; + [source seek:0 whence:SEEK_END]; + int64_t size = [source tell]; + [source seek:curOffset whence:SEEK_SET]; + return size; + } + return -1; + } + whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE); return [source seekable] ? ([source seek:offset whence:whence] ? [source tell] : -1) : -1; } @@ -444,7 +457,7 @@ int lockmgr_callback(void ** mutex, enum AVLockOp op) + (NSArray *)fileTypes { - return [NSArray arrayWithObjects:@"wma", @"asf", @"xwma", @"xma", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", @"ape", @"ac3", @"dts", @"dtshd", @"at3", @"wav", @"tta", @"vqf", @"vqe", @"vql", nil]; + return [NSArray arrayWithObjects:@"wma", @"asf", @"tak", @"mp3", @"mp2", @"m2a", @"mpa", @"ape", @"ac3", @"dts", @"dtshd", @"wav", @"tta", @"vqf", @"vqe", @"vql", nil]; } + (NSArray *)mimeTypes diff --git a/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj b/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj index caec708e4..25956447b 100644 --- a/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj +++ b/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj @@ -320,6 +320,7 @@ GCC_PREFIX_HEADER = "vgmstream/vgmstream-Prefix.pch"; HEADER_SEARCH_PATHS = ( "$(inherited)", + ../../ThirdParty/ffmpeg/include, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ../../Frameworks/Vorbis/include, ../../Frameworks/Ogg/include, @@ -343,6 +344,7 @@ GCC_PREFIX_HEADER = "vgmstream/vgmstream-Prefix.pch"; HEADER_SEARCH_PATHS = ( "$(inherited)", + ../../ThirdParty/ffmpeg/include, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, ../../Frameworks/Vorbis/include, ../../Frameworks/Ogg/include, diff --git a/Plugins/vgmstream/vgmstream/VGMDecoder.m b/Plugins/vgmstream/vgmstream/VGMDecoder.m index f054f8875..49b578c9c 100644 --- a/Plugins/vgmstream/vgmstream/VGMDecoder.m +++ b/Plugins/vgmstream/vgmstream/VGMDecoder.m @@ -130,7 +130,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) { sampleRate = stream->sample_rate; channels = stream->channels; totalFrames = get_vgmstream_play_samples( 2.0, 10.0, 10.0, stream ); - framesFade = sampleRate * 10; + framesFade = stream->loop_flag ? sampleRate * 10 : 0; framesLength = totalFrames - framesFade; framesRead = 0; @@ -161,14 +161,17 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) { { BOOL repeatone = IsRepeatOneSet(); - if (!repeatone && framesRead >= totalFrames) - return 0; + if (!repeatone) { + if (framesRead >= totalFrames) return 0; + else if (framesRead + frames > totalFrames) + frames = totalFrames - framesRead; + } sample * sbuf = (sample *) buf; render_vgmstream( sbuf, frames, stream ); - if ( !repeatone && framesRead + frames > framesLength ) { + if ( !repeatone && framesFade && framesRead + frames > framesLength ) { long fadeStart = (framesLength > framesRead) ? framesLength : framesRead; long fadeEnd = (framesRead + frames) > totalFrames ? totalFrames : (framesRead + frames); long fadePos; @@ -231,7 +234,7 @@ VGMSTREAM *init_vgmstream_from_cogfile(const char *path) { + (NSArray *)fileTypes { - return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"ydsp", @"ymf", @"zsd", @"zwdsp", nil]; + return [NSArray arrayWithObjects:@"2dx9", @"aaap", @"aax", @"acm", @"adp", @"adpcm", @"ads", @"adx", @"afc", @"agsc", @"ahx",@"aifc", @"aiff", @"aix", @"amts", @"as4", @"asd", @"asf", @"asr", @"ass", @"ast", @"at3", @"aud", @"aus", @"baf", @"baka", @"bar", @"bcstm", @"bcwav", @"bfstm", @"bfwav", @"bfwavnsmbu", @"bg00", @"bgw", @"bh2pcm", @"bmdx", @"bns", @"bnsf", @"bo2", @"brstm", @"caf", @"capdsp", @"ccc", @"cfn", @"cnk", @"dcs", @"dcsw", @"ddsp", @"de2", @"dmsg", @"dsp", @"dvi", @"dxh", @"eam", @"emff", @"enth", @"fag", @"filp", @"fsb", @"fwav", @"gca", @"gcm", @"gcsw", @"gcw", @"genh", @"gms", @"gsp", @"hca", @"hgc1", @"his", @"hps", @"hwas", @"idsp", @"idvi", @"ikm", @"ild", @"int", @"isd", @"ish", @"ivaud", @"ivb", @"joe", @"kces", @"kcey", @"khv", @"kraw", @"leg", @"logg", @"lps", @"lsf", @"lwav", @"matx", @"mcg", @"mi4", @"mib", @"mic", @"mihb", @"mpdsp", @"msa", @"mss", @"msvp", @"mus", @"musc", @"musx", @"mwv", @"myspd", @"ndp", @"npsf", @"nus3bank", @"nwa", @"omu", @"otm", @"p3d", @"pcm", @"pdt", @"pnb", @"pos", @"psh", @"psw", @"raw", @"rkv", @"rnd", @"rrds", @"rsd", @"rsf", @"rstm", @"rwar", @"rwav", @"rws", @"rwsd", @"rwx", @"rxw", @"s14", @"sab", @"sad", @"sap", @"sc", @"scd", @"sd9", @"sdt", @"seg", @"sfl", @"sfs", @"sl3", @"sli", @"smp", @"smpl", @"snd", @"sng", @"sns", @"spd", @"sps", @"spsd", @"spt", @"spw", @"ss2", @"ss7", @"ssm", @"sss", @"ster", @"sth", @"stm", @"stma", @"str", @"strm", @"sts", @"stx", @"svag", @"svs", @"swav", @"swd", @"tec", @"thp", @"tk5", @"tydsp", @"um3", @"vag", @"vas", @"vgs", @"vig", @"vjdsp", @"voi", @"vpk", @"vs", @"vsf", @"waa", @"wac", @"wad", @"wam", @"was", @"wavm", @"wb", @"wii", @"wp2", @"wsd", @"wsi", @"wvs", @"xa", @"xa2", @"xa30", @"xma", @"xmu", @"xss", @"xvas", @"xwav", @"xwb", @"xwma", @"ydsp", @"ymf", @"zsd", @"zwdsp", nil]; } + (NSArray *)mimeTypes