aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nsmp3/LAMEInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/replicant/nsmp3/LAMEInfo.cpp')
-rw-r--r--Src/replicant/nsmp3/LAMEInfo.cpp293
1 files changed, 293 insertions, 0 deletions
diff --git a/Src/replicant/nsmp3/LAMEInfo.cpp b/Src/replicant/nsmp3/LAMEInfo.cpp
new file mode 100644
index 00000000..0f791532
--- /dev/null
+++ b/Src/replicant/nsmp3/LAMEInfo.cpp
@@ -0,0 +1,293 @@
+#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;
+}