/******************************************************************************
* *
* 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
}
}