| MpegAudioHeader.java |
/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.media.format.audio.mpeg;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.HashMap;
import java.util.Map;
import javax.emb.FormatSyntaxException;
import javax.emb.MediaException;
import javax.emb.MediaHeader;
/**
* Represents an MPEG Audio Header
*
* @version <tt>$Revision 1.1 $</tt>
* @author <a href="mailto:ogreen@users.sourceforge.net">Owen Green</a>
*/
public class MpegAudioHeader implements MediaHeader
{
/*
* Lookups for bitrates at various combinations of MPEG version and layer
*/
//Version 1, Layer I
private final static int[] v1L1BitRates =
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 };
//Version 1, Layer II
private final static int[] v1L2BitRates =
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 };
//Version 1, Layer III
private final static int[] v1L3BitRates =
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };
//Version 2 and 2.5, Layer I
private final static int[] v2L1BitRates =
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 411, 460, 476, 192, 224, 256 };
//Version 2 and 2.5, Layer II and III
private final static int[] v2L23BitRates =
{ 0, 8, 16, 24, 32, 40, 28, 56, 64, 80, 96, 112, 128, 144, 160 };
/*
* Lookups for sampling rates for each version
*/
private final static int[] v1SampleRates = { 44100, 48000, 32000 };
private final static int[] v2SampleRates = { 22050, 24000, 16000 };
private final static int[] v25SampleRates = { 11025, 12000, 8000 };
/*
* field name keys for fields map
*/
private final static String VERSION_KEY = "version";
private final static String LAYER_KEY = "layer";
private final static String BITRATE_KEY = "bitRate";
private final static String SAMPLERATE_KEY = "samplingRate";
private final static String CHANNELMODE_KEY = "channelMode";
private final static String COPYRIGHT_KEY = "copyright";
private final static String ORGINAL_KEY = "original";
//Map to hold fields in
private final Map fieldMap = new HashMap(7);
private final PushbackInputStream content;
/**
* Constructs a new instance and attempts to extract an MPEG audio header
* from <code>mediaObject</code>
*
* @param mediaObject the media to extract an MPEG Audio header from
* @throws MediaException if an there is an error accessing the media
* content
* @throws FormatSyntaxException if invalid data for this format is
* encoutered
* @throws RemoteException if an RMI error occurs
*/
public MpegAudioHeader(InputStream data)
throws MediaException, FormatSyntaxException
{
try
{
content = new PushbackInputStream(data, 4);
int headerPos = findHeaderStart(content, 0);
if (headerPos != -1)
{
byte[] header = new byte[4];
content.read(header);
readHeader(header);
}
else
{
throw new FormatSyntaxException("MPEG Audio header could not be found; this is not valid MPEG Audio data");
}
}
catch (IOException ex)
{
throw new MediaException(ex.getMessage());
}
}
/**
* Returns the bit rate for this media in kbps.
*
* @return int the bit rate for this media
*/
public int getBitrate()
{
return ((Integer) fieldMap.get(BITRATE_KEY)).intValue();
}
/**
* Returns the channel mode for this MPEG audio.
*
* @return MpegAudioFormat.ChannelMode the channel mode for this MPEG audio
* @see MpegAudioFormat.ChannelMode
*/
public MpegAudioFormat.ChannelMode getChannelMode()
{
return (MpegAudioFormat.ChannelMode) fieldMap.get(CHANNELMODE_KEY);
}
/**
* Indicates whether this is copyrighted material or not
*
* @return boolean <code>true</code> if this is copyrighted material
*/
public boolean isCopyright()
{
return ((Boolean) fieldMap.get(COPYRIGHT_KEY)).booleanValue();
}
/**
* Returns the MPEG layer of this media
*
* @return MpegAudioFormat.Layer the MPEG audio layer of this media
* @see MpegAudioFormat
* @see MpegAudioFormat.Layer
*/
public MpegAudioFormat.Layer getLayer()
{
return (MpegAudioFormat.Layer) fieldMap.get(LAYER_KEY);
}
/**
* Indicates whether this is the orginal media, or a copy
*
* @return boolean <code>true</code> if this is the orginal media
*/
public boolean isOriginal()
{
return ((Boolean) fieldMap.get(ORGINAL_KEY)).booleanValue();
}
/**
* Returns the sampling rate of this audio in Hz
*
* @return int the sampling rate of this audio
*/
public int getSamplerate()
{
return ((Integer) fieldMap.get(SAMPLERATE_KEY)).intValue();
}
/**
* Returns the MPEG version of this media
*
* @return MpegAudioFormat.Version the MPEG version of this media
* @see MpegAudioFormat.Version
*/
public MpegAudioFormat.Version getVersion()
{
return (MpegAudioFormat.Version) fieldMap.get(VERSION_KEY);
}
/**
* Provides a list of the field names in this header
*
* @return a list of the field names in this header
* @see javax.emb.MediaHeader#getFieldNames()
*/
public String[] getFieldNames()
{
return (String[]) fieldMap.keySet().toArray(new String[0]);
}
/**
* Retreives a field by name. In the case of primitive fields, the object
* equivalent is returned (e.g. <code>int</code> becomes <code>java.lang.Integer</code>
*
* @param fieldname the name of the field to access
* @return the requested field, or <code>null</code> if the field name
* doesn't exist
* @see javax.emb.MediaHeader#getField(String)
*/
public Object getField(String fieldname)
{
return fieldMap.get(fieldname);
}
/**
* Finds the start of the next MPEG audio header after <code>offset</code>,
* skipping any ID3 tag.
*
* @param mediaObject the {@link javax.emb.Media}to search for a header
* @param the offset to start searching at
* @return the position in the <code>Media</code> of the next header, or
* <code>-1</code> if no header is found
*/
private int findHeaderStart(PushbackInputStream content, int offset)
throws MediaException
{
try
{
if (offset == 0)
{
byte[] id3 = new byte[4];
content.read(id3);
String id3Test = new String(id3);
content.unread(id3);
//see if we've got an ID3 tag at the begining
if (id3Test.equals("ID3"))
{
ID3Tag id3Tag = new ID3Tag(content);
offset += id3Tag.getSize();
}
}
byte[] headerTest = new byte[2];
while ((content.read(headerTest)) != -1)
{
//check for frame sync block - 1111 1111 111
if (((headerTest[0] & 0xFF) == 0xFF)
&& ((headerTest[1] & 0xE0) == 0xE0))
{
content.unread(headerTest);
return offset;
}
content.unread(headerTest[1]);
offset++;
}
}
catch (IOException e)
{
throw new MediaException(e);
}
return -1;
}
/**
* Reads and interprets an MPEG audio header chunk
*
* @param header the header data - at least 4 bytes long
* @throws FormatSyntaxException if illegal data is encountered
* @throws IllegalArgumentException if <code>header</code> is less than 4
* elements long
*/
private void readHeader(byte[] header) throws FormatSyntaxException
{
if (header.length < 4)
{
throw new IllegalArgumentException("Not enough header data");
}
fieldMap.put(VERSION_KEY, getVersion(header[1]));
fieldMap.put(LAYER_KEY, getLayer(header[1]));
fieldMap.put(BITRATE_KEY, new Integer(getBitrate(header[2])));
fieldMap.put(SAMPLERATE_KEY, new Integer(getSampleRate(header[2])));
fieldMap.put(CHANNELMODE_KEY, getChannelMode(header[3]));
fieldMap.put(COPYRIGHT_KEY, new Boolean(getCopyright(header[3])));
fieldMap.put(ORGINAL_KEY, new Boolean(getOriginal(header[3])));
}
/**
* Extracts the MPEG Version from an MPEG header
*
* @return the MPEG version
* @param versionByte the byte of the header that contains the version
* information
* @throws FormatSyntaxException if an illegal version ID is encountered
* @see MpegAudioFormat.Version
*/
private MpegAudioFormat.Version getVersion(byte versionByte)
throws FormatSyntaxException
{
switch (versionByte & 0x18) //mask with 00011000
{
case 0x00 :
return MpegAudioFormat.Version.MPEG25;
case 0x10 :
return MpegAudioFormat.Version.MPEG2;
case 0x18 :
return MpegAudioFormat.Version.MPEG1;
default :
throw new FormatSyntaxException("Could not determine MPEG Audio version");
}
}
/**
* Extracts the MPEG Layer from an MPEG header byte
*
* @return the MPEG Audio Layer
* @param layerByte the byte that contains layer information
* @throws FormatSyntaxException if an illegal Layer value is encountered
* @see MpegAudioFormat.Layer
*/
private MpegAudioFormat.Layer getLayer(byte layerByte)
throws FormatSyntaxException
{
switch (layerByte & 0x06) //mask with 0000 0110
{
case 0x02 :
return MpegAudioFormat.Layer.LAYERIII;
case 0x04 :
return MpegAudioFormat.Layer.LAYERII;
case 0x06 :
return MpegAudioFormat.Layer.LAYERI;
default :
throw new FormatSyntaxException("Could not determine MPEG Audio version");
}
}
/**
* Estabilshes whether this MPEG frame has a CRC checksum after the header
* (not currently used)
*
* @param crcByte the header byte containing the CRC indicator bit
*/
private boolean getCRC(byte crcByte)
{
return (crcByte & 0x1) == 0;
}
/**
* Extracts the MPEG bit rate from an MPEG header byte
*
* @param bitrateByte the header byte containing bit rate information
* @throws FormatSyntaxException if an illegal bit rate value is encountered
* @throws IllegalStateException if the Version and Layer have not yet been
* extracted
*/
private int getBitrate(byte bitrateByte) throws FormatSyntaxException
{
int bitrateKey = (bitrateByte & 0xF0) >>> 4;
if (0xF == bitrateKey)
{
throw new FormatSyntaxException("Illegal bitrate specified");
}
if (MpegAudioFormat.Version.MPEG1 == getVersion())
{
if (MpegAudioFormat.Layer.LAYERI == getLayer())
{
return v1L1BitRates[bitrateKey];
}
if (MpegAudioFormat.Layer.LAYERII == getLayer())
{
return v1L2BitRates[bitrateKey];
}
if (MpegAudioFormat.Layer.LAYERIII == getLayer())
{
return v1L3BitRates[bitrateKey];
}
}
if ((MpegAudioFormat.Version.MPEG2 == getVersion())
|| (MpegAudioFormat.Version.MPEG25 == getVersion()))
{
if (MpegAudioFormat.Layer.LAYERI == getLayer())
{
return v2L1BitRates[bitrateKey];
}
if ((MpegAudioFormat.Layer.LAYERII == getLayer())
|| (MpegAudioFormat.Layer.LAYERIII == getLayer()))
{
return v2L23BitRates[bitrateKey];
}
}
throw new IllegalStateException("Version and layer must be determined before extracting bitrate");
}
/**
* Extracts the MPEG sample rate from an MPEG header byte
*
* @param samplerateByte the header byte containing sample rate information
* @throws FormatSyntaxException if an illegal sample rate value is
* encountered
* @throws IllegalStateException if the Version and Layer have not yet been
* extracted
*/
private int getSampleRate(byte samplerateByte) throws FormatSyntaxException
{
int samplerateKey = (samplerateByte & 0xC) >>> 2;
if (samplerateKey > 2)
{
throw new FormatSyntaxException("Illegal sampling rate specified");
}
if (MpegAudioFormat.Version.MPEG1 == getVersion())
{
return v1SampleRates[samplerateKey];
}
if (MpegAudioFormat.Version.MPEG2 == getVersion())
{
return v2SampleRates[samplerateKey];
}
if (MpegAudioFormat.Version.MPEG25 == getVersion())
{
return v25SampleRates[samplerateKey];
}
throw new IllegalStateException("Version must be determined before extracting sample rate");
}
/**
* Extracts the MPEG channel mode from a MPEG header byte
*
* @param channelModeByte the byte containing channel mode information
* @throws FormatSyntaxException if an illegal channel mode value is
* encountered
*/
private MpegAudioFormat.ChannelMode getChannelMode(byte channelModeByte)
throws FormatSyntaxException
{
switch (channelModeByte & 0xC0)
{
case 0x00 :
return MpegAudioFormat.ChannelMode.STEREO;
case 0x40 :
return MpegAudioFormat.ChannelMode.JOINT_STEREO;
case 0x80 :
return MpegAudioFormat.ChannelMode.DUAL_CHANNEL;
case 0xC0 :
return MpegAudioFormat.ChannelMode.SINGLE_CHANNEL;
default :
throw new FormatSyntaxException("Illegal Channel Mode encountered");
}
}
/**
* Extracts the copyright bit from an MPEG header byte
*/
private boolean getCopyright(byte copyrightByte)
{
return (copyrightByte & 8) != 0;
}
private boolean getOriginal(byte originalByte)
{
return (originalByte & 4) != 0;
}
}| MpegAudioHeader.java |