FileDocCategorySizeDatePackage
OggPageHeader.javaAPI DocJaudiotagger 2.0.413646Thu Apr 28 13:34:48 BST 2011org.jaudiotagger.audio.ogg.util

OggPageHeader.java

/*
 * Entagged Audio Tag library
 * Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jaudiotagger.audio.ogg.util;

import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.generic.Utils;
import org.jaudiotagger.logging.ErrorMessage;
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.nio.ByteBuffer;


/**
 * $Id: OggPageHeader.java 965 2011-04-28 11:34:43Z paultaylor $
 * <p/>
 * reference:http://xiph.org/ogg/doc/framing.html
 *
 * @author Raphael Slinckx (KiKiDonK)
 * @version 16 d�cembre 2003
 */
public class OggPageHeader
{
    // Logger Object
    public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.ogg.atom");

    //Capture pattern at start of header
    public static final byte[] CAPTURE_PATTERN = {'O', 'g', 'g', 'S'};

    //Ogg Page header is always 27 bytes plus the size of the segment table which is variable
    public static final int OGG_PAGE_HEADER_FIXED_LENGTH = 27;

    //Can have upto 255 segments in a page
    public static final int MAXIMUM_NO_OF_SEGMENT_SIZE = 255;

    //Each segment can be upto 255 bytes
    public static final int MAXIMUM_SEGMENT_SIZE = 255;

    //Maximum size of pageheader (27 + 255 = 282)
    public static final int MAXIMUM_PAGE_HEADER_SIZE = OGG_PAGE_HEADER_FIXED_LENGTH + MAXIMUM_NO_OF_SEGMENT_SIZE;

    //Maximum size of page data following the page header (255 * 255 = 65025)
    public static final int MAXIMUM_PAGE_DATA_SIZE = MAXIMUM_NO_OF_SEGMENT_SIZE * MAXIMUM_SEGMENT_SIZE;

    //Maximum size of page includes header and data (282 + 65025 = 65307 bytes)
    public static final int MAXIMUM_PAGE_SIZE = MAXIMUM_PAGE_HEADER_SIZE + MAXIMUM_PAGE_DATA_SIZE;

    //Starting positions of the various attributes
    public static final int FIELD_CAPTURE_PATTERN_POS = 0;
    public static final int FIELD_STREAM_STRUCTURE_VERSION_POS = 4;
    public static final int FIELD_HEADER_TYPE_FLAG_POS = 5;
    public static final int FIELD_ABSOLUTE_GRANULE_POS = 6;
    public static final int FIELD_STREAM_SERIAL_NO_POS = 14;
    public static final int FIELD_PAGE_SEQUENCE_NO_POS = 18;
    public static final int FIELD_PAGE_CHECKSUM_POS = 22;
    public static final int FIELD_PAGE_SEGMENTS_POS = 26;
    public static final int FIELD_SEGMENT_TABLE_POS = 27;

    //Length of various attributes
    public static final int FIELD_CAPTURE_PATTERN_LENGTH = 4;
    public static final int FIELD_STREAM_STRUCTURE_VERSION_LENGTH = 1;
    public static final int FIELD_HEADER_TYPE_FLAG_LENGTH = 1;
    public static final int FIELD_ABSOLUTE_GRANULE_LENGTH = 8;
    public static final int FIELD_STREAM_SERIAL_NO_LENGTH = 4;
    public static final int FIELD_PAGE_SEQUENCE_NO_LENGTH = 4;
    public static final int FIELD_PAGE_CHECKSUM_LENGTH = 4;
    public static final int FIELD_PAGE_SEGMENTS_LENGTH = 1;

    private byte[] rawHeaderData;
    private double absoluteGranulePosition;
    private int checksum;
    private byte headerTypeFlag;

    private boolean isValid = false;
    private int pageLength = 0;
    private int pageSequenceNumber, streamSerialNumber;
    private byte[] segmentTable;

    private List<PacketStartAndLength> packetList = new ArrayList<PacketStartAndLength>();
    private boolean lastPacketIncomplete = false;

    private long startByte = 0;
    /**
     * Read next PageHeader from Buffer
     *
     * @param byteBuffer
     * @return
     * @throws IOException
     * @throws CannotReadException
     */
    public static OggPageHeader read(ByteBuffer byteBuffer) throws IOException, CannotReadException
       {
           //byteBuffer
           int start = byteBuffer.position();
           logger.fine("Trying to read OggPage at:" + start);

           byte[] b = new byte[OggPageHeader.CAPTURE_PATTERN.length];
           byteBuffer.get(b);
           if (!(Arrays.equals(b, OggPageHeader.CAPTURE_PATTERN)))
           {
               throw new CannotReadException(ErrorMessage.OGG_HEADER_CANNOT_BE_FOUND.getMsg(new String(b)));
           }

           byteBuffer.position(start + OggPageHeader.FIELD_PAGE_SEGMENTS_POS);
           int pageSegments = byteBuffer.get() & 0xFF; //unsigned
           byteBuffer.position(start);

           b = new byte[OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageSegments];
           byteBuffer.get(b);
           OggPageHeader pageHeader = new OggPageHeader(b);

           //Now just after PageHeader, ready for Packet Data
           return pageHeader;
       }

    /**
     * Read next PageHeader from file
     * @param raf
     * @return
     * @throws IOException
     * @throws CannotReadException
     */
    public static OggPageHeader read(RandomAccessFile raf) throws IOException, CannotReadException
    {
        long start = raf.getFilePointer();
        logger.fine("Trying to read OggPage at:" + start);

        byte[] b = new byte[OggPageHeader.CAPTURE_PATTERN.length];
        raf.read(b);
        if (!(Arrays.equals(b, OggPageHeader.CAPTURE_PATTERN)))
        {
            raf.seek(start);
            if(AbstractID3v2Tag.isId3Tag(raf))
            {
                logger.warning(ErrorMessage.OGG_CONTAINS_ID3TAG.getMsg(raf.getFilePointer() - start));
                raf.read(b);
                if ((Arrays.equals(b, OggPageHeader.CAPTURE_PATTERN)))
                {
                    //Go to the end of the ID3 header
                    start=raf.getFilePointer() - OggPageHeader.CAPTURE_PATTERN.length;
                }
            }
            else
            {
                throw new CannotReadException(ErrorMessage.OGG_HEADER_CANNOT_BE_FOUND.getMsg(new String(b)));
            }
        }

        raf.seek(start + OggPageHeader.FIELD_PAGE_SEGMENTS_POS);
        int pageSegments = raf.readByte() & 0xFF; //unsigned
        raf.seek(start);

        b = new byte[OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageSegments];
        raf.read(b);


        OggPageHeader pageHeader = new OggPageHeader(b);
        pageHeader.setStartByte(start);
        //Now just after PageHeader, ready for Packet Data
        return pageHeader;
    }

    public OggPageHeader(byte[] b)
    {
        this.rawHeaderData = b;
        int streamStructureRevision = b[FIELD_STREAM_STRUCTURE_VERSION_POS];
        headerTypeFlag = b[FIELD_HEADER_TYPE_FLAG_POS];
        if (streamStructureRevision == 0)
        {
            this.absoluteGranulePosition = 0;
            for (int i = 0; i < FIELD_ABSOLUTE_GRANULE_LENGTH; i++)
            {
                this.absoluteGranulePosition += u(b[i + FIELD_ABSOLUTE_GRANULE_POS]) * Math.pow(2, 8 * i);
            }

            streamSerialNumber = Utils.getIntLE(b, FIELD_STREAM_SERIAL_NO_POS, 17);
            pageSequenceNumber = Utils.getIntLE(b, FIELD_PAGE_SEQUENCE_NO_POS, 21);
            checksum = Utils.getIntLE(b, FIELD_PAGE_CHECKSUM_POS, 25);
            int pageSegments = u(b[FIELD_PAGE_SEGMENTS_POS]);

            this.segmentTable = new byte[b.length - OGG_PAGE_HEADER_FIXED_LENGTH];
            int packetLength = 0;
            Integer segmentLength = null;
            for (int i = 0; i < segmentTable.length; i++)
            {
                segmentTable[i] = b[OGG_PAGE_HEADER_FIXED_LENGTH + i];
                segmentLength = u(segmentTable[i]);
                this.pageLength += segmentLength;
                packetLength += segmentLength;

                if (segmentLength < MAXIMUM_SEGMENT_SIZE)
                {
                    packetList.add(new PacketStartAndLength(pageLength - packetLength, packetLength));
                    packetLength = 0;
                }
            }

            //If last segment value is 255 this packet continues onto next page
            //and will not have been added to the packetStartAndEnd list yet
            if(segmentLength!=null)
            {
                if (segmentLength == MAXIMUM_SEGMENT_SIZE)
                {
                    packetList.add(new PacketStartAndLength(pageLength - packetLength, packetLength));
                    lastPacketIncomplete = true;
                }
            }
            isValid = true;
        }

        if(logger.isLoggable(Level.CONFIG))
        {
            logger.config("Constructed OggPage:" + this.toString());
        }
    }

    private int u(int i)
    {
        return i & 0xFF;
    }

    /**
     * @return true if the last packet on this page extends to the next page
     */
    public boolean isLastPacketIncomplete()
    {
        return lastPacketIncomplete;
    }

    public double getAbsoluteGranulePosition()
    {
        logger.fine("Number Of Samples: " + absoluteGranulePosition);
        return this.absoluteGranulePosition;
    }


    public int getCheckSum()
    {
        return checksum;
    }


    public byte getHeaderType()
    {
        return headerTypeFlag;
    }


    public int getPageLength()
    {
        logger.fine("This page length: " + pageLength);
        return this.pageLength;
    }

    public int getPageSequence()
    {
        return pageSequenceNumber;
    }

    public int getSerialNumber()
    {
        return streamSerialNumber;
    }

    public byte[] getSegmentTable()
    {
        return this.segmentTable;
    }

    public boolean isValid()
    {
        return isValid;
    }

    /**
     * @return a list of packet start position and size within this page.
     */
    public List<PacketStartAndLength> getPacketList()
    {
        return packetList;
    }

    /**
     * @return the raw header data that this pageheader is derived from
     */
    public byte[] getRawHeaderData()
    {
        return rawHeaderData;
    }

    public String toString()
    {
        String out = "Ogg Page Header:isValid:" + isValid + ":type:" + headerTypeFlag + ":oggPageHeaderLength:" + rawHeaderData.length + ":length:" + pageLength + ":seqNo:" + getPageSequence() + ":packetIncomplete:" + isLastPacketIncomplete() + ":serNum:" + this.getSerialNumber();

        for (PacketStartAndLength packet : getPacketList())
        {
            out += packet.toString();
        }
        return out;
    }

    /** Startbyte of this pageHeader in the file
     *
     * This is useful for Ogg files that contain unsupported additional data at the start of the file such
     * as ID3 data
     */
    public long getStartByte()
    {
        return startByte;
    }

    public void setStartByte(long startByte)
    {
        this.startByte = startByte;
    }

    /**
     * Within the page specifies the start and length of each packet
     * in the page offset from the end of the pageheader (after the segment table)
     */
    public static class PacketStartAndLength
    {
        private Integer startPosition = 0;
        private Integer length = 0;

        public PacketStartAndLength(int startPosition, int length)
        {
            this.startPosition = startPosition;
            this.length = length;
        }

        public int getStartPosition()
        {
            return startPosition;
        }

        public void setStartPosition(int startPosition)
        {
            this.startPosition = startPosition;
        }

        public int getLength()
        {
            return length;
        }

        public void setLength(int length)
        {
            this.length = length;
        }

        public String toString()
        {
            return "NextPkt(start:" + startPosition + ":length:" + length + "),";
        }
    }

    /**
     * This represents all the flags that can be set in the headerType field.
     * Note these values can be ORED together. For example the last packet in
     * a file would normally have a value of 0x5 because both the CONTINUED_PACKET
     * bit and the END_OF_BITSTREAM bit would be set.
     */
    public static enum HeaderTypeFlag
    {
        FRESH_PACKET((byte) 0x0),
        CONTINUED_PACKET((byte) 0x1),
        START_OF_BITSTREAM((byte) 0x2),
        END_OF_BITSTREAM((byte) 0x4);

        byte fileValue;

        HeaderTypeFlag(byte fileValue)
        {
            this.fileValue = fileValue;
        }

        /**
         * @return the value that should be written to file to enable this flag
         */
        public byte getFileValue()
        {
            return fileValue;
        }
    }
}