/****************************************************************************** * * * Copyright (c) 1999-2003 Wimba S.A., All Rights Reserved. * * * * COPYRIGHT: * * This software is the property of Wimba S.A. * * This software is redistributed under the Xiph.org variant of * * the BSD license. * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * - Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * - Neither the name of Wimba, the Xiph.org Foundation nor the names of * * its contributors may be used to endorse or promote products derived * * from this software without specific prior written permission. * * * * WARRANTIES: * * This software is made available by the authors in the hope * * that it will be useful, but without any warranty. * * Wimba S.A. is not liable for any consequence related to the * * use of the provided software. * * * * Class: SpeexAudioFileReader.java * * * * Author: Marc GIMPEL * * * * Date: 12th July 2003 * * * ******************************************************************************/ /* $Id: SpeexAudioFileReader.java,v 1.2 2004/10/21 16:21:58 mgimpel Exp $ */ package org.xiph.speex.spi; import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.SequenceInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.URL; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.UnsupportedAudioFileException; import javax.sound.sampled.spi.AudioFileReader; import org.xiph.speex.OggCrc; /** * Provider for Speex audio file reading services. * This implementation can parse the format information from Speex audio file, * and can produce audio input streams from files of this type. * * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com) * @version $Revision: 1.2 $ */ public class SpeexAudioFileReader extends AudioFileReader { /** */ public static final int OGG_HEADERSIZE = 27; /** The size of the Speex header. */ public static final int SPEEX_HEADERSIZE = 80; /** */ public static final int SEGOFFSET = 26; /** The String that identifies the beginning of an Ogg packet. */ public static final String OGGID = "OggS"; /** The String that identifies the beginning of the Speex header. */ public static final String SPEEXID = "Speex "; /** * Obtains the audio file format of the File provided. * The File must point to valid audio file data. * @param file the File from which file format information should be * extracted. * @return an AudioFileFormat object describing the audio file format. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioFileFormat getAudioFileFormat(final File file) throws UnsupportedAudioFileException, IOException { InputStream inputStream = null; try { inputStream = new FileInputStream(file); return getAudioFileFormat(inputStream, (int) file.length()); } finally { inputStream.close(); } } /** * Obtains an audio input stream from the URL provided. * The URL must point to valid audio file data. * @param url the URL for which the AudioInputStream should be constructed. * @return an AudioInputStream object based on the audio file data pointed to * by the URL. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioFileFormat getAudioFileFormat(final URL url) throws UnsupportedAudioFileException, IOException { InputStream inputStream = url.openStream(); try { return getAudioFileFormat(inputStream); } finally { inputStream.close(); } } /** * Obtains an audio input stream from the input stream provided. * @param stream the input stream from which the AudioInputStream should be * constructed. * @return an AudioInputStream object based on the audio file data contained * in the input stream. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioFileFormat getAudioFileFormat(final InputStream stream) throws UnsupportedAudioFileException, IOException { return getAudioFileFormat(stream, AudioSystem.NOT_SPECIFIED); } /** * Return the AudioFileFormat from the given InputStream. * @param stream the input stream from which the AudioInputStream should be * constructed. * @param medialength * @return an AudioInputStream object based on the audio file data contained * in the input stream. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ protected AudioFileFormat getAudioFileFormat(final InputStream stream, final int medialength) throws UnsupportedAudioFileException, IOException { return getAudioFileFormat(stream, null, medialength); } /** * Return the AudioFileFormat from the given InputStream. Implementation. * @param bitStream * @param baos * @param mediaLength * @return an AudioInputStream object based on the audio file data contained * in the input stream. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ protected AudioFileFormat getAudioFileFormat(final InputStream bitStream, ByteArrayOutputStream baos, final int mediaLength) throws UnsupportedAudioFileException, IOException { AudioFormat format; try { // If we can't read the format of this stream, we must restore stream to // beginning so other providers can attempt to read the stream. if (bitStream.markSupported()) { // maximum number of bytes to determine the stream encoding: // Size of 1st Ogg Packet (Speex header) = OGG_HEADERSIZE + SPEEX_HEADERSIZE + 1 // Size of 2nd Ogg Packet (Comment) = OGG_HEADERSIZE + comment_size + 1 // Size of 3rd Ogg Header (First data) = OGG_HEADERSIZE + number_of_frames // where number_of_frames < 256 and comment_size < 256 (if within 1 frame) bitStream.mark(3*OGG_HEADERSIZE + SPEEX_HEADERSIZE + 256 + 256 + 2); } int mode = -1; int sampleRate = 0; int channels = 0; int frameSize = AudioSystem.NOT_SPECIFIED; float frameRate = AudioSystem.NOT_SPECIFIED; byte[] header = new byte[128]; int segments = 0; int bodybytes = 0; DataInputStream dis = new DataInputStream(bitStream); if (baos == null) baos = new ByteArrayOutputStream(128); int origchksum; int chksum; // read the OGG header dis.readFully(header, 0, OGG_HEADERSIZE); baos.write(header, 0, OGG_HEADERSIZE); origchksum = readInt(header, 22); header[22] = 0; header[23] = 0; header[24] = 0; header[25] = 0; chksum=OggCrc.checksum(0, header, 0, OGG_HEADERSIZE); // make sure its a OGG header if (!OGGID.equals(new String(header, 0, 4))) { throw new UnsupportedAudioFileException("missing ogg id!"); } // how many segments are there? segments = header[SEGOFFSET] & 0xFF; if (segments > 1) { throw new UnsupportedAudioFileException("Corrupt Speex Header: more than 1 segments"); } dis.readFully(header, OGG_HEADERSIZE, segments); baos.write(header, OGG_HEADERSIZE, segments); chksum=OggCrc.checksum(chksum, header, OGG_HEADERSIZE, segments); // get the number of bytes in the segment bodybytes = header[OGG_HEADERSIZE] & 0xFF; if (bodybytes!=SPEEX_HEADERSIZE) { throw new UnsupportedAudioFileException("Corrupt Speex Header: size=" + bodybytes); } // read the Speex header dis.readFully(header, OGG_HEADERSIZE+1, bodybytes); baos.write(header, OGG_HEADERSIZE+1, bodybytes); chksum=OggCrc.checksum(chksum, header, OGG_HEADERSIZE+1, bodybytes); // make sure its a Speex header if (!SPEEXID.equals(new String(header, OGG_HEADERSIZE+1, 8))) { throw new UnsupportedAudioFileException("Corrupt Speex Header: missing Speex ID"); } mode = readInt(header, OGG_HEADERSIZE+1+40); sampleRate = readInt(header, OGG_HEADERSIZE+1+36); channels = readInt(header, OGG_HEADERSIZE+1+48); int nframes = readInt(header, OGG_HEADERSIZE+1+64); boolean vbr = readInt(header, OGG_HEADERSIZE+1+60) == 1; // Checksum if (chksum != origchksum) throw new IOException("Ogg CheckSums do not match"); // Calculate frameSize if (!vbr) { // Frames size is a constant so: // Read Comment Packet the Ogg Header of 1st data packet; // the array table_segment repeats the frame size over and over. } // Calculate frameRate if (mode >= 0 && mode <= 2 && nframes > 0) { frameRate = ((float) sampleRate) / ((mode == 0 ? 160f : (mode == 1 ? 320f : 640f)) * ((float) nframes)); } format = new AudioFormat(SpeexEncoding.SPEEX, (float)sampleRate, AudioSystem.NOT_SPECIFIED, channels, frameSize, frameRate, false); } catch(UnsupportedAudioFileException e) { // reset the stream for other providers if (bitStream.markSupported()) { bitStream.reset(); } // just rethrow this exception throw e; } catch (IOException ioe) { // reset the stream for other providers if (bitStream.markSupported()) { bitStream.reset(); } throw new UnsupportedAudioFileException(ioe.getMessage()); } return new AudioFileFormat(SpeexFileFormatType.SPEEX, format, AudioSystem.NOT_SPECIFIED); } /** * Obtains an audio input stream from the File provided. * The File must point to valid audio file data. * @param file the File for which the AudioInputStream should be constructed. * @return an AudioInputStream object based on the audio file data pointed to * by the File. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioInputStream getAudioInputStream(final File file) throws UnsupportedAudioFileException, IOException { InputStream inputStream = new FileInputStream(file); try { return getAudioInputStream(inputStream, (int) file.length()); } catch (UnsupportedAudioFileException e) { inputStream.close(); throw e; } catch (IOException e) { inputStream.close(); throw e; } } /** * Obtains an audio input stream from the URL provided. * The URL must point to valid audio file data. * @param url the URL for which the AudioInputStream should be constructed. * @return an AudioInputStream object based on the audio file data pointed to * by the URL. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioInputStream getAudioInputStream(final URL url) throws UnsupportedAudioFileException, IOException { InputStream inputStream = url.openStream(); try { return getAudioInputStream(inputStream); } catch (UnsupportedAudioFileException e) { inputStream.close(); throw e; } catch (IOException e) { inputStream.close(); throw e; } } /** * Obtains an audio input stream from the input stream provided. * The stream must point to valid audio file data. * @param stream the input stream from which the AudioInputStream should be * constructed. * @return an AudioInputStream object based on the audio file data contained * in the input stream. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ public AudioInputStream getAudioInputStream(final InputStream stream) throws UnsupportedAudioFileException, IOException { return getAudioInputStream(stream, AudioSystem.NOT_SPECIFIED); } /** * Obtains an audio input stream from the input stream provided. * The stream must point to valid audio file data. * @param inputStream the input stream from which the AudioInputStream should * be constructed. * @param medialength * @return an AudioInputStream object based on the audio file data contained * in the input stream. * @exception UnsupportedAudioFileException if the File does not point to * a valid audio file data recognized by the system. * @exception IOException if an I/O exception occurs. */ protected AudioInputStream getAudioInputStream(final InputStream inputStream, final int medialength) throws UnsupportedAudioFileException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(128); AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, baos, medialength); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); SequenceInputStream sequenceInputStream = new SequenceInputStream(bais, inputStream); return new AudioInputStream(sequenceInputStream, audioFileFormat.getFormat(), audioFileFormat.getFrameLength()); } /** * Converts Little Endian (Windows) bytes to an int (Java uses Big Endian). * @param data the data to read. * @param offset the offset from which to start reading. * @return the integer value of the reassembled bytes. */ private static int readInt(final byte[] data, final int offset) { return (data[offset] & 0xff) | ((data[offset+1] & 0xff) << 8) | ((data[offset+2] & 0xff) << 16) | (data[offset+3] << 24); // no & 0xff at the end to keep the sign } }