#include "LAMEInfo.h" #include "MPEGHeader.h" #include "foundation/error.h" #include #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; }