mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
293 lines
6.8 KiB
C++
293 lines
6.8 KiB
C++
#include "LAMEInfo.h"
|
|
#include "MPEGHeader.h"
|
|
#include "foundation/error.h"
|
|
#include <string.h>
|
|
#include "nu/ByteReader.h"
|
|
#include "nu/BitReader.h"
|
|
// Xing header -
|
|
// 4 Xing
|
|
// 4 flags
|
|
// 4 frames
|
|
// 4 bytes
|
|
// 100 toc
|
|
// 4 bytes VBR quality
|
|
|
|
// Lame tag
|
|
// 9 bytes - release name
|
|
// 11
|
|
|
|
// Lame extended info tag
|
|
|
|
// http://gabriel.mp3-tech.org/mp3infotag.html
|
|
|
|
|
|
|
|
LAMEInfo::LAMEInfo()
|
|
{
|
|
memset(this, 0, sizeof(LAMEInfo));
|
|
}
|
|
|
|
bool LAMEInfo::Flag(int flag) const
|
|
{
|
|
return flags & flag;
|
|
}
|
|
|
|
int LAMEInfo::GetGaps(size_t *pregap, size_t *postgap)
|
|
{
|
|
if (!encoder_delay)
|
|
return NErr_Empty;
|
|
|
|
*pregap = encoder_delay;
|
|
*postgap = padding;
|
|
return NErr_Success;
|
|
}
|
|
|
|
uint64_t LAMEInfo::GetSeekPoint(double percent) const
|
|
{
|
|
// interpolate in TOC to get file seek point in bytes
|
|
int a;
|
|
uint64_t seekpoint;
|
|
double fa, fb, fx;
|
|
|
|
percent*=100.0;
|
|
if (percent < 0.0)
|
|
percent = 0.0;
|
|
if (percent > 100.0)
|
|
percent = 100.0;
|
|
|
|
a = (int)(percent);
|
|
if (a > 99) a = 99;
|
|
fa = toc[a];
|
|
if (a < 99)
|
|
{
|
|
fb = toc[a + 1];
|
|
}
|
|
else
|
|
{
|
|
fb = 256.0;
|
|
}
|
|
|
|
fx = fa + (fb - fa) * (percent - a);
|
|
seekpoint = (uint64_t) ((1.0 / 256.0) * fx * bytes);
|
|
return seekpoint;
|
|
}
|
|
|
|
uint64_t LAMEInfo::GetSamples() const
|
|
{
|
|
if (flags&FRAMES_FLAG)
|
|
{
|
|
uint64_t samples = frames * samples_per_frame;
|
|
samples -= (encoder_delay + padding);
|
|
return samples;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t LAMEInfo::GetFrames() const
|
|
{
|
|
if (flags&FRAMES_FLAG)
|
|
return frames;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
double LAMEInfo::GetLengthSeconds() const
|
|
{
|
|
if (flags&FRAMES_FLAG)
|
|
{
|
|
return (double)GetSamples() / (double)sample_rate;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int LAMEInfo::Read(const MPEGHeader &frame, const uint8_t *buffer, size_t buffer_length)
|
|
{
|
|
int flags;
|
|
bool crc_hack_applied=false;
|
|
bytereader_value_t byte_reader;
|
|
|
|
/* maybe toolame writes these things also, I dunno. we'll just abort for now */
|
|
if (frame.layer != MPEGHeader::Layer3)
|
|
return 0;
|
|
|
|
|
|
bytereader_init(&byte_reader, buffer, buffer_length);
|
|
|
|
sample_rate = frame.GetSampleRate();
|
|
version = frame.mpeg_version;
|
|
samples_per_frame = frame.GetSamplesPerFrame();
|
|
|
|
// skip sideinfo
|
|
if (frame.mpeg_version == MPEGHeader::MPEG1) // MPEG 1
|
|
{
|
|
if (frame.channel_mode == MPEGHeader::Mono)
|
|
bytereader_advance(&byte_reader, 17);
|
|
else
|
|
bytereader_advance(&byte_reader, 32);
|
|
}
|
|
else if (frame.mpeg_version == MPEGHeader::MPEG2) // MPEG 2
|
|
{
|
|
if (frame.channel_mode == MPEGHeader::Mono)
|
|
bytereader_advance(&byte_reader, 9);
|
|
else
|
|
bytereader_advance(&byte_reader, 17);
|
|
}
|
|
else if (frame.mpeg_version == MPEGHeader::MPEG2_5) // MPEG 2
|
|
{
|
|
if (frame.channel_mode == MPEGHeader::Mono)
|
|
bytereader_advance(&byte_reader, 9);
|
|
else
|
|
bytereader_advance(&byte_reader, 17);
|
|
}
|
|
|
|
if (bytereader_size(&byte_reader) > buffer_length /* check for wraparound */
|
|
|| bytereader_size(&byte_reader) < 8)
|
|
return NErr_Insufficient;
|
|
|
|
again:
|
|
if (bytereader_show_u32_be(&byte_reader) == 'Info')
|
|
cbr=1;
|
|
else if (bytereader_show_u32_be(&byte_reader) != 'Xing' && bytereader_show_u32_be(&byte_reader) != 'Lame')
|
|
{
|
|
// if there's CRC data, LAME sometimes writes to the wrong position
|
|
if (frame.IsCRC() && !crc_hack_applied)
|
|
{
|
|
crc_hack_applied=true;
|
|
bytereader_advance(&byte_reader, 2);
|
|
goto again;
|
|
}
|
|
return NErr_False;
|
|
}
|
|
|
|
bytereader_advance(&byte_reader, 4); // skip Xing tag
|
|
flags = this->flags = bytereader_read_u32_be(&byte_reader);
|
|
|
|
if (flags & FRAMES_FLAG)
|
|
{
|
|
if (bytereader_size(&byte_reader) < 4)
|
|
return NErr_Insufficient;
|
|
|
|
frames = bytereader_read_u32_be(&byte_reader);
|
|
}
|
|
if (flags & BYTES_FLAG)
|
|
{
|
|
if (bytereader_size(&byte_reader) < 4)
|
|
return NErr_Insufficient;
|
|
bytes = bytereader_read_u32_be(&byte_reader);
|
|
}
|
|
if (flags & TOC_FLAG)
|
|
{
|
|
if (bytereader_size(&byte_reader) < 100)
|
|
return NErr_Insufficient;
|
|
|
|
int i;
|
|
memcpy(toc, bytereader_pointer(&byte_reader), 100);
|
|
|
|
// verify that TOC isn't empty
|
|
for (i = 0; i < 100; i++)
|
|
if (toc[i]) break;
|
|
if (i == 100)
|
|
flags &= ~TOC_FLAG;
|
|
|
|
bytereader_advance(&byte_reader, 100);
|
|
}
|
|
|
|
vbr_scale = -1;
|
|
if (flags & VBR_SCALE_FLAG)
|
|
{
|
|
if (bytereader_size(&byte_reader) < 4)
|
|
return NErr_Insufficient;
|
|
vbr_scale = bytereader_read_u32_be(&byte_reader);
|
|
}
|
|
|
|
if (bytereader_size(&byte_reader) < 27)
|
|
return NErr_Success; // stop here if we have to, we have at least some data
|
|
|
|
if (bytereader_show_u32_be(&byte_reader) == 'LAME')
|
|
{
|
|
for (int i=0;i<9;i++)
|
|
encoder[i]=bytereader_read_u8(&byte_reader);
|
|
encoder[9]=0; // null terminate in case tag used all 9 characters
|
|
|
|
if (bytereader_show_u8(&byte_reader) == '(')
|
|
{
|
|
// read 11 more characters
|
|
for (int i=9;i<20;i++)
|
|
encoder[i]=bytereader_read_u8(&byte_reader);
|
|
encoder[20]=0;
|
|
}
|
|
else
|
|
{
|
|
tag_revision = bytereader_show_u8(&byte_reader)>>4;
|
|
if (tag_revision == 0)
|
|
{
|
|
encoding_method = bytereader_read_u8(&byte_reader)&0xF; // VBR method
|
|
lowpass = bytereader_read_u8(&byte_reader)*100; // lowpass value
|
|
peak=bytereader_read_f32_be(&byte_reader); // read peak value
|
|
|
|
// read track gain
|
|
int16_t gain_word = bytereader_read_s16_be(&byte_reader);
|
|
if ((gain_word & 0xFC00) == 0x2C00)
|
|
{
|
|
replaygain_track_gain = (float)(gain_word & 0x01FF);
|
|
replaygain_track_gain /= 10;
|
|
if (gain_word & 0x0200)
|
|
replaygain_track_gain = -replaygain_track_gain;
|
|
}
|
|
|
|
// read album gain
|
|
gain_word = bytereader_read_s16_be(&byte_reader);
|
|
if ((gain_word & 0xFC00) == 0x4C00)
|
|
{
|
|
replaygain_album_gain = (float)(gain_word & 0x01FF);
|
|
replaygain_album_gain /= 10;
|
|
if (gain_word & 0x0200)
|
|
replaygain_album_gain = -replaygain_album_gain;
|
|
}
|
|
|
|
bytereader_advance(&byte_reader, 1); // skip encoding flags + ATH type
|
|
abr_bitrate = bytereader_read_u8(&byte_reader); // bitrate
|
|
|
|
// get the encoder delay and padding, annoyingly as 12 bit values packed into 3 bytes
|
|
BitReader bit_reader;
|
|
bit_reader.data = (const uint8_t *)bytereader_pointer(&byte_reader);
|
|
bit_reader.numBits = 24;
|
|
const uint8_t *temp = (const uint8_t *)bytereader_pointer(&byte_reader);
|
|
encoder_delay = bit_reader.getbits(12);
|
|
padding = bit_reader.getbits(12);
|
|
bytereader_advance(&byte_reader, 3);
|
|
|
|
bytereader_advance(&byte_reader, 4);
|
|
// skip misc
|
|
// skip MP3Gain reconstruction info
|
|
// skip surround info and preset info
|
|
|
|
music_length = bytereader_read_u32_be(&byte_reader);
|
|
music_crc = bytereader_read_u16_be(&byte_reader);
|
|
tag_crc = bytereader_read_u16_be(&byte_reader);
|
|
|
|
}
|
|
}
|
|
}
|
|
else if (!memcmp(bytereader_pointer(&byte_reader), "iTunes", 6))
|
|
{
|
|
int i=0;
|
|
while (bytereader_size(&byte_reader) && i < 31)
|
|
{
|
|
encoder[i] = bytereader_read_u8(&byte_reader);
|
|
if (!encoder[i])
|
|
break;
|
|
i++;
|
|
}
|
|
encoder[31]=0;
|
|
}
|
|
else if (!memcmp(bytereader_pointer(&byte_reader), "\0\0\0\0mp3HD", 9))
|
|
{
|
|
bytereader_advance(&byte_reader, 4);
|
|
for (int i=0;i<5;i++)
|
|
encoder[i] = bytereader_read_u8(&byte_reader);
|
|
|
|
encoder[5]=0;
|
|
}
|
|
return NErr_Success;
|
|
}
|