blob: 97712f1d40c20dc39b5f09fa1b24e1da74c5656b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tika.parser.mp3;
import java.io.IOException;
import java.io.InputStream;
import org.apache.tika.exception.TikaException;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* An Audio Frame in an MP3 file. These come after the ID3v2 tags in the file.
* Currently, only the header is processed, not the raw audio data.
*/
public class AudioFrame implements MP3Frame {
/** Constant for the MPEG version 1. */
public static final int MPEG_V1 = 3;
/** Constant for the MPEG version 2. */
public static final int MPEG_V2 = 2;
/** Constant for the MPEG version 2.5. */
public static final int MPEG_V2_5 = 0;
/** Constant for audio layer 1. */
public static final int LAYER_1 = 3;
/** Constant for audio layer 2. */
public static final int LAYER_2 = 2;
/** Constant for audio layer 3. */
public static final int LAYER_3 = 1;
private final String version;
private final int versionCode;
private final int layer;
private final int sampleRate;
private final int channels;
private final int bitRate;
private final int length;
private final float duration;
public String getVersion() {
return version;
}
/**
* Get the sampling rate, in Hz
*/
public int getSampleRate() {
return sampleRate;
}
/**
* Get the number of channels (1=mono, 2=stereo)
*/
public int getChannels() {
return channels;
}
/**
* Get the version code.
* @return the version code (one of the {@code MPEG} constants)
*/
public int getVersionCode()
{
return versionCode;
}
/**
* Get the audio layer code.
* @return the audio layer (one of the {@code LAYER} constants)
*/
public int getLayer()
{
return layer;
}
/**
* Get the bit rate in bit per second.
* @return the bit rate
*/
public int getBitRate()
{
return bitRate;
}
/**
* Returns the frame length in bytes.
* @return the frame length
*/
public int getLength()
{
return length;
}
/**
* Returns the duration in milliseconds.
* @return the duration
*/
public float getDuration()
{
return duration;
}
/**
* Does this appear to be a 4 byte audio frame header?
*/
public static boolean isAudioHeader(int h1, int h2, int h3, int h4) {
if (h1 == -1 || h2 == -1 || h3 == -1 || h4 == -1) {
return false;
}
// Check for the magic 11 bits set at the start
// Note - doesn't do a CRC check
if (h1 == 0xff && (h2 & 0x60) == 0x60) {
return true;
}
return false;
}
/**
* @deprecated Use the constructor which is passed all values directly.
*/
@Deprecated
public AudioFrame(InputStream stream, ContentHandler handler)
throws IOException, SAXException, TikaException {
this(-2, -2, -2, -2, stream);
}
/**
* @deprecated Use the constructor which is passed all values directly.
*/
@Deprecated
public AudioFrame(int h1, int h2, int h3, int h4, InputStream in)
throws IOException {
if (h1 == -2 && h2 == -2 && h3 == -2 && h4 == -2) {
h1 = in.read();
h2 = in.read();
h3 = in.read();
h4 = in.read();
}
if (isAudioHeader(h1, h2, h3, h4)) {
layer = (h2 >> 1) & 0x03;
versionCode = (h2 >> 3) & 0x03;
version = generateVersionStr(versionCode, layer);
int rateCode = (h3 >> 2) & 0x03;
int rate;
switch (rateCode) {
case 0:
rate = 11025;
break;
case 1:
rate = 12000;
break;
default:
rate = 8000;
}
if (versionCode == MPEG_V2) {
rate *= 2;
} else if(versionCode == MPEG_V1) {
rate *= 4;
}
sampleRate = rate;
int chans = h4 & 0x192;
if (chans < 3) {
// Stereo, joint stereo, dual channel
channels = 2;
} else {
channels = 1;
}
bitRate = 0;
duration = 0;
length = 0;
} else {
throw new IllegalArgumentException("Magic Audio Frame Header not found");
}
}
/**
*
* Creates a new instance of {@code AudioFrame} and initializes all properties.
* @param mpegVersion the code for the MPEG version
* @param layer the code for the layer
* @param bitRate the bit rate (in bps)
* @param sampleRate the sample rate (in samples per second)
* @param channels the number of channels
* @param length the frame length (in bytes)
* @param duration the duration of this frame (in milliseconds)
*/
public AudioFrame(int mpegVersion, int layer, int bitRate, int sampleRate,
int channels, int length, float duration) {
versionCode = mpegVersion;
this.layer = layer;
this.bitRate = bitRate;
this.sampleRate = sampleRate;
this.channels = channels;
this.length = length;
this.duration = duration;
version = generateVersionStr(mpegVersion, layer);
}
/**
* Generates a string for the version of this audio frame.
* @param version the code for the MPEG version
* @param layer the code for the layer
* @return a string for the version
*/
private static String generateVersionStr(int version, int layer) {
StringBuilder buf = new StringBuilder(64);
buf.append("MPEG 3 Layer ");
if (layer == LAYER_3) {
buf.append("III");
} else if (layer == LAYER_2) {
buf.append("II");
} else if (layer == LAYER_1) {
buf.append("I");
} else {
buf.append("(reserved)");
}
buf.append(" Version ");
if (version == MPEG_V2_5) {
buf.append("2.5");
} else if(version == MPEG_V2) {
buf.append("2");
} else if(version == MPEG_V1) {
buf.append("1");
} else {
buf.append("(reserved)");
}
return buf.toString();
}
}