001: /*
002: * $Id: MP3Helper.java,v 1.4 2002/06/24 13:51:55 awason Exp $
003: *
004: * ===========================================================================
005: *
006: */
007:
008: package org.openlaszlo.iv.flash.util;
009:
010: import org.openlaszlo.iv.flash.parser.*;
011: import org.openlaszlo.iv.flash.util.*;
012: import org.openlaszlo.iv.flash.url.*;
013: import org.openlaszlo.iv.flash.api.*;
014:
015: import java.io.*;
016:
017: /**
018: * Class for parsing MP3 files, but only accepts those which the flash player
019: * understands.
020: *
021: * @author James Taylor
022: *
023: */
024:
025: public class MP3Helper {
026: private InputStream in;
027:
028: private boolean isSynched = false;
029:
030: // Per frame porperties ( but should be constant except pad and framesize )
031:
032: private int mode;
033: private int layer;
034: private int bitrate;
035: private int frequency;
036: private int pad;
037: private int frameSize;
038:
039: private int samplesPerFrame;
040:
041: // Counts samples read _so far_
042:
043: private int samples = 0;
044:
045: private boolean stereo;
046:
047: /* MP3 has a constant number of samples per frame */
048:
049: private static int SAMPLES_PER_FRAME_V1 = 1152;
050: private static int SAMPLES_PER_FRAME_V2 = 576;
051:
052: /* Flash only supports mpeg audio layer 3, so only the bitrates for layer 3
053: * are included here. Row 1 is for version 1, row 2 is for version 2 and 2.5
054: */
055:
056: private static int bitrates[][] = {
057: { -1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224,
058: 256, 320, -1 },
059: { -1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144,
060: 160, -1 } };
061:
062: /* Frequencies that flash supports. Probably a waste to use a 2d array here
063: * since flash only allows the first column ( 00 for frequency ).
064: *
065: */
066:
067: private static int frequencies[][] = { { 11025, -1, -1, -1 },
068: { -1, -1, -1, -1 }, { 22050, -1, -1, -1 },
069: { 44100, -1, -1, -1 } };
070:
071: /**
072: * Creates a new MP3Helper which reads MP3Frames from the supplied buffer.
073: *
074: */
075:
076: public MP3Helper(FlashBuffer fob) throws IOException, IVException {
077: in = fob.getInputStream();
078: skipID3(fob);
079: }
080:
081: /*
082: * Skip ID3 header.
083: * If it is not using an "unsynchronization scheme", it can confuse sync()
084: * http://www.id3.org/develop.html
085: */
086: private void skipID3(FlashBuffer fob) throws IOException {
087: int b1, b2, b3, b4;
088: // Do nothing if no valid ID3 header
089: if (fob.getSize() < 10 || fob.getByteAt(0) != 'I'
090: || fob.getByteAt(1) != 'D' || fob.getByteAt(2) != '3'
091: || fob.getUByteAt(3) >= 0xff
092: || fob.getUByteAt(4) >= 0xff
093: || (b1 = fob.getUByteAt(6)) >= 0x80
094: || (b2 = fob.getUByteAt(7)) >= 0x80
095: || (b3 = fob.getUByteAt(8)) >= 0x80
096: || (b4 = fob.getUByteAt(9)) >= 0x80)
097: return;
098:
099: // Skip ID3 data - size is a 28bit integer, with high bit of each byte ignored
100: in.skip(10 + (b1 << 21 | b2 << 14 | b3 << 7 | b4));
101: }
102:
103: /**
104: * Reads the header of the next MP3 frame, synchronzing the stream first
105: * if that has not yet been done.
106: *
107: * @return the 4 byte header, or null if there are no more frames.
108: *
109: */
110:
111: public byte[] readHeader() throws IOException, IVException {
112: byte[] header;
113:
114: if (isSynched == false) {
115: header = sync();
116: } else {
117: header = new byte[4];
118:
119: if (in.read(header, 0, 4) != 4) {
120: return null;
121: }
122:
123: }
124:
125: // Verify the header
126:
127: if (((header[0] & 0xFF) != 0xFF)) {
128: return null;
129: }
130:
131: // Parse header data
132:
133: mode = (header[1] >>> 3) & 0x03;
134:
135: samplesPerFrame = (mode == 3) ? SAMPLES_PER_FRAME_V1
136: : SAMPLES_PER_FRAME_V2;
137:
138: layer = (header[1] >>> 1) & 0x03;
139:
140: // Only layer 3 files are valid for the flash player
141:
142: if (layer != 1) {
143: throw new IVException(Resource.INVLMP3LAYER);
144: }
145:
146: // Get bitrate from bitrates array -- varies with mode
147:
148: bitrate = bitrates[(mode == 3) ? 0 : 1][(header[2] >>> 4) & 0x0F];
149:
150: // Get frequency -- also varies with mode
151:
152: frequency = frequencies[mode][(header[2] >>> 2) & 0x03];
153:
154: if (frequency == -1) {
155: throw new IVException(Resource.INVLMP3FREQUENCY);
156: }
157:
158: // Channel mode
159:
160: stereo = (((header[3] >>> 6) & 0x03) != 3);
161:
162: // Determine is this frame is padded
163:
164: pad = ((header[2] >>> 1) & 0x01);
165:
166: // Increment the sample counter
167:
168: samples += samplesPerFrame;
169:
170: // Calculate the frame size
171:
172: frameSize = (((mode == 3) ? 144 : 72) * bitrate * 1000
173: / frequency + pad);
174:
175: return header;
176: }
177:
178: /**
179: * Reads from the input stream until it finds an MP3 header.
180: *
181: * @return the 4 byte header.
182: *
183: */
184:
185: private byte[] sync() throws IOException, IVException {
186: byte[] header = new byte[4];
187:
188: while (in.read(header, 0, 1) == 1) {
189: if ((header[0] & 0xFF) == 0xFF) {
190: if (in.read(header, 1, 1) != 1)
191: throw new IVException(Resource.INVLMP3);
192: if ((header[1] & 0xE0) == 0xE0)
193: break;
194: }
195: }
196:
197: // Read remainder of header
198:
199: if (in.read(header, 2, 2) != 2) {
200: throw new IVException(Resource.INVLMP3);
201: }
202:
203: isSynched = true;
204:
205: return header;
206: }
207:
208: /**
209: * Processes the next frame in the stream, and sets the frame properties
210: * ( mode, layer, bitrate, frequency, framesize, pad )
211: *
212: * @return the contents of the frame.
213: *
214: */
215:
216: public byte[] nextFrame() throws IOException, IVException {
217: byte[] header;
218: byte[] frame;
219:
220: header = readHeader();
221:
222: if (header == null) {
223: return null;
224: }
225:
226: int datasize = frameSize - header.length;
227:
228: frame = new byte[frameSize];
229:
230: System.arraycopy(header, 0, frame, 0, header.length);
231:
232: if (in.read(frame, header.length, datasize) == -1) {
233: throw new IVException(Resource.INVLMP3);
234: }
235:
236: return frame;
237: }
238:
239: /** Frequency accessor */
240:
241: public int getFrequency() {
242: return frequency;
243: }
244:
245: /** Stereo accessor */
246:
247: public boolean getStereo() {
248: return stereo;
249: }
250:
251: /** Samples accessor */
252:
253: public int getSamples() {
254: return samples;
255: }
256:
257: /** SamplesPerFrame accessor */
258:
259: public int getSamplesPerFrame() {
260: return samplesPerFrame;
261: }
262: }
|