File: //home/arjun/projects/buyercall/node_modules/lamejs/src/main/java/mp3/VBRTag.java
/*
* Xing VBR tagging for LAME.
*
* Copyright (c) 1999 A.L. Faber
* Copyright (c) 2001 Jonathan Dee
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package mp3;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* A Vbr header may be present in the ancillary data field of the first frame of
* an mp3 bitstream<BR>
* The Vbr header (optionally) contains
* <UL>
* <LI>frames total number of audio frames in the bitstream
* <LI>bytes total number of bytes in the bitstream
* <LI>toc table of contents
* </UL>
*
* toc (table of contents) gives seek points for random access.<BR>
* The ith entry determines the seek point for i-percent duration.<BR>
* seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes<BR>
* e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes
*/
public class VBRTag {
Lame lame;
BitStream bs;
Version v;
public final void setModules(Lame lame, BitStream bs, Version v) {
this.lame = lame;
this.bs = bs;
this.v = v;
}
private static final int FRAMES_FLAG = 0x0001;
private static final int BYTES_FLAG = 0x0002;
private static final int TOC_FLAG = 0x0004;
private static final int VBR_SCALE_FLAG = 0x0008;
public static final int NUMTOCENTRIES = 100;
/**
* (0xB40) the max freeformat 640 32kHz framesize.
*/
public static final int MAXFRAMESIZE = 2880;
/**
* <PRE>
* 4 bytes for Header Tag
* 4 bytes for Header Flags
* 100 bytes for entry (toc)
* 4 bytes for frame size
* 4 bytes for stream size
* 4 bytes for VBR scale. a VBR quality indicator: 0=best 100=worst
* 20 bytes for LAME tag. for example, "LAME3.12 (beta 6)"
* ___________
* 140 bytes
* </PRE>
*/
private static final int VBRHEADERSIZE = (NUMTOCENTRIES + 4 + 4 + 4 + 4 + 4);
private static final int LAMEHEADERSIZE = (VBRHEADERSIZE + 9 + 1 + 1 + 8
+ 1 + 1 + 3 + 1 + 1 + 2 + 4 + 2 + 2);
/**
* The size of the Xing header MPEG-1, bit rate in kbps.
*/
private static final int XING_BITRATE1 = 128;
/**
* The size of the Xing header MPEG-2, bit rate in kbps.
*/
private static final int XING_BITRATE2 = 64;
/**
* The size of the Xing header MPEG-2.5, bit rate in kbps.
*/
private static final int XING_BITRATE25 = 32;
/**
* ISO-8859-1 charset for byte to string operations.
*/
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
/**
* VBR header magic string.
*/
private static final String VBRTag0 = "Xing";
/**
* VBR header magic string (VBR == VBRMode.vbr_off).
*/
private static final String VBRTag1 = "Info";
/**
* Lookup table for fast CRC-16 computation. Uses the polynomial
* x^16+x^15+x^2+1
*/
private static int crc16Lookup[] = { 0x0000, 0xC0C1, 0xC181, 0x0140,
0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741,
0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41,
0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40,
0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941,
0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40,
0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341,
0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141,
0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740,
0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40,
0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41,
0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940,
0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541,
0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340,
0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141,
0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740,
0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40,
0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,
0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940,
0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41,
0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541,
0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340,
0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140,
0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741,
0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41,
0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40,
0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941,
0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40,
0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540,
0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341,
0x4100, 0x81C1, 0x8081, 0x4040 };
/***********************************************************************
* Robert Hegemann 2001-01-17
***********************************************************************/
private void addVbr(final VBRSeekInfo v, final int bitrate) {
v.nVbrNumFrames++;
v.sum += bitrate;
v.seen++;
if (v.seen < v.want) {
return;
}
if (v.pos < v.size) {
v.bag[v.pos] = v.sum;
v.pos++;
v.seen = 0;
}
if (v.pos == v.size) {
for (int i = 1; i < v.size; i += 2) {
v.bag[i / 2] = v.bag[i];
}
v.want *= 2;
v.pos /= 2;
}
}
private void xingSeekTable(VBRSeekInfo v, byte[] t) {
if (v.pos <= 0)
return;
for (int i = 1; i < NUMTOCENTRIES; ++i) {
float j = i / (float) NUMTOCENTRIES, act, sum;
int indx = (int) (Math.floor(j * v.pos));
if (indx > v.pos - 1)
indx = v.pos - 1;
act = v.bag[indx];
sum = v.sum;
int seek_point = (int) (256. * act / sum);
if (seek_point > 255)
seek_point = 255;
t[i] = (byte) seek_point;
}
}
/**
* Add VBR entry, used to fill the VBR TOC entries.
*
* @param gfp
* global flags
*/
public final void addVbrFrame(final LameGlobalFlags gfp) {
final LameInternalFlags gfc = gfp.internal_flags;
int kbps = Tables.bitrate_table[gfp.version][gfc.bitrate_index];
assert (gfc.VBR_seek_table.bag != null);
addVbr(gfc.VBR_seek_table, kbps);
}
/**
* Read big endian integer (4-bytes) from header.
*
* @param buf
* header containing the integer
* @param bufPos
* offset into the header
* @return extracted integer
*/
private int extractInteger(final byte[] buf, final int bufPos) {
int x = buf[bufPos + 0] & 0xff;
x <<= 8;
x |= buf[bufPos + 1] & 0xff;
x <<= 8;
x |= buf[bufPos + 2] & 0xff;
x <<= 8;
x |= buf[bufPos + 3] & 0xff;
return x;
}
/**
* Write big endian integer (4-bytes) in the header.
*
* @param buf
* header to write the integer into
* @param bufPos
* offset into the header
* @param value
* integer value to write
*/
private void createInteger(final byte[] buf, final int bufPos,
final int value) {
buf[bufPos + 0] = (byte) ((value >> 24) & 0xff);
buf[bufPos + 1] = (byte) ((value >> 16) & 0xff);
buf[bufPos + 2] = (byte) ((value >> 8) & 0xff);
buf[bufPos + 3] = (byte) (value & 0xff);
}
/**
* Write big endian short (2-bytes) in the header.
*
* @param buf
* header to write the integer into
* @param bufPos
* offset into the header
* @param value
* integer value to write
*/
private void createShort(final byte[] buf, final int bufPos, final int value) {
buf[bufPos + 0] = (byte) ((value >> 8) & 0xff);
buf[bufPos + 1] = (byte) (value & 0xff);
}
/**
* Check for magic strings (Xing/Info).
*
* @param buf
* header to check
* @param bufPos
* header offset to check
* @return magic string found
*/
private boolean isVbrTag(final byte[] buf, final int bufPos) {
return new String(buf, bufPos, VBRTag0.length(), ISO_8859_1)
.equals(VBRTag0)
|| new String(buf, bufPos, VBRTag1.length(), ISO_8859_1)
.equals(VBRTag1);
}
private byte shiftInBitsValue(final byte x, final int n, final int v) {
return (byte) ((x << n) | (v & ~(-1 << n)));
}
/**
* Construct the MP3 header using the settings of the global flags.
*
* <img src="1000px-Mp3filestructure.svg.png">
*
* @param gfp
* global flags
* @param buffer
* header
*/
private void setLameTagFrameHeader(final LameGlobalFlags gfp,
final byte[] buffer) {
final LameInternalFlags gfc = gfp.internal_flags;
// MP3 Sync Word
buffer[0] = shiftInBitsValue(buffer[0], 8, 0xff);
buffer[1] = shiftInBitsValue(buffer[1], 3, 7);
buffer[1] = shiftInBitsValue(buffer[1], 1,
(gfp.out_samplerate < 16000) ? 0 : 1);
// Version
buffer[1] = shiftInBitsValue(buffer[1], 1, gfp.version);
// 01 == Layer 3
buffer[1] = shiftInBitsValue(buffer[1], 2, 4 - 3);
// Error protection
buffer[1] = shiftInBitsValue(buffer[1], 1, (!gfp.error_protection) ? 1
: 0);
// Bit rate
buffer[2] = shiftInBitsValue(buffer[2], 4, gfc.bitrate_index);
// Frequency
buffer[2] = shiftInBitsValue(buffer[2], 2, gfc.samplerate_index);
// Pad. Bit
buffer[2] = shiftInBitsValue(buffer[2], 1, 0);
// Priv. Bit
buffer[2] = shiftInBitsValue(buffer[2], 1, gfp.extension);
// Mode
buffer[3] = shiftInBitsValue(buffer[3], 2, gfp.mode.ordinal());
// Mode extension (Used with Joint Stereo)
buffer[3] = shiftInBitsValue(buffer[3], 2, gfc.mode_ext);
// Copy
buffer[3] = shiftInBitsValue(buffer[3], 1, gfp.copyright);
// Original
buffer[3] = shiftInBitsValue(buffer[3], 1, gfp.original);
// Emphasis
buffer[3] = shiftInBitsValue(buffer[3], 2, gfp.emphasis);
/* the default VBR header. 48 kbps layer III, no padding, no crc */
/* but sampling freq, mode and copyright/copy protection taken */
/* from first valid frame */
buffer[0] = (byte) 0xff;
byte abyte = (byte) (buffer[1] & 0xf1);
int bitrate;
if (1 == gfp.version) {
bitrate = XING_BITRATE1;
} else {
if (gfp.out_samplerate < 16000)
bitrate = XING_BITRATE25;
else
bitrate = XING_BITRATE2;
}
if (gfp.VBR == VbrMode.vbr_off)
bitrate = gfp.brate;
byte bbyte;
if (gfp.free_format)
bbyte = 0x00;
else
bbyte = (byte) (16 * lame.BitrateIndex(bitrate, gfp.version,
gfp.out_samplerate));
/*
* Use as much of the info from the real frames in the Xing header:
* samplerate, channels, crc, etc...
*/
if (gfp.version == 1) {
/* MPEG1 */
buffer[1] = (byte) (abyte | 0x0a); /* was 0x0b; */
abyte = (byte) (buffer[2] & 0x0d); /* AF keep also private bit */
buffer[2] = (byte) (bbyte | abyte); /* 64kbs MPEG1 frame */
} else {
/* MPEG2 */
buffer[1] = (byte) (abyte | 0x02); /* was 0x03; */
abyte = (byte) (buffer[2] & 0x0d); /* AF keep also private bit */
buffer[2] = (byte) (bbyte | abyte); /* 64kbs MPEG2 frame */
}
}
/**
* Get VBR tag information
*
* @param buf
* header to analyze
* @param bufPos
* offset into the header
* @return VBR tag data
*/
public final VBRTagData getVbrTag(final byte[] buf) {
final VBRTagData pTagData = new VBRTagData();
int bufPos = 0;
/* get Vbr header data */
pTagData.flags = 0;
/* get selected MPEG header data */
int hId = (buf[bufPos + 1] >> 3) & 1;
int hSrIndex = (buf[bufPos + 2] >> 2) & 3;
int hMode = (buf[bufPos + 3] >> 6) & 3;
int hBitrate = ((buf[bufPos + 2] >> 4) & 0xf);
hBitrate = Tables.bitrate_table[hId][hBitrate];
/* check for FFE syncword */
if ((buf[bufPos + 1] >> 4) == 0xE)
pTagData.samprate = Tables.samplerate_table[2][hSrIndex];
else
pTagData.samprate = Tables.samplerate_table[hId][hSrIndex];
/* determine offset of header */
if (hId != 0) {
/* mpeg1 */
if (hMode != 3)
bufPos += (32 + 4);
else
bufPos += (17 + 4);
} else {
/* mpeg2 */
if (hMode != 3)
bufPos += (17 + 4);
else
bufPos += (9 + 4);
}
if (!isVbrTag(buf, bufPos))
return null;
bufPos += 4;
pTagData.hId = hId;
/* get flags */
int head_flags = pTagData.flags = extractInteger(buf, bufPos);
bufPos += 4;
if ((head_flags & FRAMES_FLAG) != 0) {
pTagData.frames = extractInteger(buf, bufPos);
bufPos += 4;
}
if ((head_flags & BYTES_FLAG) != 0) {
pTagData.bytes = extractInteger(buf, bufPos);
bufPos += 4;
}
if ((head_flags & TOC_FLAG) != 0) {
if (pTagData.toc != null) {
for (int i = 0; i < NUMTOCENTRIES; i++)
pTagData.toc[i] = buf[bufPos + i];
}
bufPos += NUMTOCENTRIES;
}
pTagData.vbrScale = -1;
if ((head_flags & VBR_SCALE_FLAG) != 0) {
pTagData.vbrScale = extractInteger(buf, bufPos);
bufPos += 4;
}
pTagData.headersize = ((hId + 1) * 72000 * hBitrate)
/ pTagData.samprate;
bufPos += 21;
int encDelay = buf[bufPos + 0] << 4;
encDelay += buf[bufPos + 1] >> 4;
int encPadding = (buf[bufPos + 1] & 0x0F) << 8;
encPadding += buf[bufPos + 2] & 0xff;
/* check for reasonable values (this may be an old Xing header, */
/* not a INFO tag) */
if (encDelay < 0 || encDelay > 3000)
encDelay = -1;
if (encPadding < 0 || encPadding > 3000)
encPadding = -1;
pTagData.encDelay = encDelay;
pTagData.encPadding = encPadding;
/* success */
return pTagData;
}
/**
* Initializes the header
*
* @param gfp
* global flags
*/
public final void InitVbrTag(final LameGlobalFlags gfp) {
final LameInternalFlags gfc = gfp.internal_flags;
/**
* <PRE>
* Xing VBR pretends to be a 48kbs layer III frame. (at 44.1kHz).
* (at 48kHz they use 56kbs since 48kbs frame not big enough for
* table of contents)
* let's always embed Xing header inside a 64kbs layer III frame.
* this gives us enough room for a LAME version string too.
* size determined by sampling frequency (MPEG1)
* 32kHz: 216 bytes@48kbs 288bytes@ 64kbs
* 44.1kHz: 156 bytes 208bytes@64kbs (+1 if padding = 1)
* 48kHz: 144 bytes 192
*
* MPEG 2 values are the same since the framesize and samplerate
* are each reduced by a factor of 2.
* </PRE>
*/
int kbps_header;
if (1 == gfp.version) {
kbps_header = XING_BITRATE1;
} else {
if (gfp.out_samplerate < 16000)
kbps_header = XING_BITRATE25;
else
kbps_header = XING_BITRATE2;
}
if (gfp.VBR == VbrMode.vbr_off)
kbps_header = gfp.brate;
// make sure LAME Header fits into Frame
int totalFrameSize = ((gfp.version + 1) * 72000 * kbps_header)
/ gfp.out_samplerate;
int headerSize = (gfc.sideinfo_len + LAMEHEADERSIZE);
gfc.VBR_seek_table.TotalFrameSize = totalFrameSize;
if (totalFrameSize < headerSize || totalFrameSize > MAXFRAMESIZE) {
/* disable tag, it wont fit */
gfp.bWriteVbrTag = false;
return;
}
gfc.VBR_seek_table.nVbrNumFrames = 0;
gfc.VBR_seek_table.nBytesWritten = 0;
gfc.VBR_seek_table.sum = 0;
gfc.VBR_seek_table.seen = 0;
gfc.VBR_seek_table.want = 1;
gfc.VBR_seek_table.pos = 0;
if (gfc.VBR_seek_table.bag == null) {
gfc.VBR_seek_table.bag = new int[400];
gfc.VBR_seek_table.size = 400;
}
// write dummy VBR tag of all 0's into bitstream
byte buffer[] = new byte[MAXFRAMESIZE];
setLameTagFrameHeader(gfp, buffer);
int n = gfc.VBR_seek_table.TotalFrameSize;
for (int i = 0; i < n; ++i) {
bs.add_dummy_byte(gfp, buffer[i] & 0xff, 1);
}
}
/**
* Fast CRC-16 computation (uses table crc16Lookup).
*
* @param value
* @param crc
* @return
*/
private int crcUpdateLookup(final int value, int crc) {
int tmp = crc ^ value;
crc = (crc >> 8) ^ crc16Lookup[tmp & 0xff];
return crc;
}
public final void updateMusicCRC(final int[] crc, final byte[] buffer,
final int bufferPos, final int size) {
for (int i = 0; i < size; ++i)
crc[0] = crcUpdateLookup(buffer[bufferPos + i], crc[0]);
}
/**
* Write LAME info: mini version + info on various switches used (Jonathan
* Dee 2001/08/31).
*
* @param gfp
* global flags
* @param musicLength
* music length
* @param streamBuffer
* pointer to output buffer
* @param streamBufferPos
* offset into the output buffer
* @param crc
* computation of CRC-16 of Lame Tag so far (starting at frame
* sync)
* @return number of bytes written to the stream
*/
private int putLameVBR(final LameGlobalFlags gfp, final int musicLength,
final byte[] streamBuffer, final int streamBufferPos, int crc) {
final LameInternalFlags gfc = gfp.internal_flags;
int bytesWritten = 0;
/* encoder delay */
int encDelay = gfp.encoder_delay;
/* encoder padding */
int encPadding = gfp.encoder_padding;
/* recall: gfp.VBR_q is for example set by the switch -V */
/* gfp.quality by -q, -h, -f, etc */
int quality = (100 - 10 * gfp.VBR_q - gfp.quality);
final String version = v.getLameVeryShortVersion();
int vbr;
int revision = 0x00;
int revMethod;
// numbering different in vbr_mode vs. Lame tag
int vbrTypeTranslator[] = { 1, 5, 3, 2, 4, 0, 3 };
int lowpass = (int) (((gfp.lowpassfreq / 100.0) + .5) > 255 ? 255
: (gfp.lowpassfreq / 100.0) + .5);
int peakSignalAmplitude = 0;
int radioReplayGain = 0;
int audiophileReplayGain = 0;
int noiseShaping = gfp.internal_flags.noise_shaping;
int stereoMode = 0;
int nonOptimal = 0;
int sourceFreq = 0;
int misc = 0;
int musicCRC = 0;
// psy model type: Gpsycho or NsPsytune
boolean expNPsyTune = (gfp.exp_nspsytune & 1) != 0;
boolean safeJoint = (gfp.exp_nspsytune & 2) != 0;
boolean noGapMore = false;
boolean noGapPrevious = false;
int noGapCount = gfp.internal_flags.nogap_total;
int noGapCurr = gfp.internal_flags.nogap_current;
// 4 bits
int athType = gfp.ATHtype;
int flags = 0;
// vbr modes
int abrBitrate;
switch (gfp.VBR) {
case vbr_abr:
abrBitrate = gfp.VBR_mean_bitrate_kbps;
break;
case vbr_off:
abrBitrate = gfp.brate;
break;
default:
abrBitrate = gfp.VBR_min_bitrate_kbps;
}
// revision and vbr method
if (gfp.VBR.ordinal() < vbrTypeTranslator.length)
vbr = vbrTypeTranslator[gfp.VBR.ordinal()];
else
vbr = 0x00; // unknown
revMethod = 0x10 * revision + vbr;
// ReplayGain
if (gfc.findReplayGain) {
if (gfc.RadioGain > 0x1FE)
gfc.RadioGain = 0x1FE;
if (gfc.RadioGain < -0x1FE)
gfc.RadioGain = -0x1FE;
// set name code
radioReplayGain = 0x2000;
// set originator code to `determined automatically'
radioReplayGain |= 0xC00;
if (gfc.RadioGain >= 0) {
// set gain adjustment
radioReplayGain |= gfc.RadioGain;
} else {
// set the sign bit
radioReplayGain |= 0x200;
// set gain adjustment
radioReplayGain |= -gfc.RadioGain;
}
}
// peak sample
if (gfc.findPeakSample)
peakSignalAmplitude = Math
.abs((int) ((((float) gfc.PeakSample) / 32767.0)
* Math.pow(2, 23) + .5));
// nogap
if (noGapCount != -1) {
if (noGapCurr > 0)
noGapPrevious = true;
if (noGapCurr < noGapCount - 1)
noGapMore = true;
}
// flags
flags = athType + ((expNPsyTune ? 1 : 0) << 4)
+ ((safeJoint ? 1 : 0) << 5) + ((noGapMore ? 1 : 0) << 6)
+ ((noGapPrevious ? 1 : 0) << 7);
if (quality < 0)
quality = 0;
// stereo mode field (Intensity stereo is not implemented)
switch (gfp.mode) {
case MONO:
stereoMode = 0;
break;
case STEREO:
stereoMode = 1;
break;
case DUAL_CHANNEL:
stereoMode = 2;
break;
case JOINT_STEREO:
if (gfp.force_ms)
stereoMode = 4;
else
stereoMode = 3;
break;
case NOT_SET:
//$FALL-THROUGH$
default:
stereoMode = 7;
break;
}
if (gfp.in_samplerate <= 32000)
sourceFreq = 0x00;
else if (gfp.in_samplerate == 48000)
sourceFreq = 0x02;
else if (gfp.in_samplerate > 48000)
sourceFreq = 0x03;
else {
// default is 44100Hz
sourceFreq = 0x01;
}
// Check if the user overrided the default LAME behavior with some
// nasty options
if (gfp.short_blocks == ShortBlock.short_block_forced
|| gfp.short_blocks == ShortBlock.short_block_dispensed
|| ((gfp.lowpassfreq == -1) && (gfp.highpassfreq == -1)) || /* "-k" */
(gfp.scale_left < gfp.scale_right)
|| (gfp.scale_left > gfp.scale_right)
|| (gfp.disable_reservoir && gfp.brate < 320) || gfp.noATH
|| gfp.ATHonly || (athType == 0) || gfp.in_samplerate <= 32000)
nonOptimal = 1;
misc = noiseShaping + (stereoMode << 2) + (nonOptimal << 5)
+ (sourceFreq << 6);
musicCRC = gfc.nMusicCRC;
// Write all this information into the stream
createInteger(streamBuffer, streamBufferPos + bytesWritten, quality);
bytesWritten += 4;
for (int j = 0; j < 9; j++) {
streamBuffer[streamBufferPos + bytesWritten + j] = (byte) version
.charAt(j);
}
bytesWritten += 9;
streamBuffer[streamBufferPos + bytesWritten] = (byte) revMethod;
bytesWritten++;
streamBuffer[streamBufferPos + bytesWritten] = (byte) lowpass;
bytesWritten++;
createInteger(streamBuffer, streamBufferPos + bytesWritten,
peakSignalAmplitude);
bytesWritten += 4;
createShort(streamBuffer, streamBufferPos + bytesWritten,
radioReplayGain);
bytesWritten += 2;
createShort(streamBuffer, streamBufferPos + bytesWritten,
audiophileReplayGain);
bytesWritten += 2;
streamBuffer[streamBufferPos + bytesWritten] = (byte) flags;
bytesWritten++;
if (abrBitrate >= 255)
streamBuffer[streamBufferPos + bytesWritten] = (byte) 0xFF;
else
streamBuffer[streamBufferPos + bytesWritten] = (byte) abrBitrate;
bytesWritten++;
streamBuffer[streamBufferPos + bytesWritten] = (byte) (encDelay >> 4);
streamBuffer[streamBufferPos + bytesWritten + 1] = (byte) ((encDelay << 4) + (encPadding >> 8));
streamBuffer[streamBufferPos + bytesWritten + 2] = (byte) encPadding;
bytesWritten += 3;
streamBuffer[streamBufferPos + bytesWritten] = (byte) misc;
bytesWritten++;
// unused in rev0
streamBuffer[streamBufferPos + bytesWritten++] = 0;
createShort(streamBuffer, streamBufferPos + bytesWritten, gfp.preset);
bytesWritten += 2;
createInteger(streamBuffer, streamBufferPos + bytesWritten, musicLength);
bytesWritten += 4;
createShort(streamBuffer, streamBufferPos + bytesWritten, musicCRC);
bytesWritten += 2;
// Calculate tag CRC.... must be done here, since it includes previous
// information
for (int i = 0; i < bytesWritten; i++)
crc = crcUpdateLookup(streamBuffer[streamBufferPos + i], crc);
createShort(streamBuffer, streamBufferPos + bytesWritten, crc);
bytesWritten += 2;
return bytesWritten;
}
private int skipId3v2(final RandomAccessFile fpStream) throws IOException {
// seek to the beginning of the stream
fpStream.seek(0);
// read 10 bytes in case there's an ID3 version 2 header here
byte[] id3v2Header = new byte[10];
fpStream.readFully(id3v2Header);
/* does the stream begin with the ID3 version 2 file identifier? */
int id3v2TagSize;
if (!new String(id3v2Header, "ISO-8859-1").startsWith("ID3")) {
/*
* the tag size (minus the 10-byte header) is encoded into four
* bytes where the most significant bit is clear in each byte
*/
id3v2TagSize = (((id3v2Header[6] & 0x7f) << 21)
| ((id3v2Header[7] & 0x7f) << 14)
| ((id3v2Header[8] & 0x7f) << 7) | (id3v2Header[9] & 0x7f))
+ id3v2Header.length;
} else {
/* no ID3 version 2 tag in this stream */
id3v2TagSize = 0;
}
return id3v2TagSize;
}
public final int getLameTagFrame(final LameGlobalFlags gfp,
final byte[] buffer) {
final LameInternalFlags gfc = gfp.internal_flags;
if (!gfp.bWriteVbrTag) {
return 0;
}
if (gfc.Class_ID != Lame.LAME_ID) {
return 0;
}
if (gfc.VBR_seek_table.pos <= 0) {
return 0;
}
if (buffer.length < gfc.VBR_seek_table.TotalFrameSize) {
return gfc.VBR_seek_table.TotalFrameSize;
}
Arrays.fill(buffer, 0, gfc.VBR_seek_table.TotalFrameSize, (byte) 0);
// 4 bytes frame header
setLameTagFrameHeader(gfp, buffer);
// Create TOC entries
byte toc[] = new byte[NUMTOCENTRIES];
if (gfp.free_format) {
for (int i = 1; i < NUMTOCENTRIES; ++i)
toc[i] = (byte) (255 * i / 100);
} else {
xingSeekTable(gfc.VBR_seek_table, toc);
}
// Start writing the tag after the zero frame
int streamIndex = gfc.sideinfo_len;
/**
* Note: Xing header specifies that Xing data goes in the ancillary data
* with NO ERROR PROTECTION. If error protecton in enabled, the Xing
* data still starts at the same offset, and now it is in sideinfo data
* block, and thus will not decode correctly by non-Xing tag aware
* players
*/
if (gfp.error_protection)
streamIndex -= 2;
// Put Vbr tag
if (gfp.VBR == VbrMode.vbr_off) {
buffer[streamIndex++] = (byte) VBRTag1.charAt(0);
buffer[streamIndex++] = (byte) VBRTag1.charAt(1);
buffer[streamIndex++] = (byte) VBRTag1.charAt(2);
buffer[streamIndex++] = (byte) VBRTag1.charAt(3);
} else {
buffer[streamIndex++] = (byte) VBRTag0.charAt(0);
buffer[streamIndex++] = (byte) VBRTag0.charAt(1);
buffer[streamIndex++] = (byte) VBRTag0.charAt(2);
buffer[streamIndex++] = (byte) VBRTag0.charAt(3);
}
// Put header flags
createInteger(buffer, streamIndex, FRAMES_FLAG + BYTES_FLAG + TOC_FLAG
+ VBR_SCALE_FLAG);
streamIndex += 4;
// Put Total Number of frames
createInteger(buffer, streamIndex, gfc.VBR_seek_table.nVbrNumFrames);
streamIndex += 4;
// Put total audio stream size, including Xing/LAME Header
int streamSize = (gfc.VBR_seek_table.nBytesWritten + gfc.VBR_seek_table.TotalFrameSize);
createInteger(buffer, streamIndex, (int) streamSize);
streamIndex += 4;
/* Put TOC */
System.arraycopy(toc, 0, buffer, streamIndex, toc.length);
streamIndex += toc.length;
if (gfp.error_protection) {
// (jo) error_protection: add crc16 information to header
bs.CRC_writeheader(gfc, buffer);
}
// work out CRC so far: initially crc = 0
int crc = 0x00;
for (int i = 0; i < streamIndex; i++)
crc = crcUpdateLookup(buffer[i], crc);
// Put LAME VBR info
streamIndex += putLameVBR(gfp, streamSize, buffer, streamIndex, crc);
return gfc.VBR_seek_table.TotalFrameSize;
}
/**
* Write final VBR tag to the file.
*
* @param gfp
* global flags
* @param stream
* stream to add the VBR tag to
* @return 0 (OK), -1 else
* @throws IOException
* I/O error
*/
public final int putVbrTag(final LameGlobalFlags gfp,
final RandomAccessFile stream) throws IOException {
final LameInternalFlags gfc = gfp.internal_flags;
if (gfc.VBR_seek_table.pos <= 0)
return -1;
// Seek to end of file
stream.seek(stream.length());
// Get file size, abort if file has zero length.
if (stream.length() == 0)
return -1;
// The VBR tag may NOT be located at the beginning of the stream. If an
// ID3 version 2 tag was added, then it must be skipped to write the VBR
// tag data.
int id3v2TagSize = skipId3v2(stream);
// Seek to the beginning of the stream
stream.seek(id3v2TagSize);
byte[] buffer = new byte[MAXFRAMESIZE];
int bytes = getLameTagFrame(gfp, buffer);
if (bytes > buffer.length) {
return -1;
}
if (bytes < 1) {
return 0;
}
// Put it all to disk again
stream.write(buffer, 0, bytes);
// success
return 0;
}
}