/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.red5.io.mp4; /** This software module was originally developed by Apple Computer, Inc. in the course of development of MPEG-4. This software module is an implementation of a part of one or more MPEG-4 tools as specified by MPEG-4. ISO/IEC gives users of MPEG-4 free license to this software module or modifications thereof for use in hardware or software products claiming conformance to MPEG-4. Those intending to use this software module in hardware or software products are advised that its use may infringe existing patents. The original developer of this software module and his/her company, the subsequent editors and their companies, and ISO/IEC have no liability for use of this software module or modifications thereof in an implementation. Copyright is not released for non MPEG-4 conforming products. Apple Computer, Inc. retains full right to use the code for its own purpose, assign or donate the code to a third party and to inhibit third parties from using the code for non MPEG-4 conforming products. This copyright notice must be included in all copies or derivative works. Copyright (c) 1999. */ import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Vector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The <code>MP4Atom</code> object represents the smallest information block * of the MP4 file. It could contain other atoms as children. * * 01/29/2008 - Added support for avc1 atom (video sample) * 02/05/2008 - Added stss - sync sample atom and stts - time to sample atom * 10/2008 - Added pasp - pixel aspect ratio atom * 10/2010 - Added ctts atom * 11/2011 - Added wide, frma, chan, and terminator handling * * @author Paul Gregoire (mondain@gmail.com) */ public class MP4Atom { private static Logger log = LoggerFactory.getLogger(MP4Atom.class); /** The size of the atom. */ protected long size; /** The type of the atom. */ protected int type; /** The user's extended type of the atom. */ protected String uuid; /** The amount of bytes that are read from the mpeg stream. */ protected long readed; /** The children of this atom. */ protected List<MP4Atom> children = new ArrayList<MP4Atom>(3); protected int version = 0; protected int flags = 0; private int channelCount = 0; protected int entryCount; /** The decoding time to sample table. */ protected Vector<Long> chunks; protected int handlerType; protected Date creationTime; protected Date modificationTime; protected int timeScale; protected long duration; protected int sampleSize; protected int sampleCount; /** The decoding time to sample table. */ protected Vector<Integer> samples; protected int fieldSize; /** The decoding time to sample table. */ protected Vector<Record> records; protected Vector<Integer> syncSamples; protected Vector<TimeSampleRecord> timeToSamplesRecords; protected Vector<CompositionTimeSampleRecord> comptimeToSamplesRecords; protected int balance; protected long trackId; protected int qt_trackWidth; protected int qt_trackHeight; protected int graphicsMode; protected int opColorRed; protected int opColorGreen; protected int opColorBlue; protected int width; protected int height; protected int avcLevel; protected int avcProfile; private byte[] videoConfigBytes; protected MP4Descriptor esd_descriptor; public MP4Atom(long size, int type, String uuid, long readed) { this.size = size; this.type = type; this.uuid = uuid; this.readed = readed; } /** * Constructs an <code>Atom</code> object from the data in the bitstream. * @param bitstream the input bitstream * @return the constructed atom. */ public final static MP4Atom createAtom(MP4DataStream bitstream) throws IOException { long size = bitstream.readBytes(4); long readed = 4; if (size == 0) { //throw new IOException("Invalid size"); return new MP4Atom(0, 0, null, 0); } int type = (int) bitstream.readBytes(4); readed += 4; log.trace("Creating atom: type = {} size = {}", intToType(type), size); // if atom is 'uuid' (extended atom type) read the uuid String uuid = null; if (type == 1970628964) { uuid = bitstream.readString(16); readed += 16; } // large size if (size == 1) { size = bitstream.readBytes(8); readed += 8; } MP4Atom atom = new MP4Atom(size, type, uuid, readed); switch (type) { case 1835297121: // MP4MediaAtomType mdia case 1684631142: // MP4DataInformationAtomType dinf case 1836019574: // MP4MovieAtomType moov case 1835626086: // MP4MediaInformationAtomType minf case 1937007212: // MP4SampleTableAtomType stbl case 1953653099: // MP4TrackAtomType trak readed = atom.create_composite_atom(bitstream); break; case 1836069985: // MP4AudioSampleEntryAtomType mp4a readed = atom.create_audio_sample_entry_atom(bitstream); break; case 1668232756: // MP4ChunkLargeOffsetAtomType co64 readed = atom.create_chunk_large_offset_atom(bitstream); break; case 1937007471: // MP4ChunkOffsetAtomType stco readed = atom.create_chunk_offset_atom(bitstream); break; case 1751411826: // MP4HandlerAtomType hdlr readed = atom.create_handler_atom(bitstream); break; case 1835296868: // MP4MediaHeaderAtomType hdhd readed = atom.create_media_header_atom(bitstream); break; case 1836476516: // MP4MovieHeaderAtomType mvhd readed = atom.create_movie_header_atom(bitstream); break; case 1937011556: // MP4SampleDescriptionAtomType stsd readed = atom.create_sample_description_atom(bitstream); break; case 1937011578: // MP4SampleSizeAtomType stsz readed = atom.create_sample_size_atom(bitstream); break; case 1937013298: // MP4CompactSampleSizeAtomType stz2 readed = atom.create_compact_sample_size_atom(bitstream); break; case 1937011555: // MP4SampleToChunkAtomType stsc readed = atom.create_sample_to_chunk_atom(bitstream); break; case 1937011571: // MP4SyncSampleAtomType stss readed = atom.create_sync_sample_atom(bitstream); break; case 1937011827: // MP4TimeToSampleAtomType stts readed = atom.create_time_to_sample_atom(bitstream); break; case 1936549988: // MP4SoundMediaHeaderAtomType smhd readed = atom.create_sound_media_header_atom(bitstream); break; case 1953196132: // MP4TrackHeaderAtomType tkhd readed = atom.create_track_header_atom(bitstream); break; case 1986881636: // MP4VideoMediaHeaderAtomType vmhd readed = atom.create_video_media_header_atom(bitstream); break; case 1836070006: // MP4VisualSampleEntryAtomType mp4v readed = atom.create_visual_sample_entry_atom(bitstream); break; case 1635148593: // MP4VideoSampleEntryAtomType avc1 readed = atom.create_video_sample_entry_atom(bitstream); break; case 1702061171: // MP4ESDAtomType esds readed = atom.create_esd_atom(bitstream); break; case 1635148611: // MP4AVCAtomType avcC readed = atom.create_avc_config_atom(bitstream); break; case 1885434736: // MP4PixelAspectAtomType pasp readed = atom.create_pasp_atom(bitstream); break; case 1668576371: // MP4CompositionTimeToSampleAtomType ctts readed = atom.create_composition_time_to_sample_atom(bitstream); break; case 2002876005: // siDecompressionParam wave readed = atom.create_decompression_param_atom(bitstream); break; case 1718775137: // Format frma readed = atom.create_format_atom(bitstream); break; case 1667785070: // Audio Channel Layout chan readed = atom.create_chan_atom(bitstream); break; case 1969517665: // User data udta readed = atom.create_udta_atom(bitstream); break; case 1835365473: // Meta data meta readed = atom.create_meta_atom(bitstream); break; case 0: // Terminator 0x00000000 break; } log.debug("Finished atom: type = {} size = {}", intToType(type), size); bitstream.skipBytes(size - readed); return atom; } /** * Loads the version of the full atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_full_atom(MP4DataStream bitstream) throws IOException { long value = bitstream.readBytes(4); version = (int) value >> 24; flags = (int) value & 0xffffff; readed += 4; return readed; } /** * Loads the composite atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_composite_atom(MP4DataStream bitstream) throws IOException { while (readed < size) { MP4Atom child = MP4Atom.createAtom(bitstream); if (child.getType() != 0) { children.add(child); } readed += child.getSize(); } return readed; } /** * Lookups for a child atom with the specified <code>type</code>, skips the <code>number</code> * children with the same type before finding a result. * @param type the type of the atom. * @param number the number of atoms to skip * @return the atom which was being searched. */ public MP4Atom lookup(long type, int number) { int position = 0; for (int i = 0; i < children.size(); i++) { MP4Atom atom = children.get(i); if (atom.getType() == type) { if (position >= number) { return atom; } position++; } } return null; } public int getChannelCount() { return channelCount; } /** * Creates a user data atom. * * @param bitstream * @return bytes read * @throws IOException */ public long create_udta_atom(MP4DataStream bitstream) throws IOException { //qtff page 44 log.trace("User data"); if (size > 12) { MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); log.trace("Child: {}", intToType(child.getType())); } return readed; } /** * Creates a meta data atom. * * @param bitstream * @return bytes read * @throws IOException */ public long create_meta_atom(MP4DataStream bitstream) throws IOException { //qtff page 110 log.trace("Meta data"); create_handler_atom(bitstream); // hdlr atom if (children.size() > 0) { // TODO handle children like ilst } else { @SuppressWarnings("unused") int subSize = (int) bitstream.readBytes(4); int subType = (int) bitstream.readBytes(4); readed += 8; if (subType == MP4Atom.typeToInt("mhdr")) { log.info("Metadata header atom"); create_full_atom(bitstream); // mhdr atom int nextItemId = (int) bitstream.readBytes(4); readed += 4; log.info("Next item id: {}", MP4Atom.intToType(nextItemId)); } else if (subType == MP4Atom.typeToInt("keys")) { log.info("Keys atom"); create_full_atom(bitstream); // keys atom } } return readed; } /** * Loads AudioSampleEntry atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_audio_sample_entry_atom(MP4DataStream bitstream) throws IOException { //qtff page 117 log.trace("Audio sample entry"); bitstream.skipBytes(6); int dataReferenceIndex = (int) bitstream.readBytes(2); int version = (int) bitstream.readBytes(2); // version log.trace("Sample description version: {}", version); bitstream.skipBytes(6); channelCount = (int) bitstream.readBytes(2); log.trace("Channels: {}", channelCount); sampleSize = (int) bitstream.readBytes(2); log.trace("Sample size (bits): {}", sampleSize); bitstream.skipBytes(4); timeScale = (int) bitstream.readBytes(2); log.trace("Time scale: {}", timeScale); bitstream.skipBytes(2); readed += 28; // version 1 contains 4 additional fields if (version == 1) { int samplesPerPacket = (int) bitstream.readBytes(4); int bytesPerPacket = (int) bitstream.readBytes(4); int bytesPerFrame = (int) bitstream.readBytes(4); int bytesPerSample = (int) bitstream.readBytes(4); readed += 16; } // version 2 contains 8 more if (version == 2) { // TODO add support for v2 } MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); log.trace("Child: {}", intToType(child.getType())); return readed; } /** * Loads ChunkLargeOffset atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_chunk_large_offset_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); chunks = new Vector<Long>(); entryCount = (int) bitstream.readBytes(4); readed += 4; for (int i = 0; i < entryCount; i++) { chunks.addElement(Long.valueOf(bitstream.readBytes(8))); readed += 8; } return readed; } public Vector<Long> getChunks() { return chunks; } /** * Loads ChunkOffset atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_chunk_offset_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); chunks = new Vector<Long>(); entryCount = (int) bitstream.readBytes(4); readed += 4; for (int i = 0; i < entryCount; i++) { long chunkOffset = bitstream.readBytes(4); chunks.addElement(Long.valueOf(chunkOffset)); readed += 4; } return readed; } /** * Loads Handler atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_handler_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); int qt_componentType = (int) bitstream.readBytes(4); log.trace("Component type: {}", MP4Atom.intToType(qt_componentType)); handlerType = (int) bitstream.readBytes(4); // for qt this is component sub-type log.trace("Handler type: {}", MP4Atom.intToType(handlerType)); int qt_componentManufacturer = (int) bitstream.readBytes(4); int qt_componentFlags = (int) bitstream.readBytes(4); int qt_componentFlagsMask = (int) bitstream.readBytes(4); readed += 20; if (handlerType != MP4Atom.typeToInt("hdlr")) { int length = (int) (size - readed); String trackName = bitstream.readString(length); log.debug("Track name: {}", trackName); readed += length; } else { int tmp = (int) bitstream.readBytes(4); readed += 4; if (tmp == MP4Atom.typeToInt("appl")) { log.info("Apple flag?: {}", MP4Atom.intToType(tmp)); bitstream.skipBytes(9); readed += 9; MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); log.trace("Child: {}", intToType(child.getType())); } } return readed; } /** * Gets the handler type. * @return the handler type. */ public int getHandlerType() { return handlerType; } /** * Loads MediaHeader atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_media_header_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); if (version == 1) { creationTime = createDate(bitstream.readBytes(8)); modificationTime = createDate(bitstream.readBytes(8)); timeScale = (int) bitstream.readBytes(4); duration = bitstream.readBytes(8); readed += 28; } else { creationTime = createDate(bitstream.readBytes(4)); modificationTime = createDate(bitstream.readBytes(4)); timeScale = (int) bitstream.readBytes(4); duration = bitstream.readBytes(4); readed += 16; } int packedLanguage = (int) bitstream.readBytes(2); int qt_quality = (int) bitstream.readBytes(2); readed += 4; return readed; } public long getDuration() { return duration; } public int getTimeScale() { return timeScale; } /** * Loads MovieHeader atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_movie_header_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); if (version == 1) { creationTime = createDate(bitstream.readBytes(8)); modificationTime = createDate(bitstream.readBytes(8)); timeScale = (int) bitstream.readBytes(4); duration = bitstream.readBytes(8); readed += 28; } else { creationTime = createDate(bitstream.readBytes(4)); modificationTime = createDate(bitstream.readBytes(4)); timeScale = (int) bitstream.readBytes(4); duration = bitstream.readBytes(4); readed += 16; } int qt_preferredRate = (int) bitstream.readBytes(4); int qt_preferredVolume = (int) bitstream.readBytes(2); bitstream.skipBytes(10); long qt_matrixA = bitstream.readBytes(4); long qt_matrixB = bitstream.readBytes(4); long qt_matrixU = bitstream.readBytes(4); long qt_matrixC = bitstream.readBytes(4); long qt_matrixD = bitstream.readBytes(4); long qt_matrixV = bitstream.readBytes(4); long qt_matrixX = bitstream.readBytes(4); long qt_matrixY = bitstream.readBytes(4); long qt_matrixW = bitstream.readBytes(4); long qt_previewTime = bitstream.readBytes(4); long qt_previewDuration = bitstream.readBytes(4); long qt_posterTime = bitstream.readBytes(4); long qt_selectionTime = bitstream.readBytes(4); long qt_selectionDuration = bitstream.readBytes(4); long qt_currentTime = bitstream.readBytes(4); long nextTrackID = bitstream.readBytes(4); readed += 80; return readed; } /** * Loads SampleDescription atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_sample_description_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); entryCount = (int) bitstream.readBytes(4); log.trace("stsd entry count: {}", entryCount); readed += 4; for (int i = 0; i < entryCount; i++) { MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); } return readed; } /** * Loads MP4SampleSizeAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_sample_size_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); samples = new Vector<Integer>(); sampleSize = (int) bitstream.readBytes(4); sampleCount = (int) bitstream.readBytes(4); readed += 8; if (sampleSize == 0) { for (int i = 0; i < sampleCount; i++) { int size = (int) bitstream.readBytes(4); samples.addElement(Integer.valueOf(size)); readed += 4; } } return readed; } public Vector<Integer> getSamples() { return samples; } public int getSampleSize() { return sampleSize; } /** * Loads CompactSampleSize atom from the input stream. * @param stream the input stream * @return the number of bytes which was being loaded. */ public long create_compact_sample_size_atom(MP4DataStream stream) throws IOException { create_full_atom(stream); stream.skipBytes(3); sampleSize = 0; fieldSize = (int) stream.readBytes(1); sampleCount = (int) stream.readBytes(4); readed += 8; for (int i = 0; i < sampleCount; i++) { int size = 0; switch (fieldSize) { case 4: size = (int) stream.readBytes(1); // TODO check the following code samples.addElement(Integer.valueOf(size & 0x0f)); size = (size >> 4) & 0x0f; i++; readed += 1; break; case 8: size = (int) stream.readBytes(1); readed += 1; break; case 16: size = (int) stream.readBytes(2); readed += 2; break; } if (i < sampleCount) { samples.addElement(Integer.valueOf(size)); } } return readed; } public Vector<Record> getRecords() { return records; } /** * Loads MP4SampleToChunkAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_sample_to_chunk_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); records = new Vector<Record>(); entryCount = (int) bitstream.readBytes(4); readed += 4; for (int i = 0; i < entryCount; i++) { int firstChunk = (int) bitstream.readBytes(4); int samplesPerChunk = (int) bitstream.readBytes(4); int sampleDescriptionIndex = (int) bitstream.readBytes(4); records.addElement(new Record(firstChunk, samplesPerChunk, sampleDescriptionIndex)); readed += 12; } return readed; } public Vector<Integer> getSyncSamples() { return syncSamples; } /** * Loads MP4SyncSampleAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_sync_sample_atom(MP4DataStream bitstream) throws IOException { log.trace("Sync sample atom contains keyframe info"); create_full_atom(bitstream); syncSamples = new Vector<Integer>(); entryCount = (int) bitstream.readBytes(4); log.trace("Sync entries: {}", entryCount); readed += 4; for (int i = 0; i < entryCount; i++) { int sample = (int) bitstream.readBytes(4); //log.trace("Sync entry: {}", sample); syncSamples.addElement(Integer.valueOf(sample)); readed += 4; } return readed; } public Vector<TimeSampleRecord> getTimeToSamplesRecords() { return timeToSamplesRecords; } /** * Loads MP4TimeToSampleAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_time_to_sample_atom(MP4DataStream bitstream) throws IOException { log.trace("Time to sample atom"); create_full_atom(bitstream); timeToSamplesRecords = new Vector<TimeSampleRecord>(); entryCount = (int) bitstream.readBytes(4); log.trace("Time to sample entries: {}", entryCount); readed += 4; for (int i = 0; i < entryCount; i++) { int sampleCount = (int) bitstream.readBytes(4); int sampleDuration = (int) bitstream.readBytes(4); //log.trace("Sync entry: {}", sample); timeToSamplesRecords.addElement(new TimeSampleRecord(sampleCount, sampleDuration)); readed += 8; } return readed; } /** * Loads MP4SoundMediaHeaderAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_sound_media_header_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); balance = (int) bitstream.readBytes(2); bitstream.skipBytes(2); readed += 4; return readed; } /** * Loads MP4TrackHeaderAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_track_header_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); log.trace("Version: {}", version); if (version == 1) { creationTime = createDate(bitstream.readBytes(8)); modificationTime = createDate(bitstream.readBytes(8)); trackId = bitstream.readBytes(4); bitstream.skipBytes(4); duration = bitstream.readBytes(8); readed += 32; } else { creationTime = createDate(bitstream.readBytes(4)); modificationTime = createDate(bitstream.readBytes(4)); trackId = bitstream.readBytes(4); bitstream.skipBytes(4); duration = bitstream.readBytes(4); readed += 20; } bitstream.skipBytes(8); //reserved by apple int qt_layer = (int) bitstream.readBytes(2); int qt_alternateGroup = (int) bitstream.readBytes(2); int qt_volume = (int) bitstream.readBytes(2); log.trace("Volume: {}", qt_volume); bitstream.skipBytes(2); //reserved by apple long qt_matrixA = bitstream.readBytes(4); long qt_matrixB = bitstream.readBytes(4); long qt_matrixU = bitstream.readBytes(4); long qt_matrixC = bitstream.readBytes(4); long qt_matrixD = bitstream.readBytes(4); long qt_matrixV = bitstream.readBytes(4); long qt_matrixX = bitstream.readBytes(4); long qt_matrixY = bitstream.readBytes(4); long qt_matrixW = bitstream.readBytes(4); qt_trackWidth = (int) bitstream.readBytes(4); width = (qt_trackWidth >> 16); qt_trackHeight = (int) bitstream.readBytes(4); height = (qt_trackHeight >> 16); readed += 60; return readed; } /** * Loads MP4VideoMediaHeaderAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_video_media_header_atom(MP4DataStream bitstream) throws IOException { create_full_atom(bitstream); if ((size - readed) == 8) { graphicsMode = (int) bitstream.readBytes(2); opColorRed = (int) bitstream.readBytes(2); opColorGreen = (int) bitstream.readBytes(2); opColorBlue = (int) bitstream.readBytes(2); readed += 8; } return readed; } /** * Loads MP4VisualSampleEntryAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_visual_sample_entry_atom(MP4DataStream bitstream) throws IOException { log.trace("Visual entry atom contains wxh"); bitstream.skipBytes(24); width = (int) bitstream.readBytes(2); log.trace("Width: {}", width); height = (int) bitstream.readBytes(2); log.trace("Height: {}", height); bitstream.skipBytes(50); readed += 78; MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); return readed; } /** * Loads MP4VideoSampleEntryAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ @SuppressWarnings("unused") public long create_video_sample_entry_atom(MP4DataStream bitstream) throws IOException { log.trace("Video entry atom contains wxh"); bitstream.skipBytes(6); int dataReferenceIndex = (int) bitstream.readBytes(2); bitstream.skipBytes(2); bitstream.skipBytes(2); bitstream.skipBytes(12); width = (int) bitstream.readBytes(2); log.trace("Width: {}", width); height = (int) bitstream.readBytes(2); log.trace("Height: {}", height); int horizontalRez = (int) bitstream.readBytes(4) >> 16; log.trace("H Resolution: {}", horizontalRez); int verticalRez = (int) bitstream.readBytes(4) >> 16; log.trace("V Resolution: {}", verticalRez); bitstream.skipBytes(4); int frameCount = (int) bitstream.readBytes(2); log.trace("Frame to sample count: {}", frameCount); int stringLen = (int) bitstream.readBytes(1); log.trace("String length (cpname): {}", stringLen); String compressorName = bitstream.readString(31); log.trace("Compressor name: {}", compressorName.trim()); int depth = (int) bitstream.readBytes(2); log.trace("Depth: {}", depth); bitstream.skipBytes(2); readed += 78; log.trace("Bytes read: {}", readed); MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); return readed; } public int getHeight() { return height; } public int getWidth() { return width; } public int getAvcLevel() { return avcLevel; } public int getAvcProfile() { return avcProfile; } public byte[] getVideoConfigBytes() { return videoConfigBytes; } /** * Loads AVCC atom from the input bitstream. * * <pre> * 8+ bytes ISO/IEC 14496-10 or 3GPP AVC decode config box = long unsigned offset + long ASCII text string 'avcC' -> 1 byte version = 8-bit hex version (current = 1) -> 1 byte H.264 profile = 8-bit unsigned stream profile -> 1 byte H.264 compatible profiles = 8-bit hex flags -> 1 byte H.264 level = 8-bit unsigned stream level -> 1 1/2 nibble reserved = 6-bit unsigned value set to 63 -> 1/2 nibble NAL length = 2-bit length byte size type - 1 byte = 0 ; 2 bytes = 1 ; 4 bytes = 3 -> 1 byte number of SPS = 8-bit unsigned total -> 2+ bytes SPS length = short unsigned length -> + SPS NAL unit = hexdump -> 1 byte number of PPS = 8-bit unsigned total -> 2+ bytes PPS length = short unsigned length -> + PPS NAL unit = hexdump * </pre> * * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_avc_config_atom(MP4DataStream bitstream) throws IOException { log.trace("AVC config"); log.trace("Offset: {}", bitstream.getOffset()); //store the decoder config bytes videoConfigBytes = new byte[(int) size - 8]; for (int b = 0; b < videoConfigBytes.length; b++) { videoConfigBytes[b] = (byte) bitstream.readBytes(1); switch (b) { //0 / version case 1: //profile avcProfile = videoConfigBytes[b]; log.trace("AVC profile: {}", avcProfile); break; case 2: //compatible profile int avcCompatProfile = videoConfigBytes[b]; log.trace("AVC compatible profile: {}", avcCompatProfile); break; case 3: //avc level avcLevel = videoConfigBytes[b]; log.trace("AVC level: {}", avcLevel); break; case 4: //NAL length break; case 5: //SPS number int numberSPS = videoConfigBytes[b]; log.trace("Number of SPS: {}", numberSPS); break; default: } readed++; } return readed; } /** * Creates the PASP atom or Pixel Aspect Ratio. It is created by Quicktime * when exporting an MP4 file. The atom is required for ipod's and acts as * a container for the avcC atom in these cases. * * @param bitstream the input bitstream * @return the number of bytes which was being loaded */ public long create_pasp_atom(MP4DataStream bitstream) throws IOException { log.trace("Pixel aspect ratio"); int hSpacing = (int) bitstream.readBytes(4); int vSpacing = (int) bitstream.readBytes(4); log.trace("hSpacing: {} vSpacing: {}", hSpacing, vSpacing); readed += 8; MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); return readed; } /** * Creates the decompression param (wave) atom. * * @param bitstream the input bitstream * @return the number of bytes which was being loaded */ private long create_decompression_param_atom(MP4DataStream bitstream) throws IOException { log.trace("Decompression param"); while (readed < size) { MP4Atom child = MP4Atom.createAtom(bitstream); //log.debug("Child: {} size: {}", child, child.getSize()); if (child.getType() != 0) { children.add(child); } readed += child.getSize(); } return readed; } /** * Creates the format (frma) atom. * * @param bitstream the input bitstream * @return the number of bytes which was being loaded */ private long create_format_atom(MP4DataStream bitstream) throws IOException { log.trace("Format"); // Data format - The value of this field is copied from the data-format field of the sound sample description. while (readed < size) { MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); } return readed; } /** * Loads Audio Channel Layout atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_chan_atom(MP4DataStream bitstream) throws IOException { log.trace("Audio Channel Layout Atom atom"); create_full_atom(bitstream); // Audio channel layout - A big-endian AudioChannelLayout structure as defined in CoreAudioTypes.h. // See the <a href='http://developer.apple.com/library/mac/navigation/index.html'>Mac OS X Developer Library for CoreAudio framework details</a> while (readed < size) { MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); } return readed; } /** * Loads M4ESDAtom atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_esd_atom(MP4DataStream bitstream) throws IOException { log.trace("Elementary stream descriptor atom"); create_full_atom(bitstream); esd_descriptor = MP4Descriptor.createDescriptor(bitstream); readed += esd_descriptor.getReaded(); log.trace("Read for descriptor: {}", esd_descriptor.getReaded()); while (readed < size) { MP4Atom child = MP4Atom.createAtom(bitstream); children.add(child); readed += child.getSize(); } return readed; } /** * Returns the ESD descriptor. */ public MP4Descriptor getEsd_descriptor() { return esd_descriptor; } /** * Loads composition time to sample atom from the input bitstream. * @param bitstream the input bitstream * @return the number of bytes which was being loaded. */ public long create_composition_time_to_sample_atom(MP4DataStream bitstream) throws IOException { log.trace("Composition time to sample atom"); create_full_atom(bitstream); comptimeToSamplesRecords = new Vector<CompositionTimeSampleRecord>(); entryCount = (int) bitstream.readBytes(4); log.trace("Composition time to sample entries: {}", entryCount); readed += 4; for (int i = 0; i < entryCount; i++) { int sampleCount = (int) bitstream.readBytes(4); int sampleOffset = (int) bitstream.readBytes(4); //log.trace("Sync entry: {}", sample); comptimeToSamplesRecords.addElement(new CompositionTimeSampleRecord(sampleCount, sampleOffset)); readed += 8; } return readed; } public Vector<CompositionTimeSampleRecord> getCompositionTimeToSamplesRecords() { return comptimeToSamplesRecords; } /** * Converts the time in seconds since midnight 1 Jan 1904 to the <code>Date</code>. * @param movieTime the time in milliseconds since midnight 1 Jan 1904. * @return the <code>Date</code> object. */ public static final Date createDate(long movieTime) { return new Date(movieTime * 1000 - 2082850791998L); } /** * Convert the atom identifer to an integer. * * @param type * @return atom type as an integer */ public static int typeToInt(String type) { int result = (type.charAt(0) << 24) + (type.charAt(1) << 16) + (type.charAt(2) << 8) + type.charAt(3); return result; } /** * Convert the atom integer to a string. * * @param type * @return atom type as a string */ public static String intToType(int type) { StringBuilder st = new StringBuilder(); st.append((char) ((type >> 24) & 0xff)); st.append((char) ((type >> 16) & 0xff)); st.append((char) ((type >> 8) & 0xff)); st.append((char) (type & 0xff)); return st.toString(); } /** * Gets children from this atom. * @return children from this atom. */ public List<MP4Atom> getChildren() { return children; } /** * Gets the size of this atom. * @return the size of this atom. */ public long getSize() { return size; } /** * Returns the type of this atom. */ public int getType() { return type; } /** * Returns the name of this atom. */ public String toString() { return intToType(type); } public static class Record { private int firstChunk; private int samplesPerChunk; private int sampleDescriptionIndex; public Record(int firstChunk, int samplesPerChunk, int sampleDescriptionIndex) { this.firstChunk = firstChunk; this.samplesPerChunk = samplesPerChunk; this.sampleDescriptionIndex = sampleDescriptionIndex; } public int getFirstChunk() { return firstChunk; } public int getSamplesPerChunk() { return samplesPerChunk; } public int getSampleDescriptionIndex() { return sampleDescriptionIndex; } } public static class TimeSampleRecord { private int consecutiveSamples; private int sampleDuration; public TimeSampleRecord(int consecutiveSamples, int sampleDuration) { this.consecutiveSamples = consecutiveSamples; this.sampleDuration = sampleDuration; } public int getConsecutiveSamples() { return consecutiveSamples; } public int getSampleDuration() { return sampleDuration; } } public static class CompositionTimeSampleRecord { private int consecutiveSamples; private int sampleOffset; public CompositionTimeSampleRecord(int consecutiveSamples, int sampleOffset) { this.consecutiveSamples = consecutiveSamples; this.sampleOffset = sampleOffset; } public int getConsecutiveSamples() { return consecutiveSamples; } public int getSampleOffset() { return sampleOffset; } public void setSampleOffset(int offset) { sampleOffset = offset; } } }