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

Mp4EsdsBox.java

package org.jaudiotagger.audio.mp4.atom;

import org.jaudiotagger.audio.generic.Utils;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * EsdsBox ( stream specific description box), usually holds the Bitrate/No of Channels
 * <p/>
 * It contains a number of  (possibly optional?)  sections (section 3 - 6) (containing optional filler) with
 * differeent info in each section.
 * <p/>
 * <p/>
 * -> 4 bytes version/flags = 8-bit hex version + 24-bit hex flags
 * (current = 0)
 * <p/>
 * Section 3
 * -> 1 byte ES descriptor type tag = 8-bit hex value 0x03
 * -> 3 bytes optional extended descriptor type tag string = 3 * 8-bit hex value
 * - types are 0x80,0x81,0xFE
 * -> 1 byte descriptor type length = 8-bit unsigned length
 * -> 2 bytes ES ID = 16-bit unsigned value
 * -> 1 byte stream priority = 8-bit unsigned value
 * - Defaults to 16 and ranges from 0 through to 31
 * <p/>
 * Section 4
 * -> 1 byte decoder config descriptor type tag = 8-bit hex value 0x04
 * -> 3 bytes optional extended descriptor type tag string = 3 * 8-bit hex value
 * - types are 0x80,0x81,0xFE
 * -> 1 byte descriptor type length = 8-bit unsigned length *
 * -> 1 byte object type ID = 8-bit unsigned value
 * -> 6 bits stream type = 3/4 byte hex value
 * - type IDs are object descript. = 1 ; clock ref. = 2
 * - type IDs are scene descript. = 4 ; visual = 4
 * - type IDs are audio = 5 ; MPEG-7 = 6 ; IPMP = 7
 * - type IDs are OCI = 8 ; MPEG Java = 9
 * - type IDs are user private = 32
 * -> 1 bit upstream flag = 1/8 byte hex value
 * -> 1 bit reserved flag = 1/8 byte hex value set to 1
 * -> 3 bytes buffer size = 24-bit unsigned value
 * -> 4 bytes maximum bit rate = 32-bit unsigned value
 * -> 4 bytes average bit rate = 32-bit unsigned value
 * <p/>
 * Section 5
 * -> 1 byte decoder specific descriptor type tag 8-bit hex value 0x05
 * -> 3 bytes optional extended descriptor type tag string = 3 * 8-bit hex value
 * - types are 0x80,0x81,0xFE
 * -> 1 byte descriptor type length = 8-bit unsigned length
 * -> 1 byte Audio profile Id
 * - 5 bits Profile Id
 * - 3 bits Unknown
 * -> 8 bits other flags
 * - 3 bits unknown
 * - 2 bits is No of Channels
 * - 3 bits unknown
 * <p/>
 * Section 6
 * <p/>
 * -> 1 byte SL config descriptor type tag = 8-bit hex value 0x06
 * -> 3 bytes optional extended descriptor type tag string = 3 * 8-bit hex value
 * - types are 0x80,0x81,0xFE
 * -> 1 byte descriptor type length = 8-bit unsigned length
 * -> 1 byte SL value = 8-bit hex value set to 0x02
 */
public class Mp4EsdsBox extends AbstractMp4Box
{
    public static final int VERSION_FLAG_LENGTH = 1;
    public static final int OTHER_FLAG_LENGTH = 3;
    public static final int DESCRIPTOR_TYPE_LENGTH = 1;
    public static final int ES_ID_LENGTH = 2;
    public static final int STREAM_PRIORITY_LENGTH = 1;
    public static final int CONFIG_TYPE_LENGTH = 1;
    public static final int OBJECT_TYPE_LENGTH = 1;
    public static final int STREAM_TYPE_LENGTH = 1;
    public static final int BUFFER_SIZE_LENGTH = 3;
    public static final int MAX_BITRATE_LENGTH = 4;
    public static final int AVERAGE_BITRATE_LENGTH = 4;
    public static final int DESCRIPTOR_OBJECT_TYPE_LENGTH = 1;
    public static final int CHANNEL_FLAGS_LENGTH = 1;

    //Data we are tring to extract
    private Kind kind;
    private AudioProfile audioProfile;
    private int numberOfChannels;
    private int maxBitrate;
    private int avgBitrate;

    //Section indentifiers
    private static final int SECTION_THREE = 0x03;
    private static final int SECTION_FOUR = 0x04;
    private static final int SECTION_FIVE = 0x05;
    private static final int SECTION_SIX = 0x06;

    //Possible Section Filler values
    private static final int FILLER_START = 0x80;
    private static final int FILLER_OTHER = 0x81;
    private static final int FILLER_END = 0xFE;

    private static Map<Integer, Kind> kindMap;
    private static Map<Integer, AudioProfile> audioProfileMap;


    static
    {
        //Create maps to speed up lookup from raw value to enum
        kindMap = new HashMap<Integer, Kind>();
        for (Kind next : Kind.values())
        {
            kindMap.put(next.getId(), next);
        }

        audioProfileMap = new HashMap<Integer, AudioProfile>();
        for (AudioProfile next : AudioProfile.values())
        {
            audioProfileMap.put(next.getId(), next);
        }
    }

    /**
     * DataBuffer must start from from the start of the body
     *
     * @param header     header info
     * @param dataBuffer data of box (doesnt include header data)
     */
    public Mp4EsdsBox(Mp4BoxHeader header, ByteBuffer dataBuffer)
    {
        this.header = header;

        //Not currently used, as lengths can extend over more than one section i think
        int sectionThreeLength;
        int sectionFourLength;
        int sectionFiveLength;
        int sectionSixLength;

        //As explained earlier the length of this atom is not fixed so processing is a bit more difficult
        //Process Flags
        dataBuffer.position(dataBuffer.position() + VERSION_FLAG_LENGTH + OTHER_FLAG_LENGTH);

        //Process Section 3 if exists
        if (dataBuffer.get() == SECTION_THREE)
        {
            sectionThreeLength = processSectionHeader(dataBuffer);
            //Skip Other Section 3 data
            dataBuffer.position(dataBuffer.position() + ES_ID_LENGTH + STREAM_PRIORITY_LENGTH);
        }

        //Process Section 4 (to getFields type and bitrate)
        if (dataBuffer.get() == SECTION_FOUR)
        {
            sectionFourLength = processSectionHeader(dataBuffer);

            //kind (in iTunes)
            kind = kindMap.get((int) dataBuffer.get());

            //Skip Other Section 4 data
            dataBuffer.position(dataBuffer.position() + STREAM_TYPE_LENGTH + BUFFER_SIZE_LENGTH);

            //Bit rates
            this.maxBitrate = Utils.getIntBE(dataBuffer, dataBuffer.position(), (dataBuffer.position() + MAX_BITRATE_LENGTH - 1));
            dataBuffer.position(dataBuffer.position() + MAX_BITRATE_LENGTH);

            this.avgBitrate = Utils.getIntBE(dataBuffer, dataBuffer.position(), (dataBuffer.position() + AVERAGE_BITRATE_LENGTH - 1));
            dataBuffer.position(dataBuffer.position() + AVERAGE_BITRATE_LENGTH);


        }
        //Process Section 5,(to getFields no of channels and audioprofile(profile in itunes))
        if (dataBuffer.get() == SECTION_FIVE)
        {
            sectionFiveLength = processSectionHeader(dataBuffer);

            //Audio Profile
            audioProfile = audioProfileMap.get((dataBuffer.get() >> 3));

            //Channels
            byte channelByte = dataBuffer.get();
            numberOfChannels = (channelByte << 1) >> 4;
        }

        //Process Section 6, not needed ...


    }

    public int getNumberOfChannels()
    {
        return numberOfChannels;
    }

    /**
     * @return maximum bit rate (bps)
     */
    public int getMaxBitrate()
    {
        return maxBitrate;
    }

    /**
     * @return average bit rate (bps)
     */
    public int getAvgBitrate()
    {
        return avgBitrate;
    }

    /**
     * Process header, skipping filler bytes and calculating size
     *
     * @param dataBuffer
     * @return section header
     */
    public int processSectionHeader(ByteBuffer dataBuffer)
    {
        int datalength;
        byte nextByte = dataBuffer.get();
        if (((nextByte & 0xFF) == FILLER_START) || ((nextByte & 0xFF) == FILLER_OTHER) || ((nextByte & 0xFF) == FILLER_END))
        {
            dataBuffer.get();
            dataBuffer.get();
            datalength = dataBuffer.get();
        }
        else
        {
            datalength = nextByte;
        }
        return datalength;
    }

    /**
     * Only expext MPG_Audio,
     * TODO shouldnt matter if another type of audio, but something gone wrong if type of video
     *
     * @return the file type for the track
     */
    public Kind getKind()
    {
        return kind;
    }

    /**
     * Get audio profile, usually AAC Low Complexity
     *
     * @return the audio profile
     */
    public AudioProfile getAudioProfile()
    {
        return audioProfile;
    }

    /**
     * File type, held in Section 4 , only really expecting type 0x64 (AAC)
     */
    public static enum Kind
    {
        V1(1),
        V2(2),
        MPEG4_VIDEO(32),
        MPEG4_AVC_SPS(33),
        MPEG4_AVC_PPS(34),
        MPEG4_AUDIO(64),
        MPEG2_SIMPLE_VIDEO(96),
        MPEG2_MAIN_VIDEO(97),
        MPEG2_SNR_VIDEO(98),
        MPEG2_SPATIAL_VIDEO(99),
        MPEG2_HIGH_VIDEO(100),
        MPEG2_422_VIDEO(101),
        MPEG4_ADTS_MAIN(102),
        MPEG4_ADTS_LOW_COMPLEXITY(103),
        MPEG4_ADTS_SCALEABLE_SAMPLING(104),
        MPEG2_ADTS_MAIN(105),
        MPEG1_VIDEO(106),
        MPEG1_ADTS(107),
        JPEG_VIDEO(108),
        PRIVATE_AUDIO(192),
        PRIVATE_VIDEO(208),
        PCM_LITTLE_ENDIAN_AUDIO(224),
        VORBIS_AUDIO(225),
        DOLBY_V3_AUDIO(226),
        ALAW_AUDIO(227),
        MULAW_AUDIO(228),
        ADPCM_AUDIO(229),
        PCM_BIG_ENDIAN_AUDIO(230),
        YV12_VIDEO(240),
        H264_VIDEO(241),
        H263_VIDEO(242),
        H261_VIDEO(243);

        private int id;

        Kind(int id)
        {
            this.id = id;
        }

        public int getId()
        {
            return id;
        }
    }

    /**
     * Audio profile, held in Section 5 this is usually type LOW_COMPLEXITY
     */
    public static enum AudioProfile
    {
        MAIN(1, "Main"),
        LOW_COMPLEXITY(2, "Low Complexity"),
        SCALEABLE(3, "Scaleable Sample rate"),
        T_F(4, "T/F"),
        T_F_MAIN(5, "T/F Main"),
        T_F_LC(6, "T/F LC"),
        TWIN_VQ(7, "TWIN"),
        CELP(8, "CELP"),
        HVXC(9, "HVXC"),
        HILN(10, "HILN"),
        TTSI(11, "TTSI"),
        MAIN_SYNTHESIS(12, "MAIN_SYNTHESIS"),
        WAVETABLE(13, "WAVETABLE"),;

        private int id;
        private String description;

        /**
         * @param id          it is stored as in file
         * @param description human readable description
         */
        AudioProfile(int id, String description)
        {
            this.id = id;
            this.description = description;
        }

        public int getId()
        {
            return id;
        }

        public String getDescription()
        {
            return description;
        }
    }
}