mirror of
https://github.com/love2d/love.git
synced 2026-08-17 11:00:31 +02:00
Improve MP3 detection to reduce false positive.
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user