Browse Source

Implement audio probing to reduce audio false positive.

Miku AuahDark 3 years ago
parent
commit
5f386cfa87

+ 5 - 0
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;

+ 1 - 0
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

+ 15 - 0
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)

+ 1 - 0
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;

+ 16 - 0
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<Stream> s(stream->clone(), Acquire::NORETAIN);

+ 1 - 0
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;

+ 60 - 0
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<Stream> s(stream->clone(), Acquire::NORETAIN);

+ 1 - 0
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;

+ 13 - 0
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<Stream> s(stream->clone(), Acquire::NORETAIN);

+ 1 - 0
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;

+ 35 - 3
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<typename DecoderType>
 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<DecoderImpl> possibleDecoders = {
+	std::vector<DecoderImpl> possibleActiveDecoders, possibleDecoders = {
 		DecoderImplFor<MP3Decoder>(),
 		DecoderImplFor<VorbisDecoder>(),
 #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
 		{

+ 13 - 0
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<Stream> s(stream->clone(), Acquire::NORETAIN);

+ 1 - 0
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;

+ 21 - 0
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<Stream> s(stream->clone(), Acquire::NORETAIN);

+ 1 - 0
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;