| /* |
| * 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(); |
| } |
| } |