~ubuntu-branches/ubuntu/trusty/libjaudiotagger-java/trusty

« back to all changes in this revision

Viewing changes to src/org/jaudiotagger/audio/mp4/Mp4InfoReader.java

  • Committer: Bazaar Package Importer
  • Author(s): Damien Raude-Morvan
  • Date: 2011-04-28 23:52:43 UTC
  • mfrom: (3.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20110428235243-pzalvw6lncis3ukf
Tags: 2.0.3-1
* d/control: Drop Depends on default-jre per Debian Java Policy as its
  a library package.
* d/watch: Fix to directly monitor SVN tags.
* Switch to 3.0 (quilt) format.
* Bump Standards-Version to 3.9.2 (no changes needed).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Entagged Audio Tag library
3
 
 * Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
4
 
 * 
5
 
 * This library is free software; you can redistribute it and/or
6
 
 * modify it under the terms of the GNU Lesser General Public
7
 
 * License as published by the Free Software Foundation; either
8
 
 * version 2.1 of the License, or (at your option) any later version.
9
 
 *  
10
 
 * This library is distributed in the hope that it will be useful,
11
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 
 * Lesser General Public License for more details.
14
 
 * 
15
 
 * You should have received a copy of the GNU Lesser General Public
16
 
 * License along with this library; if not, write to the Free Software
17
 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 
 */
19
 
package org.jaudiotagger.audio.mp4;
20
 
 
21
 
import org.jaudiotagger.audio.exceptions.CannotReadException;
22
 
import org.jaudiotagger.audio.exceptions.CannotReadVideoException;
23
 
import org.jaudiotagger.audio.generic.GenericAudioHeader;
24
 
import org.jaudiotagger.audio.mp4.atom.*;
25
 
import org.jaudiotagger.logging.ErrorMessage;
26
 
 
27
 
import java.io.IOException;
28
 
import java.io.RandomAccessFile;
29
 
import java.nio.ByteBuffer;
30
 
import java.util.logging.Logger;
31
 
 
32
 
/**
33
 
 * Read audio info from file.
34
 
 * <p/>
35
 
 * <p/>
36
 
 * The info is held in the mvdh and mdhd fields as shown below
37
 
 * <pre>
38
 
 * |--- ftyp
39
 
 * |--- moov
40
 
 * |......|
41
 
 * |......|----- mvdh
42
 
 * |......|----- trak
43
 
 * |...............|----- mdia
44
 
 * |.......................|---- mdhd
45
 
 * |.......................|---- minf
46
 
 * |..............................|---- smhd
47
 
 * |..............................|---- stbl
48
 
 * |......................................|--- stsd
49
 
 * |.............................................|--- mp4a
50
 
 * |......|----- udta
51
 
 * |
52
 
 * |--- mdat
53
 
 * </pre>
54
 
 */
55
 
public class Mp4InfoReader
56
 
{
57
 
    // Logger Object
58
 
    public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.mp4.atom");
59
 
 
60
 
    public GenericAudioHeader read(RandomAccessFile raf) throws CannotReadException, IOException
61
 
    {
62
 
        Mp4AudioHeader info = new Mp4AudioHeader();
63
 
 
64
 
        //File Identification
65
 
        Mp4BoxHeader ftypHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4NotMetaFieldKey.FTYP.getFieldName());
66
 
        if (ftypHeader == null)
67
 
        {
68
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_CONTAINER.getMsg());
69
 
        }
70
 
        ByteBuffer ftypBuffer = ByteBuffer.allocate(ftypHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
71
 
        raf.getChannel().read(ftypBuffer);
72
 
        ftypBuffer.rewind();
73
 
        Mp4FtypBox ftyp = new Mp4FtypBox(ftypHeader, ftypBuffer);
74
 
        ftyp.processData();
75
 
        info.setBrand(ftyp.getMajorBrand());
76
 
 
77
 
        //Get to the facts everything we are interested in is within the moov box, so just load data from file
78
 
        //once so no more file I/O needed
79
 
        Mp4BoxHeader moovHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4NotMetaFieldKey.MOOV.getFieldName());
80
 
        if (moovHeader == null)
81
 
        {
82
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
83
 
        }
84
 
        ByteBuffer moovBuffer = ByteBuffer.allocate(moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
85
 
        raf.getChannel().read(moovBuffer);
86
 
        moovBuffer.rewind();
87
 
 
88
 
        //Level 2-Searching for "mvhd" somewhere within "moov", we make a slice after finding header
89
 
        //so all get() methods will be relative to mvdh positions
90
 
        Mp4BoxHeader boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.MVHD.getFieldName());
91
 
        if (boxHeader == null)
92
 
        {
93
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
94
 
        }
95
 
        ByteBuffer mvhdBuffer = moovBuffer.slice();
96
 
        Mp4MvhdBox mvhd = new Mp4MvhdBox(boxHeader, mvhdBuffer);
97
 
        info.setLength(mvhd.getLength());
98
 
        //Advance position, TODO should we put this in box code ?
99
 
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
100
 
 
101
 
        //Level 2-Searching for "trak" within "moov"
102
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.TRAK.getFieldName());
103
 
        int endOfFirstTrackInBuffer = mvhdBuffer.position() + boxHeader.getDataLength();
104
 
 
105
 
        if (boxHeader == null)
106
 
        {
107
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
108
 
        }
109
 
        //Level 3-Searching for "mdia" within "trak"
110
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDIA.getFieldName());
111
 
        if (boxHeader == null)
112
 
        {
113
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
114
 
        }
115
 
        //Level 4-Searching for "mdhd" within "mdia"
116
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDHD.getFieldName());
117
 
        if (boxHeader == null)
118
 
        {
119
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
120
 
        }
121
 
        Mp4MdhdBox mdhd = new Mp4MdhdBox(boxHeader, mvhdBuffer.slice());
122
 
        info.setSamplingRate(mdhd.getSampleRate());
123
 
 
124
 
        //Level 4-Searching for "hdlr" within "mdia"
125
 
        /*We dont currently need to process this because contains nothing we want
126
 
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
127
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.HDLR.getFieldName());
128
 
        if (boxHeader == null)
129
 
        {
130
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
131
 
        }
132
 
        Mp4HdlrBox hdlr = new Mp4HdlrBox(boxHeader, mvhdBuffer.slice());
133
 
        hdlr.processData();
134
 
        */
135
 
 
136
 
        //Level 4-Searching for "minf" within "mdia"
137
 
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
138
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MINF.getFieldName());
139
 
        if (boxHeader == null)
140
 
        {
141
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
142
 
        }
143
 
 
144
 
        //Level 5-Searching for "smhd" within "minf"
145
 
        //Only an audio track would have a smhd frame
146
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.SMHD.getFieldName());
147
 
        if (boxHeader == null)
148
 
        {
149
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
150
 
        }
151
 
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
152
 
 
153
 
        //Level 5-Searching for "stbl within "minf"
154
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.STBL.getFieldName());
155
 
        if (boxHeader == null)
156
 
        {
157
 
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
158
 
        }
159
 
 
160
 
        //Level 6-Searching for "stsd within "stbl" and process it direct data, dont think these are mandatory so dont throw
161
 
        //exception if unable to find
162
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.STSD.getFieldName());
163
 
        if (boxHeader != null)
164
 
        {
165
 
            Mp4StsdBox stsd = new Mp4StsdBox(boxHeader, mvhdBuffer);
166
 
            stsd.processData();
167
 
            int positionAfterStsdHeaderAndData = mvhdBuffer.position();
168
 
 
169
 
            ///Level 7-Searching for "mp4a within "stsd"
170
 
            boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MP4A.getFieldName());
171
 
            if (boxHeader != null)
172
 
            {
173
 
                ByteBuffer mp4aBuffer = mvhdBuffer.slice();
174
 
                Mp4Mp4aBox mp4a = new Mp4Mp4aBox(boxHeader, mp4aBuffer);
175
 
                mp4a.processData();
176
 
                //Level 8-Searching for "esds" within mp4a to get No Of Channels and bitrate
177
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mp4aBuffer, Mp4NotMetaFieldKey.ESDS.getFieldName());
178
 
                if (boxHeader != null)
179
 
                {
180
 
                    Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mp4aBuffer.slice());
181
 
 
182
 
                    //Set Bitrate in kbps
183
 
                    info.setBitrate(esds.getAvgBitrate() / 1000);
184
 
 
185
 
                    //Set Number of Channels
186
 
                    info.setChannelNumber(esds.getNumberOfChannels());
187
 
 
188
 
                    info.setKind(esds.getKind());
189
 
                    info.setProfile(esds.getAudioProfile());
190
 
 
191
 
                    info.setEncodingType(EncoderType.AAC.getDescription());
192
 
                }
193
 
            }
194
 
            else
195
 
            {
196
 
                //Level 7 -Searching for drms within stsd instead (m4p files)
197
 
                mvhdBuffer.position(positionAfterStsdHeaderAndData);
198
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.DRMS.getFieldName());
199
 
                if (boxHeader != null)
200
 
                {
201
 
                    Mp4DrmsBox drms = new Mp4DrmsBox(boxHeader, mvhdBuffer);
202
 
                    drms.processData();
203
 
 
204
 
                    //Level 8-Searching for "esds" within drms to get No Of Channels and bitrate
205
 
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ESDS.getFieldName());
206
 
                    if (boxHeader != null)
207
 
                    {
208
 
                        Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mvhdBuffer.slice());
209
 
 
210
 
                        //Set Bitrate in kbps
211
 
                        info.setBitrate(esds.getAvgBitrate() / 1000);
212
 
 
213
 
                        //Set Number of Channels
214
 
                        info.setChannelNumber(esds.getNumberOfChannels());
215
 
 
216
 
                        info.setKind(esds.getKind());
217
 
                        info.setProfile(esds.getAudioProfile());
218
 
 
219
 
                        info.setEncodingType(EncoderType.DRM_AAC.getDescription());
220
 
                    }
221
 
                }
222
 
                //Level 7-Searching for alac (Apple Lossless) instead
223
 
                else
224
 
                {
225
 
                    mvhdBuffer.position(positionAfterStsdHeaderAndData);
226
 
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ALAC.getFieldName());
227
 
                    if (boxHeader != null)
228
 
                    {
229
 
                        //Process First Alac
230
 
                        Mp4AlacBox alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
231
 
                        alac.processData();
232
 
                        
233
 
                        //Level 8-Searching for 2nd "alac" within box that contains the info we really want
234
 
                        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ALAC.getFieldName());
235
 
                        if (boxHeader != null)
236
 
                        {
237
 
                            alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
238
 
                            alac.processData();
239
 
                            info.setEncodingType(EncoderType.APPLE_LOSSLESS.getDescription());
240
 
                            info.setChannelNumber(alac.getChannels());
241
 
                            info.setBitrate(alac.getBitRate()/1000);
242
 
                        }
243
 
                    }
244
 
                }
245
 
            }
246
 
        }
247
 
        //Set default channels if couldnt calculate it
248
 
        if (info.getChannelNumber() == -1)
249
 
        {
250
 
            info.setChannelNumber(2);
251
 
        }
252
 
 
253
 
        //Set default bitrate if couldnt calculate it
254
 
        if (info.getBitRateAsNumber() == -1)
255
 
        {
256
 
            info.setBitrate(128);
257
 
        }
258
 
 
259
 
        //This is the most likley option if cant find a match
260
 
        if (info.getEncodingType().equals(""))
261
 
        {
262
 
            info.setEncodingType(EncoderType.AAC.getDescription());
263
 
        }
264
 
 
265
 
        logger.info(info.toString());
266
 
 
267
 
        //Level 2-Searching for another "trak" within "moov", if more than one track exists then probably
268
 
        //video so reject it, unless it fits into certain encoding patterns
269
 
        mvhdBuffer.position(endOfFirstTrackInBuffer);
270
 
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.TRAK.getFieldName());
271
 
        if (boxHeader != null)
272
 
        {
273
 
            //We only allow multiple tracks as audio if they follow the format used by the winamp encoder or
274
 
            //are marked as being audio only and if track contains a nmhd atom rather than smhd.
275
 
            //TODO this probably too restrictive but it fixes the test cases we have
276
 
            if (ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.ISO14496_1_VERSION_2.getId())
277
 
                    || ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.APPLE_AUDIO_ONLY.getId())
278
 
                    || ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.APPLE_AUDIO.getId()))
279
 
            {
280
 
                //Ok, need to do further checks on this track to ensure it is a scene descriptor
281
 
                //Level 3-Searching for "mdia" within "trak"
282
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDIA.getFieldName());
283
 
                if (boxHeader == null)
284
 
                {
285
 
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
286
 
                }
287
 
                //Level 4-Searching for "mdhd" within "mdia"
288
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDHD.getFieldName());
289
 
                if (boxHeader == null)
290
 
                {
291
 
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
292
 
                }
293
 
                //Level 4-Searching for "minf" within "mdia"
294
 
                mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
295
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MINF.getFieldName());
296
 
                if (boxHeader == null)
297
 
                {
298
 
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
299
 
                }
300
 
 
301
 
                //Level 5-Searching for "nmhd" within "minf"
302
 
                //Only an audio track would have a nmhd frame
303
 
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.NMHD.getFieldName());
304
 
                if (boxHeader == null)
305
 
                {
306
 
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
307
 
                }
308
 
            }
309
 
            else
310
 
            {
311
 
                logger.info(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg() + ":" + ftyp.getMajorBrand());
312
 
                throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
313
 
            }
314
 
        }
315
 
 
316
 
        //Build AtomTree to ensure it is valid, this means we can detect any problems early on
317
 
        Mp4AtomTree atomTree = new Mp4AtomTree(raf,false); 
318
 
 
319
 
        return info;
320
 
    }
321
 
 
322
 
 
323
 
}
 
1
/*
 
2
 * Entagged Audio Tag library
 
3
 * Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
 
4
 * 
 
5
 * This library is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU Lesser General Public
 
7
 * License as published by the Free Software Foundation; either
 
8
 * version 2.1 of the License, or (at your option) any later version.
 
9
 *  
 
10
 * This library is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * Lesser General Public License for more details.
 
14
 * 
 
15
 * You should have received a copy of the GNU Lesser General Public
 
16
 * License along with this library; if not, write to the Free Software
 
17
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
 */
 
19
package org.jaudiotagger.audio.mp4;
 
20
 
 
21
import org.jaudiotagger.audio.exceptions.CannotReadException;
 
22
import org.jaudiotagger.audio.exceptions.CannotReadVideoException;
 
23
import org.jaudiotagger.audio.generic.GenericAudioHeader;
 
24
import org.jaudiotagger.audio.mp4.atom.*;
 
25
import org.jaudiotagger.logging.ErrorMessage;
 
26
 
 
27
import java.io.IOException;
 
28
import java.io.RandomAccessFile;
 
29
import java.nio.ByteBuffer;
 
30
import java.util.logging.Logger;
 
31
 
 
32
/**
 
33
 * Read audio info from file.
 
34
 * <p/>
 
35
 * <p/>
 
36
 * The info is held in the mvdh and mdhd fields as shown below
 
37
 * <pre>
 
38
 * |--- ftyp
 
39
 * |--- moov
 
40
 * |......|
 
41
 * |......|----- mvdh
 
42
 * |......|----- trak
 
43
 * |...............|----- mdia
 
44
 * |.......................|---- mdhd
 
45
 * |.......................|---- minf
 
46
 * |..............................|---- smhd
 
47
 * |..............................|---- stbl
 
48
 * |......................................|--- stsd
 
49
 * |.............................................|--- mp4a
 
50
 * |......|----- udta
 
51
 * |
 
52
 * |--- mdat
 
53
 * </pre>
 
54
 */
 
55
public class Mp4InfoReader
 
56
{
 
57
    // Logger Object
 
58
    public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.mp4.atom");
 
59
 
 
60
    public GenericAudioHeader read(RandomAccessFile raf) throws CannotReadException, IOException
 
61
    {
 
62
        Mp4AudioHeader info = new Mp4AudioHeader();
 
63
 
 
64
        //File Identification
 
65
        Mp4BoxHeader ftypHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4NotMetaFieldKey.FTYP.getFieldName());
 
66
        if (ftypHeader == null)
 
67
        {
 
68
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_CONTAINER.getMsg());
 
69
        }
 
70
        ByteBuffer ftypBuffer = ByteBuffer.allocate(ftypHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
 
71
        raf.getChannel().read(ftypBuffer);
 
72
        ftypBuffer.rewind();
 
73
        Mp4FtypBox ftyp = new Mp4FtypBox(ftypHeader, ftypBuffer);
 
74
        ftyp.processData();
 
75
        info.setBrand(ftyp.getMajorBrand());
 
76
 
 
77
        //Get to the facts everything we are interested in is within the moov box, so just load data from file
 
78
        //once so no more file I/O needed
 
79
        Mp4BoxHeader moovHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4NotMetaFieldKey.MOOV.getFieldName());
 
80
        if (moovHeader == null)
 
81
        {
 
82
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
83
        }
 
84
        ByteBuffer moovBuffer = ByteBuffer.allocate(moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
 
85
        raf.getChannel().read(moovBuffer);
 
86
        moovBuffer.rewind();
 
87
 
 
88
        //Level 2-Searching for "mvhd" somewhere within "moov", we make a slice after finding header
 
89
        //so all get() methods will be relative to mvdh positions
 
90
        Mp4BoxHeader boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.MVHD.getFieldName());
 
91
        if (boxHeader == null)
 
92
        {
 
93
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
94
        }
 
95
        ByteBuffer mvhdBuffer = moovBuffer.slice();
 
96
        Mp4MvhdBox mvhd = new Mp4MvhdBox(boxHeader, mvhdBuffer);
 
97
        info.setLength(mvhd.getLength());
 
98
        //Advance position, TODO should we put this in box code ?
 
99
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
 
100
 
 
101
        //Level 2-Searching for "trak" within "moov"
 
102
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.TRAK.getFieldName());
 
103
        int endOfFirstTrackInBuffer = mvhdBuffer.position() + boxHeader.getDataLength();
 
104
 
 
105
        if (boxHeader == null)
 
106
        {
 
107
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
108
        }
 
109
        //Level 3-Searching for "mdia" within "trak"
 
110
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDIA.getFieldName());
 
111
        if (boxHeader == null)
 
112
        {
 
113
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
114
        }
 
115
        //Level 4-Searching for "mdhd" within "mdia"
 
116
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDHD.getFieldName());
 
117
        if (boxHeader == null)
 
118
        {
 
119
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
120
        }
 
121
        Mp4MdhdBox mdhd = new Mp4MdhdBox(boxHeader, mvhdBuffer.slice());
 
122
        info.setSamplingRate(mdhd.getSampleRate());
 
123
 
 
124
        //Level 4-Searching for "hdlr" within "mdia"
 
125
        /*We dont currently need to process this because contains nothing we want
 
126
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
 
127
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.HDLR.getFieldName());
 
128
        if (boxHeader == null)
 
129
        {
 
130
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
131
        }
 
132
        Mp4HdlrBox hdlr = new Mp4HdlrBox(boxHeader, mvhdBuffer.slice());
 
133
        hdlr.processData();
 
134
        */
 
135
 
 
136
        //Level 4-Searching for "minf" within "mdia"
 
137
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
 
138
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MINF.getFieldName());
 
139
        if (boxHeader == null)
 
140
        {
 
141
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
142
        }
 
143
 
 
144
        //Level 5-Searching for "smhd" within "minf"
 
145
        //Only an audio track would have a smhd frame
 
146
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.SMHD.getFieldName());
 
147
        if (boxHeader == null)
 
148
        {
 
149
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
150
        }
 
151
        mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
 
152
 
 
153
        //Level 5-Searching for "stbl within "minf"
 
154
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.STBL.getFieldName());
 
155
        if (boxHeader == null)
 
156
        {
 
157
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_AUDIO.getMsg());
 
158
        }
 
159
 
 
160
        //Level 6-Searching for "stsd within "stbl" and process it direct data, dont think these are mandatory so dont throw
 
161
        //exception if unable to find
 
162
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.STSD.getFieldName());
 
163
        if (boxHeader != null)
 
164
        {
 
165
            Mp4StsdBox stsd = new Mp4StsdBox(boxHeader, mvhdBuffer);
 
166
            stsd.processData();
 
167
            int positionAfterStsdHeaderAndData = mvhdBuffer.position();
 
168
 
 
169
            ///Level 7-Searching for "mp4a within "stsd"
 
170
            boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MP4A.getFieldName());
 
171
            if (boxHeader != null)
 
172
            {
 
173
                ByteBuffer mp4aBuffer = mvhdBuffer.slice();
 
174
                Mp4Mp4aBox mp4a = new Mp4Mp4aBox(boxHeader, mp4aBuffer);
 
175
                mp4a.processData();
 
176
                //Level 8-Searching for "esds" within mp4a to get No Of Channels and bitrate
 
177
                boxHeader = Mp4BoxHeader.seekWithinLevel(mp4aBuffer, Mp4NotMetaFieldKey.ESDS.getFieldName());
 
178
                if (boxHeader != null)
 
179
                {
 
180
                    Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mp4aBuffer.slice());
 
181
 
 
182
                    //Set Bitrate in kbps
 
183
                    info.setBitrate(esds.getAvgBitrate() / 1000);
 
184
 
 
185
                    //Set Number of Channels
 
186
                    info.setChannelNumber(esds.getNumberOfChannels());
 
187
 
 
188
                    info.setKind(esds.getKind());
 
189
                    info.setProfile(esds.getAudioProfile());
 
190
 
 
191
                    info.setEncodingType(EncoderType.AAC.getDescription());
 
192
                }
 
193
            }
 
194
            else
 
195
            {
 
196
                //Level 7 -Searching for drms within stsd instead (m4p files)
 
197
                mvhdBuffer.position(positionAfterStsdHeaderAndData);
 
198
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.DRMS.getFieldName());
 
199
                if (boxHeader != null)
 
200
                {
 
201
                    Mp4DrmsBox drms = new Mp4DrmsBox(boxHeader, mvhdBuffer);
 
202
                    drms.processData();
 
203
 
 
204
                    //Level 8-Searching for "esds" within drms to get No Of Channels and bitrate
 
205
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ESDS.getFieldName());
 
206
                    if (boxHeader != null)
 
207
                    {
 
208
                        Mp4EsdsBox esds = new Mp4EsdsBox(boxHeader, mvhdBuffer.slice());
 
209
 
 
210
                        //Set Bitrate in kbps
 
211
                        info.setBitrate(esds.getAvgBitrate() / 1000);
 
212
 
 
213
                        //Set Number of Channels
 
214
                        info.setChannelNumber(esds.getNumberOfChannels());
 
215
 
 
216
                        info.setKind(esds.getKind());
 
217
                        info.setProfile(esds.getAudioProfile());
 
218
 
 
219
                        info.setEncodingType(EncoderType.DRM_AAC.getDescription());
 
220
                    }
 
221
                }
 
222
                //Level 7-Searching for alac (Apple Lossless) instead
 
223
                else
 
224
                {
 
225
                    mvhdBuffer.position(positionAfterStsdHeaderAndData);
 
226
                    boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ALAC.getFieldName());
 
227
                    if (boxHeader != null)
 
228
                    {
 
229
                        //Process First Alac
 
230
                        Mp4AlacBox alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
 
231
                        alac.processData();
 
232
                        
 
233
                        //Level 8-Searching for 2nd "alac" within box that contains the info we really want
 
234
                        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.ALAC.getFieldName());
 
235
                        if (boxHeader != null)
 
236
                        {
 
237
                            alac = new Mp4AlacBox(boxHeader, mvhdBuffer);
 
238
                            alac.processData();
 
239
                            info.setEncodingType(EncoderType.APPLE_LOSSLESS.getDescription());
 
240
                            info.setChannelNumber(alac.getChannels());
 
241
                            info.setBitrate(alac.getBitRate()/1000);
 
242
                        }
 
243
                    }
 
244
                }
 
245
            }
 
246
        }
 
247
        //Set default channels if couldnt calculate it
 
248
        if (info.getChannelNumber() == -1)
 
249
        {
 
250
            info.setChannelNumber(2);
 
251
        }
 
252
 
 
253
        //Set default bitrate if couldnt calculate it
 
254
        if (info.getBitRateAsNumber() == -1)
 
255
        {
 
256
            info.setBitrate(128);
 
257
        }
 
258
 
 
259
        //This is the most likley option if cant find a match
 
260
        if (info.getEncodingType().equals(""))
 
261
        {
 
262
            info.setEncodingType(EncoderType.AAC.getDescription());
 
263
        }
 
264
 
 
265
        logger.info(info.toString());
 
266
 
 
267
        //Level 2-Searching for another "trak" within "moov", if more than one track exists then probably
 
268
        //video so reject it, unless it fits into certain encoding patterns
 
269
        mvhdBuffer.position(endOfFirstTrackInBuffer);
 
270
        boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.TRAK.getFieldName());
 
271
        if (boxHeader != null)
 
272
        {
 
273
            //We only allow multiple tracks as audio if they follow the format used by the winamp encoder or
 
274
            //are marked as being audio only and if track contains a nmhd atom rather than smhd.
 
275
            //TODO this probably too restrictive but it fixes the test cases we have
 
276
            if (ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.ISO14496_1_VERSION_2.getId())
 
277
                    || ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.APPLE_AUDIO_ONLY.getId())
 
278
                    || ftyp.getMajorBrand().equals(Mp4FtypBox.Brand.APPLE_AUDIO.getId()))
 
279
            {
 
280
                //Ok, need to do further checks on this track to ensure it is a scene descriptor
 
281
                //Level 3-Searching for "mdia" within "trak"
 
282
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDIA.getFieldName());
 
283
                if (boxHeader == null)
 
284
                {
 
285
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
 
286
                }
 
287
                //Level 4-Searching for "mdhd" within "mdia"
 
288
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MDHD.getFieldName());
 
289
                if (boxHeader == null)
 
290
                {
 
291
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
 
292
                }
 
293
                //Level 4-Searching for "minf" within "mdia"
 
294
                mvhdBuffer.position(mvhdBuffer.position() + boxHeader.getDataLength());
 
295
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.MINF.getFieldName());
 
296
                if (boxHeader == null)
 
297
                {
 
298
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
 
299
                }
 
300
 
 
301
                //Level 5-Searching for "nmhd" within "minf"
 
302
                //Only an audio track would have a nmhd frame
 
303
                boxHeader = Mp4BoxHeader.seekWithinLevel(mvhdBuffer, Mp4NotMetaFieldKey.NMHD.getFieldName());
 
304
                if (boxHeader == null)
 
305
                {
 
306
                    throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
 
307
                }
 
308
            }
 
309
            else
 
310
            {
 
311
                logger.info(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg() + ":" + ftyp.getMajorBrand());
 
312
                throw new CannotReadVideoException(ErrorMessage.MP4_FILE_IS_VIDEO.getMsg());
 
313
            }
 
314
        }
 
315
 
 
316
        //Build AtomTree to ensure it is valid, this means we can detect any problems early on
 
317
        Mp4AtomTree atomTree = new Mp4AtomTree(raf,false); 
 
318
 
 
319
        return info;
 
320
    }
 
321
 
 
322
 
 
323
}