Package net.sourceforge.jaad.mp4.api

Source Code of net.sourceforge.jaad.mp4.api.Track$Codec

/*
*  Copyright (C) 2011 in-somnia
*
*  This file is part of JAAD.
*
*  JAAD is free software; you can redistribute it and/or modify it
*  under the terms of the GNU Lesser General Public License as
*  published by the Free Software Foundation; either version 3 of the
*  License, or (at your option) any later version.
*
*  JAAD is distributed in the hope that it will be useful, but WITHOUT
*  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
*  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
*  Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library.
*  If not, see <http://www.gnu.org/licenses/>.
*/
package net.sourceforge.jaad.mp4.api;

import java.io.EOFException;
import java.util.logging.Logger;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import net.sourceforge.jaad.mp4.MP4InputStream;
import net.sourceforge.jaad.mp4.boxes.Box;
import net.sourceforge.jaad.mp4.boxes.BoxTypes;
import net.sourceforge.jaad.mp4.boxes.impl.ChunkOffsetBox;
import net.sourceforge.jaad.mp4.boxes.impl.DataEntryUrlBox;
import net.sourceforge.jaad.mp4.boxes.impl.DataReferenceBox;
import net.sourceforge.jaad.mp4.boxes.impl.MediaHeaderBox;
import net.sourceforge.jaad.mp4.boxes.impl.SampleSizeBox;
import net.sourceforge.jaad.mp4.boxes.impl.SampleToChunkBox;
import net.sourceforge.jaad.mp4.boxes.impl.DecodingTimeToSampleBox;
import net.sourceforge.jaad.mp4.boxes.impl.TrackHeaderBox;
import net.sourceforge.jaad.mp4.od.DecoderSpecificInfo;
import net.sourceforge.jaad.mp4.boxes.impl.ESDBox;
import net.sourceforge.jaad.mp4.od.Descriptor;

/**
* This class represents a track in a movie.
*
* Each track contains either a decoder specific info as a byte array or a
* <code>DecoderInfo</code> object that contains necessary information for the
* decoder.
*
* @author in-somnia
*/
//TODO: expand javadoc; use generics for subclasses?
public abstract class Track {

  public interface Codec {
    //TODO: currently only marker interface
  }
  private final MP4InputStream in;
  protected final TrackHeaderBox tkhd;
  private final MediaHeaderBox mdhd;
  private final boolean inFile;
  private final List<Frame> frames;
  private URL location;
  private int currentFrame;
  //info structures
  protected DecoderSpecificInfo decoderSpecificInfo;
  protected DecoderInfo decoderInfo;
  protected Protection protection;
    private int lastFramePadding;
    private DecodingTimeToSampleBox stts;

  Track(Box trak, MP4InputStream in) {
    this.in = in;

    tkhd = (TrackHeaderBox) trak.getChild(BoxTypes.TRACK_HEADER_BOX);

    final Box mdia = trak.getChild(BoxTypes.MEDIA_BOX);
    mdhd = (MediaHeaderBox) mdia.getChild(BoxTypes.MEDIA_HEADER_BOX);
    final Box minf = mdia.getChild(BoxTypes.MEDIA_INFORMATION_BOX);

    final Box dinf = minf.getChild(BoxTypes.DATA_INFORMATION_BOX);
    final DataReferenceBox dref = (DataReferenceBox) dinf.getChild(BoxTypes.DATA_REFERENCE_BOX);
    //TODO: support URNs
    if(dref.hasChild(BoxTypes.DATA_ENTRY_URL_BOX)) {
      DataEntryUrlBox url = (DataEntryUrlBox) dref.getChild(BoxTypes.DATA_ENTRY_URL_BOX);
      inFile = url.isInFile();
      if(!inFile) {
        try {
          location = new URL(url.getLocation());
        }
        catch(MalformedURLException e) {
          Logger.getLogger("MP4 API").log(Level.WARNING, "Parsing URL-Box failed: {0}, url: {1}", new String[]{e.toString(), url.getLocation()});
          location = null;
        }
      }
    }
    /*else if(dref.containsChild(BoxTypes.DATA_ENTRY_URN_BOX)) {
    DataEntryUrnBox urn = (DataEntryUrnBox) dref.getChild(BoxTypes.DATA_ENTRY_URN_BOX);
    inFile = urn.isInFile();
    location = urn.getLocation();
    }*/
    else {
      inFile = true;
      location = null;
    }

    //sample table
    final Box stbl = minf.getChild(BoxTypes.SAMPLE_TABLE_BOX);
    if(stbl.hasChildren()) {
      frames = new ArrayList<Frame>();
      parseSampleTable(stbl);
    }
    else frames = Collections.emptyList();

        parseGaplessInfo();
    currentFrame = 0;
  }

  private void parseSampleTable(Box stbl) {
    final double timeScale = mdhd.getTimeScale();
    final Type type = getType();

    //sample sizes
    final long[] sampleSizes = ((SampleSizeBox) stbl.getChild(BoxTypes.SAMPLE_SIZE_BOX)).getSampleSizes();

    //chunk offsets
    final ChunkOffsetBox stco;
    if(stbl.hasChild(BoxTypes.CHUNK_OFFSET_BOX)) stco = (ChunkOffsetBox) stbl.getChild(BoxTypes.CHUNK_OFFSET_BOX);
    else stco = (ChunkOffsetBox) stbl.getChild(BoxTypes.CHUNK_LARGE_OFFSET_BOX);
    final long[] chunkOffsets = stco.getChunks();

    //samples to chunks
    final SampleToChunkBox stsc = ((SampleToChunkBox) stbl.getChild(BoxTypes.SAMPLE_TO_CHUNK_BOX));
    final long[] firstChunks = stsc.getFirstChunks();
    final long[] samplesPerChunk = stsc.getSamplesPerChunk();

    //sample durations/timestamps
        stts = (DecodingTimeToSampleBox) stbl.getChild(BoxTypes.DECODING_TIME_TO_SAMPLE_BOX);
    final long[] sampleCounts = stts.getSampleCounts();
    final long[] sampleDeltas = stts.getSampleDeltas();
    final long[] timeOffsets = new long[sampleSizes.length];
    long tmp = 0;
    int off = 0;
    for(int i = 0; i<sampleCounts.length; i++) {
      for(int j = 0; j<sampleCounts[i]; j++) {
        timeOffsets[off+j] = tmp;
        tmp += sampleDeltas[i];
      }
      off += sampleCounts[i];
    }

    //create samples
    int current = 0;
    int lastChunk;
    double timeStamp;
    long offset = 0;
    //iterate over all chunk groups
    for(int i = 0; i<firstChunks.length; i++) {
      if(i<firstChunks.length-1) lastChunk = (int) firstChunks[i+1]-1;
      else lastChunk = chunkOffsets.length;

      //iterate over all chunks in current group
      for(int j = (int) firstChunks[i]-1; j<lastChunk; j++) {
        offset = chunkOffsets[j];

        //iterate over all samples in current chunk
        for(int k = 0; k<samplesPerChunk[i]; k++) {
          //create samples
          timeStamp = ((double) timeOffsets[current])/timeScale;
          frames.add(new Frame(type, offset, sampleSizes[current], timeStamp));
          offset += sampleSizes[current];
          current++;
        }
      }
    }

    //frames need not to be time-ordered: sort by timestamp
    //TODO: is it possible to add them to the specific position?
    Collections.sort(frames);
  }

    private void parseGaplessInfo() {
        // if stts has two entries, last one is usually padding
        if (stts != null && stts.getSampleDeltas().length == 2) {
            lastFramePadding = (int) stts.getSampleDeltas()[1];
        }
    }

    //TODO: implement other entry descriptors
  protected void findDecoderSpecificInfo(ESDBox esds) {
    final Descriptor ed = esds.getEntryDescriptor();
    final List<Descriptor> children = ed.getChildren();
    List<Descriptor> children2;

    for(Descriptor e : children) {
      children2 = e.getChildren();
      for(Descriptor e2 : children2) {
        switch(e2.getType()) {
          case Descriptor.TYPE_DECODER_SPECIFIC_INFO:
            decoderSpecificInfo = (DecoderSpecificInfo) e2;
            break;
        }
      }
    }
  }

  protected <T> void parseSampleEntry(Box sampleEntry, Class<T> clazz) {
    T type;
    try {
      type = clazz.newInstance();
      if(sampleEntry.getClass().isInstance(type)) {
        System.out.println("true");
      }
    }
    catch(InstantiationException ex) {
      ex.printStackTrace();
    }
    catch(IllegalAccessException ex) {
      ex.printStackTrace();
    }
  }

  public abstract Type getType();

  public abstract Codec getCodec();

  //tkhd
  /**
   * Returns true if the track is enabled. A disabled track is treated as if
   * it were not present.
   * @return true if the track is enabled
   */
  public boolean isEnabled() {
    return tkhd.isTrackEnabled();
  }

  /**
   * Returns true if the track is used in the presentation.
   * @return true if the track is used
   */
  public boolean isUsed() {
    return tkhd.isTrackInMovie();
  }

  /**
   * Returns true if the track is used in previews.
   * @return true if the track is used in previews
   */
  public boolean isUsedForPreview() {
    return tkhd.isTrackInPreview();
  }

  /**
   * Returns the time this track was created.
   * @return the creation time
   */
  public Date getCreationTime() {
    return Utils.getDate(tkhd.getCreationTime());
  }

  /**
   * Returns the last time this track was modified.
   * @return the modification time
   */
  public Date getModificationTime() {
    return Utils.getDate(tkhd.getModificationTime());
  }

  //mdhd
  /**
   * Returns the language for this media.
   * @return the language
   */
  public Locale getLanguage() {
    return new Locale(mdhd.getLanguage());
  }

  /**
   * Returns true if the data for this track is present in this file (stream).
   * If not, <code>getLocation()</code> returns the URL where the data can be
   * found.
   * @return true if the data is in this file (stream), false otherwise
   */
  public boolean isInFile() {
    return inFile;
  }

  /**
   * If the data for this track is not present in this file (if
   * <code>isInFile</code> returns false), this method returns the data's
   * location. Else null is returned.
   * @return the data's location or null if the data is in this file
   */
  public URL getLocation() {
    return location;
  }

  //info structures
  /**
   * Returns the decoder specific info, if present. It contains configuration
   * data for the decoder. If the decoder specific info is not present, the
   * track contains a <code>DecoderInfo</code>.
   *
   * @see #getDecoderInfo()
   * @return the decoder specific info
   */
  public byte[] getDecoderSpecificInfo() {
    return decoderSpecificInfo.getData();
  }

  /**
   * Returns the <code>DecoderInfo</code>, if present. It contains
   * configuration information for the decoder. If the structure is not
   * present, the track contains a decoder specific info.
   *
   * @see #getDecoderSpecificInfo()
   * @return the codec specific structure
   */
  public DecoderInfo getDecoderInfo() {
    return decoderInfo;
  }

  /**
   * Returns the <code>ProtectionInformation</code> object that contains
   * details about the DRM system used. If no protection is present this
   * method returns null.
   *
   * @return a <code>ProtectionInformation</code> object or null if no
   * protection is used
   */
  public Protection getProtection() {
    return protection;
  }

  //reading
  /**
   * Indicates if there are more frames to be read in this track.
   *
   * @return true if there is at least one more frame to read.
   */
  public boolean hasMoreFrames() {
    return currentFrame<frames.size();
  }

  /**
   * Reads the next frame from this track. If it contains no more frames to
   * read, null is returned.
   *
   * @return the next frame or null if there are no more frames to read
   * @throws IOException if reading fails
   */
  public Frame readNextFrame() throws IOException {
    Frame frame = null;
    if(hasMoreFrames()) {
      frame = frames.get(currentFrame);

      final long diff = frame.getOffset()-in.getOffset();
      if(diff>0) in.skipBytes(diff);
      else if(diff<0) {
        if(in.hasRandomAccess()) in.seek(frame.getOffset());
        else {
          Logger.getLogger("MP4 API").log(Level.WARNING, "readNextFrame failed: frame {0} already skipped, offset:{1}, stream:{2}", new Object[]{currentFrame, frame.getOffset(), in.getOffset()});
          throw new IOException("frame already skipped and no random access");
        }
      }

      final byte[] b = new byte[(int) frame.getSize()];
      try {
        in.readBytes(b);
      }
      catch(EOFException e) {
        Logger.getLogger("MP4 API").log(Level.WARNING, "readNextFrame failed: tried to read {0} bytes at {1}", new Long[]{frame.getSize(), in.getOffset()});
        throw e;
      }
      frame.setData(b);
      currentFrame++;
    }
    return frame;
  }

  /**
   * This method tries to seek to the frame that is nearest to the given
   * timestamp. It returns the timestamp of the frame it seeked to or -1 if
   * none was found.
   *
   * @param timestamp a timestamp to seek to
   * @return the frame's timestamp that the method seeked to
   */
  public double seek(double timestamp) {
    //find first frame > timestamp
    Frame frame = null;
    for(int i = 0; i<frames.size(); i++) {
      frame = frames.get(i);
      if(frame.getTime()>timestamp) {
        currentFrame = i - 1;
        break;
      }
    }
    return (frame==null) ? -1 : frames.get(currentFrame).getTime();
  }

  /**
   * Returns the timestamp of the next frame to be read. This is needed to
   * read frames from a movie that contains multiple tracks.
   *
   * @return the next frame's timestamp
   */
  double getNextTimeStamp() {
    return frames.get(currentFrame).getTime();
  }

    /**
     * Returns duration of sample, based on stts data
     * @param sample sample number
     * @return duration in pcm samples
     */
    public long getSampleDuration(int sample) {
        int co = 0;

        for (int i = 0; i < stts.getSampleCounts().length; i++) {
            long delta = stts.getSampleCounts()[i];
            if (sample < co + delta)
                return stts.getSampleDeltas()[i];
            co += delta;
        }

        return 0;
    }

    public int getLastFramePadding() {
        return lastFramePadding;
    }

    public void setCurrentFrame(int frame) {
        currentFrame = frame;
    }

    public int getFrameCount() {
        return frames.size();
    }
}
TOP

Related Classes of net.sourceforge.jaad.mp4.api.Track$Codec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.