/*
 * @(#)MP3ElementaryInputStream.java	1.0	2011-01-03
 *
 * Copyright © 2010 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.media.mp3;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.util.HashMap;
import javax.sound.sampled.AudioFormat;

/**
 * Facilitates reading of an MP3 elementary stream frame by frame.
 * <p>
 * An MP3 frame has a 32-bit header with the following contents in big endian
 * order:
 * <ul>
 * <li>bit 31-21, MP3 Sync Word, all bits must be set</li>
 * <li>bit 20-19, Version, 00=MPEG 2.5,01=reserved,10=MPEG 2, 11=MPEG 1</li>
 * <li>bit 18-17, Layer, 00=reserved, 01=layer 3, 10=layer 2, 11=layer 1</li>
 * <li>bit 16, Error protection, 0=16 bit CRC follows header, 1=Not protected</li>
 * <li>bit 15-12, Bit Rate in kbps, interpretation depends on version and layer</li>
 * <li>bit 11-10, Frequency, interpretation depends on version</li>
 * <li>bit 9, Pad Bit, 0=frame is not padded, 1=frame is padded to exactly fit the bit rate</li>
 * <li>bit 8, Private bit, only informative</li>
 * <li>bit 7-6, Channel Mode, 00=stereo, 01=joint stereo, 10=dual channel (2 mono channels), 11=single channel (mono)</li>
 * <li>bit 5-4, Mode Extension (only used with Joint Stereo), interpretation depends on version and layer</li>
 * <li>bit 3, Copyright, 0=not copyrighted, 1=copyrighted</li>
 * <li>bit 2, Original, 0=Copy of original media,1=original media</li>
 * <li>bit 1-0, Emphasis, 00=none,01=50/15ms,10=reserved,11=CCIT J.17</li>
 * </ul>
 * <p>
 * Reference:<br>
 * <a href="http://en.wikipedia.org/wiki/MP3">http://en.wikipedia.org/wiki/MP3</a><br>
 * <a href="http://www.datavoyage.com/mpgscript/mpeghdr.htm">http://www.datavoyage.com/mpgscript/mpeghdr.htm</a><br>
 * <a href="http://www.mp3-tech.org/programmer/frame_header.html">http://www.mp3-tech.org/programmer/frame_header.html</a><br>
 * <a href="http://lame.sourceforge.net/tech-FAQ.txt">http://lame.sourceforge.net/tech-FAQ.txt</a><br>
 * <a href0"http://www.altera.com/literature/dc/1.4-2005_Taiwan_2nd_SouthernTaiwanU-web.pdf">http://www.altera.com/literature/dc/1.4-2005_Taiwan_2nd_SouthernTaiwanU-web.pdf</a><br>
 *
 * @author Werner Randelshofer
 * @version 1.0 2011-01-03 Created.
 */
public class MP3ElementaryInputStream extends FilterInputStream {

		/** Defines the "MP3" encoding. */
		public final static AudioFormat.Encoding MP3 = new AudioFormat.Encoding("MP3");
		private Frame frame;
		private long pos;
		private final static int[][] BIT_RATES = { // All values are in kbps
				// V1 - MPEG Version 1
				// V2 - MPEG Version 2 and Version 2.5
				// L1 - Layer I
				// L2 - Layer II
				// L3 - Layer III
				//
				// V1L1, V1L2, V1L3, V2L1, V2L2&L3
				{-1, -1, -1, -1, -1}, // free
				{32, 32, 32, 32, 8},
				{64, 48, 40, 48, 16},
				{96, 56, 48, 56, 24},
				{128, 64, 56, 64, 32},
				{160, 80, 64, 80, 40},
				{192, 96, 80, 96, 48},
				{224, 112, 96, 112, 56},
				{256, 128, 112, 128, 64},
				{288, 160, 128, 144, 80},
				{320, 192, 160, 160, 96},
				{352, 224, 192, 176, 112},
				{384, 256, 224, 192, 128},
				{416, 320, 256, 224, 144},
				{448, 384, 320, 256, 160},
				{-2, -2, -2, -2, -2}, // bad
		};
		private final static int[][] SAMPLE_RATES = { // All values are in Hz
				// V1 - MPEG Version 1
				// V2 - MPEG Version 2
				// V25 - MPEG Version 2.5
				//
				// V1, V2, V25
				{44100, 22050, 11025},
				{48000, 24000, 12000},
				{32000, 16000, 8000},
				{-1, -1, -1}, // reserved
		};

		/** An elementary frame. */
		public static class Frame {

				/** 32-bit header. */
				private int header;
				/** 16-bit CRC. */
				private int crc;
				/** The size of the data in the frame. */
				private int bodySize;
				/** The offset of the data in the frame. */
				private long bodyOffset;

				/** Creates a new frame.
				 *
				 * @param header The 32-bit Frame header
				 */
				public Frame(int header) {
						this.header = header;
				}

				public int getHeader() {
						return header;

				}

				/** Returns the version number: 1=MPEG 1, 2=MPEG 2, 25=MPEG 2.5; -1=unknown. */
				public int getVersion() {
						switch (getVersionCode()) {
								case 0:
										return 25;
								case 2:
										return 2;
								case 3:
										return 1;
								default:
										return -1;
						}
				}

				/** Returns the raw version code as it is stored in the
				 * header. 3=MPEG 1, 2=MPEG 2, 1=reserved, 0=MPEG 2.5. */
				public int getVersionCode() {
						return (header >>> 19) & 3;
				}

				/** Returns the layer number.
				 * 1=Layer I, 2=Layer II, 3=Layer III, -1=unknown. */
				public int getLayer() {
						switch (getLayerCode()) {
								case 1:
										return 3;
								case 2:
										return 2;
								case 3:
										return 1;
								default:
										return -1;
						}
				}

				/** Returns the raw layer code as it is stored in the header.
				 * 3=Layer I, 2=Layer II, 1=Layer III, 0=reserved. */
				public int getLayerCode() {
						return (header >>> 17) & 3;
				}

				/** Returns the bitrate of the frame. Returns -1 if unknown.
				 */
				public int getBitRate() {
						if (getVersion() < 0 || getLayer() < 0) {
								return -1;
						}
						int v = getVersion() == 1 ? 0 : 3;
						int l = getVersion() == 1 ? getLayer() - 1 : (getLayer() == 1 ? 0 : 1);
						return BIT_RATES[getBitRateCode()][v + l];
				}

				/** Returns the raw bitrate code as it is stored in the header.
				 */
				public int getBitRateCode() {
						return (header >>> 12) & 15;
				}

				/** Returns true if this frame has a CRC. */
				public boolean hasCRC() {
						return ((header >>> 16) & 1) == 0;
				}

				/** Returns the CRC of this frame. The value is only valid if hasCRC() returns true. */
				public int getCRC() {
						return crc;
				}

				public boolean hasPadding() {
						return ((header >>> 9) & 1) == 1;
				}

				/** Returns the sample rate in Hz.
				 * Returns -1 if unknown.
				 */
				public int getSampleRate() {
						if (getVersion() < 0 || getLayer() < 0) {
								return -1;
						}
						int v = getVersion() == 25 ? 2 : getVersion() - 1;
						return SAMPLE_RATES[getSampleRateCode()][v];
				}

				/** Returns the raw sample rate code as it is stored in the header.
				 */
				public int getSampleRateCode() {
						return (header >>> 10) & 3;
				}

				/** Returns the number of samples in the frame.
				 * It is constant and always 384 samples for Layer I and 1152 samples
				 * for Layer II and Layer III.
				 * Returns -1 if unknown.
				 */
				public int getSampleCount() {
						if (getLayer() < 0) {
								return -1;
						}
						return (getLayer() == 1 ? 192 : 576) * getChannelCount();
				}

				/** Returns the number of channels.
				 * @return 1=mono, 2=stereo, joint stereo or dual channel.
				 */
				public int getChannelCount() {
						return getChannelModeCode() == 3 ? 1 : 2;
				}

				/** Returns the sample size in bits. Always 16 bit per sample. */
				public int getSampleSize() {
						return 16;
				}

				/** Returns the raw channel mode as stored in the header.
				 *
				 * @return 0=stereo, 1=joint stereo, 2=dual channel, 3=single channel (mono).
				 */
				public int getChannelModeCode() {
						return (header >>> 6) & 3;
				}

				/** Returns the frame header as a byte array. */
				public byte[] headerToByteArray() {
						byte[] data = new byte[hasCRC() ? 6 : 4];
						headerToByteArray(data, 0);
						return data;
				}

				/** Writes the frame header into the specified byte array.
				 * Returns the number of bytes written.
				 */
				public int headerToByteArray(byte[] data, int offset) {
						if (data.length - offset < getHeaderSize()) {
								throw new IllegalArgumentException("data array is too small");
						}
						data[offset + 0] = (byte) (header >>> 24);
						data[offset + 1] = (byte) (header >>> 16);
						data[offset + 2] = (byte) (header >>> 8);
						data[offset + 3] = (byte) (header >>> 0);
						if (hasCRC()) {
								data[offset + 4] = (byte) (crc >>> 8);
								data[offset + 5] = (byte) (crc >>> 0);
						}
						return getHeaderSize();
				}

				/** Writes the frame header into the specified output stream. */
				public void writeHeader(OutputStream out) throws IOException {
						out.write((header >>> 24));
						out.write((header >>> 16));
						out.write((header >>> 8));
						out.write((header >>> 0));
						if (hasCRC()) {
								out.write((crc >>> 8));
								out.write((crc >>> 0));
						}
				}

				/** Returns the offset of the frame in the input stream. */
				public long getFrameOffset() {
						return getBodyOffset() - getHeaderSize();
				}

				/** Returns the size of the frame in bytes.
				 * This size includes the header, the data and the padding.
				 */
				public int getFrameSize() {
						return getHeaderSize() + getBodySize();
				}

				/** Returns the offset of the header in the input stream. */
				public long getHeaderOffset() {
						return getFrameOffset();
				}

				/** Returns the size of the header in bytes. */
				public int getHeaderSize() {
						return hasCRC() ? 6 : 4;
				}

				/** Returns the offset of the side info in the input stream. */
				public long getSideInfoOffset() {
						return bodyOffset;
				}

				/** Returns the size of the side info in bytes.
				 * It is 17 bytes long in a single channel frame and 32 bytes in dual
				 * channel or stereo channel.
				 */
				public int getSideInfoSize() {
						return getChannelCount() == 1 ? 17 : 32;
				}

				/** Returns the offset of the frame body in the input stream. */
				public long getBodyOffset() {
						return bodyOffset;
				}

				/** Returns the size of the frame body in bytes.
				 * The body includes the side info, the audio data, and the padding.
				 */
				public int getBodySize() {
						return bodySize;
				}

				/** Padding is used to fit the bit rates exactly.
				 * For an example: 128k 44.1kHz layer II uses a lot of 418 bytes and
				 * some of 417 bytes long frames to get the exact 128k bitrate. For
				 * Layer I slot is 32 bits long, for Layer II and Layer III slot is 8
				 * bits long. */
				public int getPaddingSize() {
						if (hasPadding()) {
								return getLayer() == 1 ? 4 : 1;
						}
						return 0;
				}

				private float getFrameRate() {
						return (float) getSampleRate() / getSampleCount();
				}
		}

		public MP3ElementaryInputStream(File file) throws IOException {
				super(new PushbackInputStream(new BufferedInputStream(new FileInputStream(file)), 6));
		}

		public MP3ElementaryInputStream(InputStream in) {
				super(new PushbackInputStream(in, 6));
		}

		/** Gets the next frame from the input stream.
		 * Positions the stream in front of the frame header.
		 */
		public Frame getNextFrame() throws IOException {
				while (frame != null && pos < frame.getBodyOffset() + frame.getBodySize()) {
						long skipped = skip(frame.getBodyOffset() + frame.getBodySize() - pos);
						if (skipped < 0) {
								break;
						}
				}

				while (true) {
						int b = read0();
						if (b == -1) {
								frame = null;
								break;
						} else if (b == 255) {
								int h0 = b;
								int h1 = read0();
								if (h1 != -1 && (h1 & 0xe0) == 0xe0) {
										int h2 = read0();
										int h3 = read0();
										if (h3 != -1) {
												frame = new Frame((h0 << 24) | (h1 << 16) | (h2 << 8) | h3);
												if (frame.getBitRate() == -1 || frame.getLayer() == -1 || frame.getSampleRate() == -1) {
														// => the header is corrupt: push back 3 bytes
														PushbackInputStream pin = (PushbackInputStream) in;
														pin.unread(h3);
														pin.unread(h2);
														pin.unread(h1);
														pos -= 3;
														continue;
												}

												int crc0 = -1, crc1 = -1;
												if (frame.hasCRC()) {
														crc0 = read0();
														crc1 = read0();
														if (crc1 == -1) {
																throw new EOFException();
														}
														frame.crc = (crc0 << 8) | crc1;
												}
												frame.bodyOffset = pos;
												if (frame.getBitRate() <= 0 || frame.getSampleRate() <= 0) {
														frame.bodySize = 0;
												} else if (frame.getLayer() == 1) {
														frame.bodySize = (int) ((12000L * frame.getBitRate() / frame.getSampleRate()) * 4) - frame.getHeaderSize() + frame.getPaddingSize();
												} else if (frame.getLayer() == 2 || frame.getLayer() == 3) {
														if (frame.getChannelCount() == 1) {
																frame.bodySize = (int) (72000L * frame.getBitRate() / (frame.getSampleRate() + frame.getPaddingSize())) - frame.getHeaderSize() + frame.getPaddingSize();
														} else {
																frame.bodySize = (int) (144000L * frame.getBitRate() / (frame.getSampleRate() + frame.getPaddingSize())) - frame.getHeaderSize() + frame.getPaddingSize();
														}
												}
												PushbackInputStream pin = (PushbackInputStream) in;
												if (frame.hasCRC()) {
														pin.unread(crc1);
														pin.unread(crc0);
														pos -= 2;
												}
												pin.unread(h3);
												pin.unread(h2);
												pin.unread(h1);
												pin.unread(h0);
												pos -= 4;
												assert pos == frame.getFrameOffset() : pos + "!=" + frame.getFrameOffset();
												break;
										}
								}
						}
				}
				return frame;
		}

		/** Returns the current frame. */
		public Frame getFrame() {
				return frame;
		}

		/** Gets the format of the current frame.
		 * Returns null if the input stream is not positioned on a frame, or the frame
		 * is not valid.
		 * @return AudioFormat of current frame or null.
		 */
		public AudioFormat getFormat() {
				if (frame == null) {
						return null;
				} else {
						HashMap<String, Object> properties = new HashMap<String, Object>();
						properties.put("vbr", true);
						return new AudioFormat(MP3, //
										frame.getSampleRate(), frame.getSampleSize(), frame.getChannelCount(),//
										frame.getFrameSize(), frame.getFrameRate(), true, properties);
				}
		}

		private int read0() throws IOException {
				int b = super.read();
				if (b != -1) {
						pos++;
				}
				return b;
		}

		/** Reads a byte from the current frame (its header and its data).
		 * Returns -1 on an attempt to read past the end of the frame.
		 */
		@Override
		public int read() throws IOException {
				if (frame == null || pos >= frame.getBodyOffset() + frame.getBodySize()) {
						return -1;
				}
				return read0();
		}

		/** Reads up to {@code len} bytes from the current frame (its header and its data).
		 * May read less then {@code len} bytes. Returns the actual number of bytes read.
		 * Returns -1 on an attempt to read past the end of the frame.
		 */
		@Override
		public int read(byte[] b, int off, int len) throws IOException {
				if (frame == null) {
						return -1;
				}
				int maxlen = (int) (frame.getBodyOffset() + frame.getBodySize() - pos);
				if (maxlen < 1) {
						return -1;
				}
				len = Math.min(maxlen, len);
				int count = super.read(b, off, len);
				if (count != -1) {
						pos += count;
				}
				return count;
		}

		/**
		 * Reads {@code b.length} bytes from the current frame (its header and its data).
		 * @throws {@code IOException} on an attempt to read past the end of the frame.
		 */
		public final void readFully(byte b[]) throws IOException {
				readFully(b, 0, b.length);
		}

		/**
		 * Reads {@code len} bytes from the current frame (its header and its data).
		 * @throws {@code IOException} on an attempt to read past the end of the frame.
		 */
		public final void readFully(byte b[], int off, int len) throws IOException {
				if (len < 0) {
						throw new IndexOutOfBoundsException();
				}
				int n = 0;
				while (n < len) {
						int count = in.read(b, off + n, len - n);
						if (count < 0) {
								throw new EOFException();
						}
						n += count;
						pos += count;
				}
		}

		/** Skips up to {@code n} bytes from the current frame (its header and its data).
		 * Returns the actual number of bytes that have been skipped.
		 * Returns -1 on an attempt to skip past the end of the frame.
		 */
		@Override
		public long skip(long n) throws IOException {
				if (frame == null) {
						return -1;
				}
				int maxlen = (int) (frame.getBodyOffset() + frame.getBodySize() - pos);
				if (maxlen < 1) {
						return -1;
				}
				n = Math.min(maxlen, n);
				long skipped = in.skip(n);
				if (skipped > 0) {
						pos += skipped;
				}
				return skipped;
		}

		/**
		 * Returns the current position in the stream.
		 * @return The stream position.
		 */
		public long getStreamPosition() {
				return pos;
		}
}
