/* * @(#)MP3AudioInputStream.java 1.0 2011-01-01 * * 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.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; /** * {@code AudioInputStream} adapter for {@link MP3ElementaryInputStream}. * <p> * Unlike a regular audio input stream, an MP3 audio input stream can have a * variable frame size and can change its encoding method in mid-stream. * Therefore method getFormat can return different values for each frame, * and mark/reset is not supported, and method getFrameLength can not return * the total number of frames in the stream. * * @author Werner Randelshofer * @version 1.0 2011-01-01 Created. */ public class MP3AudioInputStream extends AudioInputStream { private MP3ElementaryInputStream in; /** Creates an MP3AudioInputStream and reads the stream until the first * frame is reached. * * @param file A File. * @throws IOException if the file does not contain an MP3 elementary stream. */ public MP3AudioInputStream(File file) throws IOException { this(new BufferedInputStream(new FileInputStream(file))); } /** Creates an MP3AudioInputStream and reads the stream until the first * frame is reached. * * @param in An InputStream. * @throws IOException if the stream does not contain an MP3 elementary stream. */ public MP3AudioInputStream(InputStream in) throws IOException { // Feed superclass with nonsense - we override all methods anyway. super(null, new AudioFormat(MP3ElementaryInputStream.MP3, 44100, 16, 2, 626, 44100f / 1152f, true), -1); this.in = new MP3ElementaryInputStream(in); if (this.in.getNextFrame() == null) { throw new IOException("Stream is not an MP3 elementary stream"); } } @Override public int available() throws IOException { return in.available(); } /** Returns the format of the <i>next</i> frame. Returns null if the stream * is not positioned inside a frame. */ @Override public AudioFormat getFormat() { return in.getFormat(); } /** Returns -1 because we don't know how many frames the stream has. */ @Override public long getFrameLength() { return -1; } @Override public void close() throws IOException { in.close(); } /** Throws an IOException, because the frame size is greater than 1. */ @Override public int read() throws IOException { throw new IOException("cannot read a single byte if frame size > 1"); } /** Reads some number of bytes from the audio input stream and stores them * into the buffer array b. The number of bytes actually read is returned as * an integer. This method blocks until input data is available, the end of * the stream is detected, or an exception is thrown. * This method will always read an integral number of frames. If the length * of the array is not an integral number of frames, a maximum of * {@code b.length - (b.length % frameSize)} bytes will be read. * * @return Returns the total number of bytes read into the buffer, or -1 if there is * no more data because the end of the stream has been reached. */ @Override public int read(byte[] b, int off, int len) throws IOException { if (in.getFrame() == null && in.getNextFrame() == null) { return -1; } if (in.getStreamPosition() != in.getFrame().getFrameOffset()) { if (in.getNextFrame() == null) { return -1; } } int bytesRead = 0; int frameSize = in.getFrame().getFrameSize(); while (len >= frameSize) { in.readFully(b, off, frameSize); len -= frameSize; bytesRead += frameSize; off += frameSize; if (in.getNextFrame() == null) { break; } frameSize = in.getFrame().getFrameSize(); } return bytesRead; } @Override public long skip(long n) throws IOException { return in.skip(n); } @Override public void mark(int readlimit) { // can't do anything } @Override public boolean markSupported() { return false; } @Override public void reset() throws IOException { throw new IOException("mark/reset not supported"); } }