FileDocCategorySizeDatePackage
Mp4BoxHeader.javaAPI DocJaudiotagger 2.0.412347Wed Mar 30 16:11:44 BST 2011org.jaudiotagger.audio.mp4.atom

Mp4BoxHeader

public class Mp4BoxHeader extends Object
Everything in MP4s are held in boxes (formally known as atoms), they are held as a hierachial tree within the MP4.

We are most interested in boxes that are used to hold metadata, but we have to know about some other boxes as well in order to find them.

All boxes consist of a 4 byte box length (big Endian), and then a 4 byte identifier, this is the header which is model in this class.

The length includes the length of the box including the identifier and the length itself. Then they may contain data and/or sub boxes, if they contain subboxes they are known as a parent box. Parent boxes shouldn't really contain data, but sometimes they do.

Parent boxes length includes the length of their immediate sub boxes

This class is normally used by instantiating with the empty constructor, then use the update method to pass the header data which is used to read the identifier and the the size of the box

Fields Summary
public static Logger
logger
public static final int
OFFSET_POS
public static final int
IDENTIFIER_POS
public static final int
OFFSET_LENGTH
public static final int
IDENTIFIER_LENGTH
public static final int
HEADER_LENGTH
private String
id
protected int
length
private long
filePos
protected ByteBuffer
dataBuffer
public static final String
CHARSET_UTF_8
Constructors Summary
public Mp4BoxHeader()
Construct empty header

Can be populated later with update method


                    
     
    

    
public Mp4BoxHeader(String id)
Construct header to allow manual creation of header for writing to file

param
id

        if(id.length()!=IDENTIFIER_LENGTH)
        {
            throw new RuntimeException("Invalid length:atom idenifier should always be 4 characters long");
        }
        dataBuffer = ByteBuffer.allocate(HEADER_LENGTH);
        try
        {
            this.id    = id;
            dataBuffer.put(4, id.getBytes("ISO-8859-1")[0]);
            dataBuffer.put(5, id.getBytes("ISO-8859-1")[1]);
            dataBuffer.put(6, id.getBytes("ISO-8859-1")[2]);
            dataBuffer.put(7, id.getBytes("ISO-8859-1")[3]);
        }
        catch(UnsupportedEncodingException uee)
        {
            //Should never happen
            throw new RuntimeException(uee);
        }
    
public Mp4BoxHeader(ByteBuffer headerData)
Construct header

Create header using headerdata, expected to find header at headerdata current position

Note after processing adjusts position to immediately after header

param
headerData

        update(headerData);
    
Methods Summary
public intgetDataLength()

return
the length of the data only (does not include the header size)

        return length - HEADER_LENGTH;
    
public java.lang.StringgetEncoding()

return
UTF_8 (always used by Mp4)

        return CHARSET_UTF_8;
    
public longgetFilePos()

return
location in file of the start of file header (i.e where the 4 byte length field starts)

        return filePos;
    
public java.nio.ByteBuffergetHeaderData()

return
the 8 byte header buffer

        dataBuffer.rewind();
        return dataBuffer;
    
public java.lang.StringgetId()

return
the box identifier

        return id;
    
public intgetLength()

return
the length of the boxes data (includes the header size)

        return length;
    
public static org.jaudiotagger.audio.mp4.atom.Mp4BoxHeaderseekWithinLevel(java.io.RandomAccessFile raf, java.lang.String id)
Seek for box with the specified id starting from the current location of filepointer,

Note it wont find the box if it is contained with a level below the current level, nor if we are at a parent atom that also contains data and we havent yet processed the data. It will work if we are at the start of a child box even if it not the required box as long as the box we are looking for is the same level (or the level above in some cases).

param
raf
param
id
throws
java.io.IOException
return

        logger.finer("Started searching for:" + id + " in file at:" + raf.getChannel().position());

        Mp4BoxHeader boxHeader = new Mp4BoxHeader();
        ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_LENGTH);
        int bytesRead = raf.getChannel().read(headerBuffer);
        if (bytesRead != HEADER_LENGTH)
        {
            return null;
        }
        headerBuffer.rewind();
        boxHeader.update(headerBuffer);
        while (!boxHeader.getId().equals(id))
        {
            logger.finer("Found:" + boxHeader.getId() + " Still searching for:" + id + " in file at:" + raf.getChannel().position());

            //Something gone wrong probably not at the start of an atom so return null;
            if (boxHeader.getLength() < Mp4BoxHeader.HEADER_LENGTH)
            {
                return null;
            }
            int noOfBytesSkipped = raf.skipBytes(boxHeader.getDataLength());
            logger.finer("Skipped:" + noOfBytesSkipped);
            if (noOfBytesSkipped < boxHeader.getDataLength())
            {
                return null;
            }
            headerBuffer.rewind();
            bytesRead = raf.getChannel().read(headerBuffer);
            logger.finer("Header Bytes Read:" + bytesRead);
            headerBuffer.rewind();
            if (bytesRead == Mp4BoxHeader.HEADER_LENGTH)
            {
                boxHeader.update(headerBuffer);
            }
            else
            {
                return null;
            }
        }
        return boxHeader;
    
public static org.jaudiotagger.audio.mp4.atom.Mp4BoxHeaderseekWithinLevel(java.nio.ByteBuffer data, java.lang.String id)
Seek for box with the specified id starting from the current location of filepointer,

Note it won't find the box if it is contained with a level below the current level, nor if we are at a parent atom that also contains data and we havent yet processed the data. It will work if we are at the start of a child box even if it not the required box as long as the box we are looking for is the same level (or the level above in some cases).

param
data
param
id
throws
java.io.IOException
return

        logger.finer("Started searching for:" + id + " in bytebuffer at" + data.position());

        Mp4BoxHeader boxHeader = new Mp4BoxHeader();
        if (data.remaining() >= Mp4BoxHeader.HEADER_LENGTH)
        {
            boxHeader.update(data);
        }
        else
        {
            return null;
        }
        while (!boxHeader.getId().equals(id))
        {
            logger.finer("Found:" + boxHeader.getId() + " Still searching for:" + id + " in bytebuffer at" + data.position());
            //Something gone wrong probably not at the start of an atom so return null;
            if (boxHeader.getLength() < Mp4BoxHeader.HEADER_LENGTH)
            {
                return null;
            }
            if(data.remaining()<(boxHeader.getLength() - HEADER_LENGTH))
            {
                //i.e Could happen if Moov header had size incorrectly recorded
                return null;    
            }
            data.position(data.position() + (boxHeader.getLength() - HEADER_LENGTH));
            if (data.remaining() >= Mp4BoxHeader.HEADER_LENGTH)
            {
                boxHeader.update(data);
            }
            else
            {
                return null;
            }
        }
        logger.finer("Found:" + id + " in bytebuffer at" + data.position());

        return boxHeader;
    
public voidsetFilePos(long filePos)
Set location in file of the start of file header (i.e where the 4 byte length field starts)

param
filePos

        this.filePos = filePos;
    
public voidsetId(int length)
Set the Id.

Allows you to manully create a header This will modify the databuffer accordingly

param
length

        byte[] headerSize = Utils.getSizeBEInt32(length);
        dataBuffer.put(5, headerSize[0]);
        dataBuffer.put(6, headerSize[1]);
        dataBuffer.put(7, headerSize[2]);
        dataBuffer.put(8, headerSize[3]);

        this.length = length;

    
public voidsetLength(int length)
Set the length.

This will modify the databuffer accordingly

param
length

        byte[] headerSize = Utils.getSizeBEInt32(length);
        dataBuffer.put(0, headerSize[0]);
        dataBuffer.put(1, headerSize[1]);
        dataBuffer.put(2, headerSize[2]);
        dataBuffer.put(3, headerSize[3]);

        this.length = length;

    
public java.lang.StringtoString()

        return "Box " + id + ":length" + length + ":filepos:" + filePos;
    
public voidupdate(java.nio.ByteBuffer headerData)
Create header using headerdata, expected to find header at headerdata current position

Note after processing adjusts position to immediately after header

param
headerData

        //Read header data into byte array
        byte[] b = new byte[HEADER_LENGTH];
        headerData.get(b);
        //Keep reference to copy of RawData
        dataBuffer = ByteBuffer.wrap(b);

        //Calculate box size
        this.length = Utils.getIntBE(b, OFFSET_POS, OFFSET_LENGTH - 1);
        //Calculate box id
        this.id = Utils.getString(b, IDENTIFIER_POS, IDENTIFIER_LENGTH, "ISO-8859-1");

        logger.finest("Mp4BoxHeader id:"+id+":length:"+length);
        if (id.equals("\0\0\0\0"))
        {
            throw new NullBoxIdException(ErrorMessage.MP4_UNABLE_TO_FIND_NEXT_ATOM_BECAUSE_IDENTIFIER_IS_INVALID.getMsg(id));
        }

        if(length<HEADER_LENGTH)
        {
            throw new InvalidBoxHeaderException(ErrorMessage.MP4_UNABLE_TO_FIND_NEXT_ATOM_BECAUSE_IDENTIFIER_IS_INVALID.getMsg(id,length));
        }