Improve MP3 detection to reduce false positive.

This commit is contained in:
Miku AuahDark
2022-05-02 09:20:04 +08:00
parent c19d7cab1e
commit c9ab08a2d3
2 changed files with 110 additions and 11 deletions
+106 -11
View File
@@ -32,23 +32,119 @@ namespace lullaby
static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead) static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead)
{ {
auto stream = (Stream *) pUserData; auto decoder = (MP3Decoder *) pUserData;
int64 read = stream->read(pBufferOut, bytesToRead); int64 read = decoder->stream->read(pBufferOut, bytesToRead);
return std::max<int64>(0, read); return std::max<int64>(0, read);
} }
static drmp3_bool32 onSeek(void *pUserData, int offset, drmp3_seek_origin origin) static drmp3_bool32 onSeek(void *pUserData, int offset, drmp3_seek_origin origin)
{ {
auto stream = (Stream *) pUserData; auto decoder = (MP3Decoder *) pUserData;
auto seekorigin = origin == drmp3_seek_origin_current ? Stream::SEEKORIGIN_CURRENT : Stream::SEEKORIGIN_BEGIN; int64 pos = decoder->offset;
return stream->seek(offset, seekorigin) ? DRMP3_TRUE : DRMP3_FALSE;
// Due to possible offsets, we have to calculate the position ourself.
switch (origin)
{
case drmp3_seek_origin_start:
pos += offset;
break;
case drmp3_seek_origin_current:
pos = decoder->stream->tell() + offset;
break;
default:
return false;
}
if (pos < decoder->offset)
return false;
return decoder->stream->seek(pos, Stream::SEEKORIGIN_BEGIN) ? DRMP3_TRUE : DRMP3_FALSE;
}
// Copied from dr_mp3 function drmp3_hdr_valid()
static bool isMP3HeaderValid(const uint8 *h)
{
return
// Sync bits
h[0] == 0xff &&
((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) &&
// Check layer
(DRMP3_HDR_GET_LAYER(h) != 0) &&
// Check bitrate
(DRMP3_HDR_GET_BITRATE(h) != 15) &&
// Check sample rate
(DRMP3_HDR_GET_SAMPLE_RATE(h) != 3);
}
static int64 findFirstValidHeader(Stream* stream)
{
constexpr size_t LOOKUP_SIZE = 16384;
std::vector<uint8> data(LOOKUP_SIZE);
uint8 header[10];
uint8 *dataPtr = data.data();
int64 buffer = 0;
int64 offset = 0;
if (stream->read(header, 10) < 10)
return -1;
if (memcmp(header, "TAG", 3) == 0)
{
// ID3v1 tag is always 128 bytes long
if (!stream->seek(128, Stream::SEEKORIGIN_BEGIN))
return -1;
buffer = stream->read(dataPtr, LOOKUP_SIZE);
offset = 128;
}
else if (memcmp(header, "ID3", 3) == 0)
{
// ID3v2 tag header is 10 bytes long, but we're
// only interested on how much we should skip.
int64 off =
header[9] |
((int64) header[8] << 7) |
((int64) header[7] << 14) |
((int64) header[6] << 21);
if (!stream->seek(off, Stream::SEEKORIGIN_CURRENT))
return -1;
buffer = stream->read(dataPtr, LOOKUP_SIZE);
offset = off + 10;
}
else
{
// Copy the rest to data buffer
memcpy(dataPtr, header, 10);
buffer = 10 + stream->read(dataPtr + 10, LOOKUP_SIZE - 10);
}
// Look for mp3 data
for (int i = 0; i < buffer - 4; i++, offset++)
{
if (isMP3HeaderValid(dataPtr++))
{
stream->seek(offset, Stream::SEEKORIGIN_BEGIN);
return offset;
}
}
// No valid MP3 frame found in first 16KB data
return -1;
} }
MP3Decoder::MP3Decoder(Stream *stream, int bufferSize) MP3Decoder::MP3Decoder(Stream *stream, int bufferSize)
: Decoder(stream, bufferSize) : Decoder(stream, bufferSize)
{ {
// Check for possible ID3 tag and skip it if necessary.
offset = findFirstValidHeader(stream);
if (offset == -1)
throw love::Exception("Could not find first valid mp3 header.");
// initialize mp3 handle // initialize mp3 handle
if (!drmp3_init(&mp3, onRead, onSeek, stream, nullptr, nullptr)) if (!drmp3_init(&mp3, onRead, onSeek, this, nullptr, nullptr))
throw love::Exception("Could not read mp3 data."); throw love::Exception("Could not read mp3 data.");
sampleRate = mp3.sampleRate; sampleRate = mp3.sampleRate;
@@ -63,14 +159,13 @@ MP3Decoder::MP3Decoder(Stream *stream, int bufferSize)
duration = ((double) pcmCount) / ((double) mp3.sampleRate); duration = ((double) pcmCount) / ((double) mp3.sampleRate);
// create seek table // create seek table
uint32_t mp3FrameInt = mp3FrameCount; drmp3_uint32 mp3FrameInt = (drmp3_uint32) mp3FrameCount;
seekTable.resize(mp3FrameCount, {0ULL, 0ULL, 0, 0}); seekTable.resize((size_t) mp3FrameCount, {0ULL, 0ULL, 0, 0});
if (!drmp3_calculate_seek_points(&mp3, &mp3FrameInt, seekTable.data())) if (!drmp3_calculate_seek_points(&mp3, &mp3FrameInt, seekTable.data()))
{ {
drmp3_uninit(&mp3); drmp3_uninit(&mp3);
throw love::Exception("Could not calculate mp3 seek table"); throw love::Exception("Could not calculate mp3 seek table");
} }
mp3FrameInt = mp3FrameInt > mp3FrameCount ? mp3FrameCount : mp3FrameInt;
// bind seek table // bind seek table
if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data())) if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data()))
@@ -105,7 +200,7 @@ int MP3Decoder::decode()
bool MP3Decoder::seek(double s) bool MP3Decoder::seek(double s)
{ {
drmp3_uint64 targetSample = s * mp3.sampleRate; drmp3_uint64 targetSample = (drmp3_uint64) (s * mp3.sampleRate);
drmp3_bool32 success = drmp3_seek_to_pcm_frame(&mp3, targetSample); drmp3_bool32 success = drmp3_seek_to_pcm_frame(&mp3, targetSample);
if (success) if (success)
+4
View File
@@ -54,11 +54,15 @@ public:
double getDuration() override; double getDuration() override;
private: private:
friend size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead);
friend drmp3_bool32 onSeek(void *pUserData, int offset, drmp3_seek_origin origin);
// MP3 handle // MP3 handle
drmp3 mp3; drmp3 mp3;
// Used for fast seeking // Used for fast seeking
std::vector<drmp3_seek_point> seekTable; std::vector<drmp3_seek_point> seekTable;
// Position of first MP3 frame found
int64 offset;
double duration; double duration;
}; // MP3Decoder }; // MP3Decoder