MpegStream.java

/*
 * 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 java.io.PushbackInputStream;

import org.apache.commons.io.IOUtils;

/**
 * <p>
 * A specialized stream class which can be used to extract single frames of MPEG
 * audio files.
 * </p>
 * <p>
 * Instances of this class are constructed with an underlying stream which
 * should point to an audio file. Read operations are possible in the usual way.
 * However, there are special methods for searching and extracting headers of
 * MPEG frames. Some meta information of frames can be queried.
 * </p>
 */
class MpegStream extends PushbackInputStream {
    /**
     * Bit rate table for MPEG V1, layer 1.
     */
    private static final int[] BIT_RATE_MPEG1_L1 =
            {0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000,
                    384000, 416000, 448000};

    /**
     * Bit rate table for MPEG V1, layer 2.
     */
    private static final int[] BIT_RATE_MPEG1_L2 =
            {0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000,
                    256000, 320000, 384000};

    /**
     * Bit rate table for MPEG V1, layer 3.
     */
    private static final int[] BIT_RATE_MPEG1_L3 =
            {0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000,
                    224000, 256000, 320000};

    /**
     * Bit rate table for MPEG V2/V2.5, layer 1.
     */
    private static final int[] BIT_RATE_MPEG2_L1 =
            {0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000,
                    192000, 224000, 256000};

    /**
     * Bit rate table for MPEG V2/V2.5, layer 2 and 3.
     */
    private static final int[] BIT_RATE_MPEG2_L2 =
            {0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000,
                    144000, 160000};

    /**
     * Sample rate table for MPEG V1.
     */
    private static final int[] SAMPLE_RATE_MPEG1 = {44100, 48000, 32000};

    /**
     * Sample rate table for MPEG V2.
     */
    private static final int[] SAMPLE_RATE_MPEG2 = {22050, 24000, 16000};

    /**
     * Sample rate table for MPEG V2.5.
     */
    private static final int[] SAMPLE_RATE_MPEG2_5 = {11025, 12000, 8000};

    /**
     * Sample rate table for all MPEG versions.
     */
    private static final int[][] SAMPLE_RATE = createSampleRateTable();

    /**
     * Constant for the number of samples for a layer 1 frame.
     */
    private static final int SAMPLE_COUNT_L1 = 384;

    /**
     * Constant for the number of samples for a layer 2 or 3 frame.
     */
    private static final int SAMPLE_COUNT_L2 = 1152;

    /**
     * Constant for the size of an MPEG frame header in bytes.
     */
    private static final int HEADER_SIZE = 4;

    /**
     * The current MPEG header.
     */
    private AudioFrame currentHeader;

    /**
     * A flag whether the end of the stream is reached.
     */
    private boolean endOfStream;

    /**
     * Creates a new instance of {@code MpegStream} and initializes it with the
     * underlying stream.
     *
     * @param in the underlying audio stream
     */
    public MpegStream(InputStream in) {
        super(in, 2 * HEADER_SIZE);
    }

    /**
     * Calculates the bit rate based on the given parameters.
     *
     * @param mpegVer the MPEG version
     * @param layer   the layer
     * @param code    the code for the bit rate
     * @return the bit rate in bits per second
     */
    private static int calculateBitRate(int mpegVer, int layer, int code) {
        int[] arr = null;

        if (mpegVer == AudioFrame.MPEG_V1) {
            switch (layer) {
                case AudioFrame.LAYER_1:
                    arr = BIT_RATE_MPEG1_L1;
                    break;
                case AudioFrame.LAYER_2:
                    arr = BIT_RATE_MPEG1_L2;
                    break;
                case AudioFrame.LAYER_3:
                    arr = BIT_RATE_MPEG1_L3;
                    break;
            }
        } else {
            if (layer == AudioFrame.LAYER_1) {
                arr = BIT_RATE_MPEG2_L1;
            } else {
                arr = BIT_RATE_MPEG2_L2;
            }
        }
        return arr[code];
    }

    /**
     * Calculates the sample rate based on the given parameters.
     *
     * @param mpegVer the MPEG version
     * @param code    the code for the sample rate
     * @return the sample rate in samples per second
     */
    private static int calculateSampleRate(int mpegVer, int code) {
        return SAMPLE_RATE[mpegVer][code];
    }

    /**
     * Calculates the length of an MPEG frame based on the given parameters.
     *
     * @param layer      the layer
     * @param bitRate    the bit rate
     * @param sampleRate the sample rate
     * @param padding    the padding flag
     * @return the length of the frame in bytes
     */
    private static int calculateFrameLength(int layer, int bitRate, int sampleRate, int padding) {
        if (layer == AudioFrame.LAYER_1) {
            return (12 * bitRate / sampleRate + padding) * 4;
        } else {
            return 144 * bitRate / sampleRate + padding;
        }
    }

    /**
     * Calculates the duration of a MPEG frame based on the given parameters.
     *
     * @param layer      the layer
     * @param sampleRate the sample rate
     * @return the duration of this frame in milliseconds
     */
    private static float calculateDuration(int layer, int sampleRate) {
        int sampleCount = (layer == AudioFrame.LAYER_1) ? SAMPLE_COUNT_L1 : SAMPLE_COUNT_L2;
        return (1000.0f / sampleRate) * sampleCount;
    }

    /**
     * Calculates the number of channels based on the given parameters.
     *
     * @param chan the code for the channels
     * @return the number of channels
     */
    private static int calculateChannels(int chan) {
        return chan < 3 ? 2 : 1;
    }

    /**
     * Creates the complete array for the sample rate mapping.
     *
     * @return the table for the sample rates
     */
    private static int[][] createSampleRateTable() {
        int[][] arr = new int[4][];
        arr[AudioFrame.MPEG_V1] = SAMPLE_RATE_MPEG1;
        arr[AudioFrame.MPEG_V2] = SAMPLE_RATE_MPEG2;
        arr[AudioFrame.MPEG_V2_5] = SAMPLE_RATE_MPEG2_5;
        return arr;
    }

    /**
     * Searches for the next MPEG frame header from the current stream position
     * on. This method advances the underlying input stream until it finds a
     * valid frame header or the end of the stream is reached. In the former
     * case a corresponding {@code AudioFrame} object is created. In the latter
     * case there are no more headers, so the end of the stream is probably
     * reached.
     *
     * @return the next {@code AudioFrame} or <b>null</b>
     * @throws IOException if an IO error occurs
     */
    public AudioFrame nextFrame() throws IOException {
        AudioFrame frame = null;
        while (!endOfStream && frame == null) {
            findFrameSyncByte();
            if (!endOfStream) {
                HeaderBitField headerField = createHeaderField();
                if (!endOfStream) {
                    frame = createHeader(headerField);
                    if (frame == null) {
                        pushBack(headerField);
                    }
                }
            }
        }

        currentHeader = frame;
        return frame;
    }

    /**
     * Skips the current MPEG frame. This method can be called after a valid
     * MPEG header has been retrieved using {@code nextFrame()}. In this case
     * the underlying stream is advanced to the end of the associated MPEG
     * frame or until the EOF is reached. The return value indicates
     * whether the full frame could be skipped.
     *
     * @return <b>true</b> if a frame could be skipped, <b>false</b> otherwise, perhaps EOF?
     * @throws IOException if an IO error occurs
     */
    public boolean skipFrame() throws IOException {
        if (currentHeader != null) {
            long toSkip = currentHeader.getLength() - HEADER_SIZE;
            long skipped = IOUtils.skip(in, toSkip);
            currentHeader = null;
            if (skipped < toSkip) {
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * Advances the underlying stream until the first byte of frame sync is
     * found.
     *
     * @throws IOException if an error occurs
     */
    private void findFrameSyncByte() throws IOException {
        boolean found = false;
        while (!found && !endOfStream) {
            if (nextByte() == 0xFF) {
                found = true;
            }
        }
    }

    /**
     * Creates a bit field for the MPEG frame header.
     *
     * @return the bit field
     * @throws IOException if an error occurs
     */
    private HeaderBitField createHeaderField() throws IOException {
        HeaderBitField field = new HeaderBitField();
        field.add(nextByte());
        field.add(nextByte());
        field.add(nextByte());
        return field;
    }

    /**
     * Creates an {@code AudioFrame} object based on the given header field. If
     * the header field contains invalid values, result is <b>null</b>.
     *
     * @param bits the header bit field
     * @return the {@code AudioFrame}
     */
    private AudioFrame createHeader(HeaderBitField bits) {
        if (bits.get(21, 23) != 7) {
            return null;
        }

        int mpegVer = bits.get(19, 20);
        int layer = bits.get(17, 18);
        int bitRateCode = bits.get(12, 15);
        int sampleRateCode = bits.get(10, 11);
        int padding = bits.get(9);

        if (mpegVer == 1 || layer == 0 || bitRateCode == 0 || bitRateCode == 15 ||
                sampleRateCode == 3) {
            // invalid header values
            return null;
        }

        int bitRate = calculateBitRate(mpegVer, layer, bitRateCode);
        int sampleRate = calculateSampleRate(mpegVer, sampleRateCode);
        int length = calculateFrameLength(layer, bitRate, sampleRate, padding);
        float duration = calculateDuration(layer, sampleRate);
        int channels = calculateChannels(bits.get(6, 7));
        return new AudioFrame(mpegVer, layer, bitRate, sampleRate, channels, length, duration);
    }

    /**
     * Reads the next byte.
     *
     * @return the next byte
     * @throws IOException if an error occurs
     */
    private int nextByte() throws IOException {
        int result = 0;
        if (!endOfStream) {
            result = read();
            if (result == -1) {
                endOfStream = true;
            }
        }
        return endOfStream ? 0 : result;
    }

    /**
     * Pushes the given header field back in the stream so that the bytes are
     * read again. This method is called if an invalid header was detected. Then
     * search has to continue at the next byte after the frame sync byte.
     *
     * @param field the header bit field with the invalid frame header
     * @throws IOException if an error occurs
     */
    private void pushBack(HeaderBitField field) throws IOException {
        unread(field.toArray());
    }

    /**
     * A class representing the bit field of an MPEG header. It allows
     * convenient access to specific bit groups.
     */
    private static class HeaderBitField {
        /**
         * The internal value.
         */
        private int value;

        /**
         * Adds a byte to this field.
         *
         * @param b the byte to be added
         */
        public void add(int b) {
            value <<= 8;
            value |= b;
        }

        /**
         * Returns the value of the bit group from the given start and end
         * index. E.g. ''from'' = 0, ''to'' = 3 will return the value of the
         * first 4 bits.
         *
         * @param from index
         * @param to   the to index
         * @return the value of this group of bits
         */
        public int get(int from, int to) {
            int shiftVal = value >> from;
            int mask = (1 << (to - from + 1)) - 1;
            return shiftVal & mask;
        }

        /**
         * Returns the value of the bit with the given index. The bit index is
         * 0-based. Result is either 0 or 1, depending on the value of this bit.
         *
         * @param bit the bit index
         * @return the value of this bit
         */
        public int get(int bit) {
            return get(bit, bit);
        }

        /**
         * Returns the internal value of this field as an array. The array
         * contains 3 bytes.
         *
         * @return the internal value of this field as int array
         */
        public byte[] toArray() {
            byte[] result = new byte[3];
            result[0] = (byte) get(16, 23);
            result[1] = (byte) get(8, 15);
            result[2] = (byte) get(0, 7);
            return result;
        }
    }
}