FileDocCategorySizeDatePackage
Mp4InfoReader.javaAPI DocJaudiotagger 2.0.414315Thu Sep 22 10:42:54 BST 2011org.jaudiotagger.audio.mp4

Mp4InfoReader

public class Mp4InfoReader extends Object
Read audio info from file.

The info is held in the mvdh and mdhd fields as shown below

|--- ftyp
|--- moov
|......|
|......|----- mvdh
|......|----- trak
|...............|----- mdia
|.......................|---- mdhd
|.......................|---- minf
|..............................|---- smhd
|..............................|---- stbl
|......................................|--- stsd
|.............................................|--- mp4a
|......|----- udta
|
|--- mdat

Fields Summary
public static Logger
logger
Constructors Summary
Methods Summary
private booleanisTrackAtomVideo(Mp4FtypBox ftyp, Mp4BoxHeader boxHeader, java.nio.ByteBuffer mvhdBuffer)


             
             
    
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MDIA.getFieldName());
        if (boxHeader == null)
        {
            return false;
        }
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MDHD.getFieldName());
        if (boxHeader == null)
        {
            return false;
        }
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MINF.getFieldName());
        if (boxHeader == null)
        {
            return false;
        }
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.VMHD.getFieldName());
        if (boxHeader != null)
        {
            return true;
        }
        return false;
    
public org.jaudiotagger.audio.generic.GenericAudioHeaderread(java.io.RandomAccessFile raf)

        Mp4AudioHeader info = new Mp4AudioHeader();

        //File Identification
        Mp4BoxHeader ftypHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4AtomIdentifier.FTYP.getFieldName());
        if (ftypHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_CONTAINER.getMsg());
        }
        ByteBuffer ftypBuffer = ByteBuffer.allocate(ftypHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
        raf.getChannel().read(ftypBuffer);
        ftypBuffer.rewind();
        Mp4FtypBox ftyp = new Mp4FtypBox(ftypHeader, ftypBuffer);
        ftyp.processData();
        info.setBrand(ftyp.getMajorBrand());

        //Get to the facts everything we are interested in is within the moov box, so just load data from file
        //once so no more file I/O needed
        Mp4BoxHeader moovHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4AtomIdentifier.MOOV.getFieldName());
        if (moovHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        ByteBuffer moovBuffer = ByteBuffer.allocate(moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
        raf.getChannel().read(moovBuffer);
        moovBuffer.rewind();

        //Level 2-Searching for "mvhd" somewhere within "moov", we make a slice after finding header
        //so all get() methods will be relative to mvdh positions
        Mp4BoxHeader boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4AtomIdentifier.MVHD.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        ByteBuffer mvhdBuffer = moovBuffer.slice();
        Mp4MvhdBox mvhd = new Mp4MvhdBox(boxHeader, mvhdBuffer);
        info.setLength(mvhd.getLength());
        //Advance position, TODO should we put this in box code ?
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());

        //Level 2-Searching for "trak" within "moov"
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.TRAK.getFieldName());
        int endOfFirstTrackInBuffer = mvhdBuffer.position() + boxHeader.getDataLength();

        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        //Level 3-Searching for "mdia" within "trak"
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MDIA.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        //Level 4-Searching for "mdhd" within "mdia"
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MDHD.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        Mp4MdhdBox mdhd = new Mp4MdhdBox(boxHeader, mvhdBuffer.slice());
        info.setSamplingRate(mdhd.getSampleRate());

        //Level 4-Searching for "hdlr" within "mdia"
        /*We dont currently need to process this because contains nothing we want
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.HDLR.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }
        Mp4HdlrBox hdlr = new Mp4HdlrBox(boxHeader, mvhdBuffer.slice());
        hdlr.processData();
        */

        //Level 4-Searching for "minf" within "mdia"
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MINF.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }

        //Level 5-Searching for "smhd" within "minf"
        //Only an audio track would have a smhd frame
        int pos = mvhdBuffer.position();
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.SMHD.getFieldName());
        if (boxHeader == null)
        {
            mvhdBuffer.position(pos);
            boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.VMHD.getFieldName());
            //try easy check to confirm that it is video
            if(boxHeader!=null)
            {
                throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
            }
            else
            {
                throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
            }
        }
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());

        //Level 5-Searching for "stbl within "minf"
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.STBL.getFieldName());
        if (boxHeader == null)
        {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
        }

        //Level 6-Searching for "stsd within "stbl" and process it direct data, dont think these are mandatory so dont throw
        //exception if unable to find
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.STSD.getFieldName());
        if (boxHeader != null)
        {
            Mp4StsdBox stsd = new Mp4StsdBox(boxHeader, mvhdBuffer);
            stsd.processData();
            int positionAfterStsdHeaderAndData = mvhdBuffer.position();

            ///Level 7-Searching for "mp4a within "stsd"
            boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.MP4A.getFieldName());
            if (boxHeader != null)
            {
                ByteBuffer mp4aBuffer = mvhdBuffer.slice();
                Mp4Mp4aBox mp4a = new Mp4Mp4aBox(boxHeader, mp4aBuffer);
                mp4a.processData();
                //Level 8-Searching for "esds" within mp4a to get No Of Channels and bitrate
                boxHeader = Mp4BoxHeader.seekWithinLevel(mp4aBuffer, Mp4AtomIdentifier.ESDS.getFieldName());
                if (boxHeader != null)
                {
                    Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mp4aBuffer.slice());

                    //Set Bitrate in kbps
                    info.setBitrate(esds.getAvgBitrate() / 1000);

                    //Set Number of Channels
                    info.setChannelNumber(esds.getNumberOfChannels());

                    info.setKind(esds.getKind());
                    info.setProfile(esds.getAudioProfile());

                    info.setEncodingType(EncoderType.AAC.getDescription());
                }
            }
            else
            {
                //Level 7 -Searching for drms within stsd instead (m4p files)
                mvhdBuffer.position(positionAfterStsdHeaderAndData);
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.DRMS.getFieldName());
                if (boxHeader != null)
                {
                    Mp4DrmsBox drms = new Mp4DrmsBox(boxHeader, mvhdBuffer);
                    drms.processData();

                    //Level 8-Searching for "esds" within drms to get No Of Channels and bitrate
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.ESDS.getFieldName());
                    if (boxHeader != null)
                    {
                        Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mvhdBuffer.slice());

                        //Set Bitrate in kbps
                        info.setBitrate(esds.getAvgBitrate() / 1000);

                        //Set Number of Channels
                        info.setChannelNumber(esds.getNumberOfChannels());

                        info.setKind(esds.getKind());
                        info.setProfile(esds.getAudioProfile());

                        info.setEncodingType(EncoderType.DRM_AAC.getDescription());
                    }
                }
                //Level 7-Searching for alac (Apple Lossless) instead
                else
                {
                    mvhdBuffer.position(positionAfterStsdHeaderAndData);
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.ALAC.getFieldName());
                    if (boxHeader != null)
                    {
                        //Process First Alac
                        Mp4AlacBox alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
                        alac.processData();
                        
                        //Level 8-Searching for 2nd "alac" within box that contains the info we really want
                        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.ALAC.getFieldName());
                        if (boxHeader != null)
                        {
                            alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
                            alac.processData();
                            info.setEncodingType(EncoderType.APPLE_LOSSLESS.getDescription());
                            info.setChannelNumber(alac.getChannels());
                            info.setBitrate(alac.getBitRate()/1000);
                        }
                    }
                }
            }
        }
        //Set default channels if couldn't calculate it
        if (info.getChannelNumber() == -1)
        {
            info.setChannelNumber(2);
        }

        //Set default bitrate if couldnt calculate it
        if (info.getBitRateAsNumber() == -1)
        {
            info.setBitrate(128);
        }

        //This is the most likely option if cant find a match
        if (info.getEncodingType().equals(""))
        {
            info.setEncodingType(EncoderType.AAC.getDescription());
        }

        logger.config(info.toString());

        //Level 2-Searching for others "trak" within "moov", if we find any traks containing video
        //then reject it if no track if not video then we allow it because many encoders seem to contain all sorts
        //of stuff that you wouldn't expect in an audio track
        mvhdBuffer.position(endOfFirstTrackInBuffer);
        while(mvhdBuffer.hasRemaining())
        {
            boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4AtomIdentifier.TRAK.getFieldName());
            if (boxHeader != null)
            {
                if(isTrackAtomVideo(ftyp,boxHeader,mvhdBuffer))
                {
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
                }
            }
            else
            {
                break;
            }
        }

        //Build AtomTree to ensure it is valid, this means we can detect any problems early on
        new Mp4AtomTree(raf,false);

        return info;