From 5f386cfa87016f6861efb2d59e56dc397c6a0300 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sun, 1 May 2022 17:01:50 +0800 Subject: [PATCH] Implement audio probing to reduce audio false positive. --- src/modules/sound/Decoder.cpp | 5 ++ src/modules/sound/Decoder.h | 1 + .../sound/lullaby/CoreAudioDecoder.cpp | 15 +++++ src/modules/sound/lullaby/CoreAudioDecoder.h | 1 + src/modules/sound/lullaby/FLACDecoder.cpp | 16 +++++ src/modules/sound/lullaby/FLACDecoder.h | 1 + src/modules/sound/lullaby/MP3Decoder.cpp | 60 +++++++++++++++++++ src/modules/sound/lullaby/MP3Decoder.h | 1 + src/modules/sound/lullaby/ModPlugDecoder.cpp | 13 ++++ src/modules/sound/lullaby/ModPlugDecoder.h | 1 + src/modules/sound/lullaby/Sound.cpp | 38 +++++++++++- src/modules/sound/lullaby/VorbisDecoder.cpp | 13 ++++ src/modules/sound/lullaby/VorbisDecoder.h | 1 + src/modules/sound/lullaby/WaveDecoder.cpp | 21 +++++++ src/modules/sound/lullaby/WaveDecoder.h | 1 + 15 files changed, 185 insertions(+), 3 deletions(-) diff --git a/src/modules/sound/Decoder.cpp b/src/modules/sound/Decoder.cpp index 355b783c9..7c2a71c18 100644 --- a/src/modules/sound/Decoder.cpp +++ b/src/modules/sound/Decoder.cpp @@ -48,6 +48,11 @@ Decoder::~Decoder() delete [](char *) buffer; } +int Decoder::probe(Stream */* stream */) +{ + return 0; +} + void *Decoder::getBuffer() const { return buffer; diff --git a/src/modules/sound/Decoder.h b/src/modules/sound/Decoder.h index 1c91be281..6b6efe8aa 100644 --- a/src/modules/sound/Decoder.h +++ b/src/modules/sound/Decoder.h @@ -51,6 +51,7 @@ public: Decoder(Stream *stream, int bufferSize); virtual ~Decoder(); + static int probe(Stream *stream); /** * Indicates how many bytes of raw data should be generated at each diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.cpp b/src/modules/sound/lullaby/CoreAudioDecoder.cpp index 499ecc43e..6839e2c43 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.cpp +++ b/src/modules/sound/lullaby/CoreAudioDecoder.cpp @@ -118,6 +118,21 @@ CoreAudioDecoder::~CoreAudioDecoder() closeAudioFile(); } +int CoreAudioDecoder::probe(Stream* stream) +{ + AudioFileID audioFile; + + // I think this is sufficient + err = AudioFileOpenWithCallbacks(stream, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile); + if (err == noErr) + { + AudioFileClose(audioFile); + return 80; + } + + return 0; +} + void CoreAudioDecoder::closeAudioFile() { if (extAudioFile != nullptr) diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.h b/src/modules/sound/lullaby/CoreAudioDecoder.h index 9153dd2c4..34ad19372 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.h +++ b/src/modules/sound/lullaby/CoreAudioDecoder.h @@ -49,6 +49,7 @@ public: CoreAudioDecoder(Stream *stream, int bufferSize); virtual ~CoreAudioDecoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override; diff --git a/src/modules/sound/lullaby/FLACDecoder.cpp b/src/modules/sound/lullaby/FLACDecoder.cpp index d45386490..29403de49 100644 --- a/src/modules/sound/lullaby/FLACDecoder.cpp +++ b/src/modules/sound/lullaby/FLACDecoder.cpp @@ -59,6 +59,22 @@ FLACDecoder::~FLACDecoder() drflac_close(flac); } +int FLACDecoder::probe(Stream *stream) +{ + char header[4]; + + if (stream->read(header, 4) >= 4) + { + if (memcmp(header, "fLaC", 4) == 0) + return 100; + else if (memcmp(header, "OggS", 4) == 0) + // Vorbis has higher priority + return 40; + } + + return 0; +} + love::sound::Decoder *FLACDecoder::clone() { StrongRef s(stream->clone(), Acquire::NORETAIN); diff --git a/src/modules/sound/lullaby/FLACDecoder.h b/src/modules/sound/lullaby/FLACDecoder.h index 7e5fcdc30..b0f6c62d5 100644 --- a/src/modules/sound/lullaby/FLACDecoder.h +++ b/src/modules/sound/lullaby/FLACDecoder.h @@ -40,6 +40,7 @@ class FLACDecoder : public Decoder public: FLACDecoder(Stream *stream, int bufferSize); ~FLACDecoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override; diff --git a/src/modules/sound/lullaby/MP3Decoder.cpp b/src/modules/sound/lullaby/MP3Decoder.cpp index 2a1215639..028849bcb 100644 --- a/src/modules/sound/lullaby/MP3Decoder.cpp +++ b/src/modules/sound/lullaby/MP3Decoder.cpp @@ -85,6 +85,66 @@ MP3Decoder::~MP3Decoder() drmp3_uninit(&mp3); } +int MP3Decoder::probe(Stream* stream) +{ + // Header size of ID3v2 + unsigned char header[10]; + + if (stream->read(header, 10) >= 10) + { + if (memcmp(header, "TAG", 3) == 0) + { + // ID3v1 size is 128 bytes. https://id3.org/ID3v1 + stream->seek(128, Stream::SEEKORIGIN_BEGIN); + // We just need 4 bytes actually + if (stream->read(header, 4) < 4) + return 0; + } + else if (memcmp(header, "ID3", 3) == 0) + { + // ID3v2 size is variable + size_t id3Size = + size_t(header[9] & 0x7F) | + (size_t(header[8] & 0x7F) << 7) | + (size_t(header[7] & 0x7F) << 14) | + (size_t(header[6] & 0x7F) << 21); + stream->seek(3 /* "ID3" */ + 2 /* version */ + 1 /* flags */ + 4 /* size */ + id3Size, Stream::SEEKORIGIN_BEGIN); + // We just need 4 bytes actually + if (stream->read(header, 4) < 4) + return 0; + } + } + else + return 0; + + // According to http://www.mp3-tech.org/programmer/frame_header.html + // Check sync bits + if ((header[0] == 0xFF) && (((header[1] >> 5) & 0x7) == 0x7)) + { + if (((header[1] >> 3) & 3) == 1) + // Reserved version + return 0; + if (((header[1] >> 1) & 3) == 0) + // Reserved layer + return 0; + if (((header[2] >> 4) & 0xF) == 0xF) + // Bad bitrate + return 0; + if (((header[2] >> 2) & 0x3) == 0x3) + // Reserved sample rate + return 0; + if ((header[3] & 0x3) == 2) + // Reserved emphasis + return 0; + + // Likely MP3. + return 75; + } + + // Sync bits probably elsewhere + return 1; +} + love::sound::Decoder *MP3Decoder::clone() { StrongRef s(stream->clone(), Acquire::NORETAIN); diff --git a/src/modules/sound/lullaby/MP3Decoder.h b/src/modules/sound/lullaby/MP3Decoder.h index d8589210e..baf84005e 100644 --- a/src/modules/sound/lullaby/MP3Decoder.h +++ b/src/modules/sound/lullaby/MP3Decoder.h @@ -43,6 +43,7 @@ public: MP3Decoder(Stream *stream, int bufsize); virtual ~MP3Decoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override; diff --git a/src/modules/sound/lullaby/ModPlugDecoder.cpp b/src/modules/sound/lullaby/ModPlugDecoder.cpp index 440aa44d8..438a254a4 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.cpp +++ b/src/modules/sound/lullaby/ModPlugDecoder.cpp @@ -95,6 +95,19 @@ ModPlugDecoder::~ModPlugDecoder() ModPlug_Unload(plug); } +int ModPlugDecoder::probe(Stream* stream) +{ + // Ideally we want to probe every single format that ModPlug supports. + Data *data = stream->read(1024 * 1024 * 4); + ModPlugFile *plug = ModPlug_Load(data->getData(), (int)data->getSize()); + + if (plug) + ModPlug_Unload(plug); + + data->release(); + return plug ? 80 : 0; +} + love::sound::Decoder *ModPlugDecoder::clone() { StrongRef s(stream->clone(), Acquire::NORETAIN); diff --git a/src/modules/sound/lullaby/ModPlugDecoder.h b/src/modules/sound/lullaby/ModPlugDecoder.h index b99a2d0e6..59372d77f 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.h +++ b/src/modules/sound/lullaby/ModPlugDecoder.h @@ -49,6 +49,7 @@ public: ModPlugDecoder(Stream *stream, int bufferSize); virtual ~ModPlugDecoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override; diff --git a/src/modules/sound/lullaby/Sound.cpp b/src/modules/sound/lullaby/Sound.cpp index 63fe642d6..66fe1991f 100644 --- a/src/modules/sound/lullaby/Sound.cpp +++ b/src/modules/sound/lullaby/Sound.cpp @@ -38,8 +38,15 @@ struct DecoderImpl { love::sound::Decoder *(*create)(love::Stream *stream, int bufferSize); + int (*probe)(love::Stream *stream); + int probeScore; }; +static bool compareProbeScore(const DecoderImpl& a, const DecoderImpl& b) +{ + return a.probeScore > b.probeScore; +} + template DecoderImpl DecoderImplFor() { @@ -48,6 +55,20 @@ DecoderImpl DecoderImplFor() { return new DecoderType(stream, bufferSize); }; + decoderImpl.probe = [](love::Stream* stream) + { + return DecoderType::probe(stream); + }; + // Short description of probe score: + // Probe score indicates how likely is a file is in certain format. If the + // score is 0 then this particular decoder factory is skipped. Otherwise, + // decoder with highest probe score is used first then decoder with lower + // score. + // There's no standarized value for the probe value (except 0) but if a file + // is "very likely" on certain format, it's safe to return 100. If the file + // is "unlikely" to be certain format but determining such thing requires + // more complicated parsing, it's better to return 1. + decoderImpl.probeScore = 0; return decoderImpl; } @@ -73,7 +94,7 @@ const char *Sound::getName() const sound::Decoder *Sound::newDecoder(Stream *stream, int bufferSize) { - std::vector possibleDecoders = { + std::vector possibleActiveDecoders, possibleDecoders = { DecoderImplFor(), DecoderImplFor(), #ifdef LOVE_SUPPORT_COREAUDIO @@ -86,9 +107,20 @@ sound::Decoder *Sound::newDecoder(Stream *stream, int bufferSize) #endif }; + // Probe decoders + for (DecoderImpl& possibleDecoder : possibleDecoders) + { + stream->seek(0); + possibleDecoder.probeScore = possibleDecoder.probe(stream); + + if (possibleDecoder.probeScore > 0) + possibleActiveDecoders.push_back(possibleDecoder); + } + std::sort(possibleActiveDecoders.begin(), possibleActiveDecoders.end(), compareProbeScore); + + // Load std::stringstream decodingErrors; - decodingErrors << "Failed to determine file type:\n"; - for (DecoderImpl &possibleDecoder : possibleDecoders) + for (DecoderImpl &possibleDecoder : possibleActiveDecoders) { try { diff --git a/src/modules/sound/lullaby/VorbisDecoder.cpp b/src/modules/sound/lullaby/VorbisDecoder.cpp index 6aeac42b9..fbe1f4f28 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.cpp +++ b/src/modules/sound/lullaby/VorbisDecoder.cpp @@ -97,6 +97,19 @@ VorbisDecoder::~VorbisDecoder() ov_clear(&handle); } +int VorbisDecoder::probe(Stream *stream) +{ + char header[4]; + + if (stream->read(header, 4) >= 4) + { + if (memcmp(header, "OggS", 4) == 0) + return 60; + } + + return 1; +} + love::sound::Decoder *VorbisDecoder::clone() { StrongRef s(stream->clone(), Acquire::NORETAIN); diff --git a/src/modules/sound/lullaby/VorbisDecoder.h b/src/modules/sound/lullaby/VorbisDecoder.h index 88f21e489..b1c0f262d 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.h +++ b/src/modules/sound/lullaby/VorbisDecoder.h @@ -44,6 +44,7 @@ public: VorbisDecoder(Stream *stream, int bufferSize); virtual ~VorbisDecoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override; diff --git a/src/modules/sound/lullaby/WaveDecoder.cpp b/src/modules/sound/lullaby/WaveDecoder.cpp index 012e4d42d..1e9cd3085 100644 --- a/src/modules/sound/lullaby/WaveDecoder.cpp +++ b/src/modules/sound/lullaby/WaveDecoder.cpp @@ -91,6 +91,27 @@ WaveDecoder::~WaveDecoder() wuff_close(handle); } +int WaveDecoder::probe(Stream* stream) +{ + char header[8]; + + if (stream->read(header, 4) < 4) + return 0; + if (memcmp(header, "RIFF", 4) != 0) + return 0; + + // Ignore size + stream->seek(4, Stream::SEEKORIGIN_CURRENT); + + if (stream->read(header, 8) < 8) + return 0; + if (memcmp(header, "WAVEfmt ", 8) != 0) + return 0; + + // WAV file + return 100; +} + love::sound::Decoder *WaveDecoder::clone() { StrongRef s(stream->clone(), Acquire::NORETAIN); diff --git a/src/modules/sound/lullaby/WaveDecoder.h b/src/modules/sound/lullaby/WaveDecoder.h index 9f7ddbeb4..9997744f9 100644 --- a/src/modules/sound/lullaby/WaveDecoder.h +++ b/src/modules/sound/lullaby/WaveDecoder.h @@ -40,6 +40,7 @@ public: WaveDecoder(Stream *stream, int bufferSize); virtual ~WaveDecoder(); + static int probe(Stream *stream); love::sound::Decoder *clone() override; int decode() override;