FileDocCategorySizeDatePackage
SmoothStreamingTrack.javaAPI Docmp4parser 1.0-RC-179338Wed Dec 19 20:10:21 GMT 2012com.googlecode.mp4parser.tools.smoothstreamingdownloader

SmoothStreamingTrack.java

package com.googlecode.mp4parser.tools.smoothstreamingdownloader;

import com.coremedia.iso.Hex;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.boxes.*;
import com.coremedia.iso.boxes.fragment.TrackFragmentBox;
import com.coremedia.iso.boxes.fragment.TrackRunBox;
import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
import com.coremedia.iso.boxes.mdat.SampleList;
import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.TrackMetaData;
import com.googlecode.mp4parser.util.Path;
import nu.xom.*;
import org.apache.commons.io.IOUtils;


import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

public class SmoothStreamingTrack implements Track {
    String fourCC;
    String codecPrivateData;
    TrackMetaData trackMetaData;
    List<IsoFile> fragments = new LinkedList<IsoFile>();

    public SmoothStreamingTrack(URI manifestFile, String type, String bitRate) throws IOException, ParsingException {
        Builder parser = new Builder();
        Document doc = parser.build(manifestFile.toASCIIString());
        Nodes levelNodes = doc.query(String.format("/SmoothStreamingMedia/StreamIndex[@Type='%s']/QualityLevel[@Bitrate=%s]", type, bitRate));
        Nodes fragments = doc.query(String.format("/SmoothStreamingMedia/StreamIndex[@Type='%s']/c", type));
        if (levelNodes.size() != 1) {
            throw new IOException("Something's wrong");
        }
        Element level = (Element) levelNodes.get(0);
        fourCC = level.getAttribute("FourCC").getValue();
        codecPrivateData = level.getAttribute("CodecPrivateData").getValue();

        trackMetaData = new TrackMetaData();
        if ("avc1".equalsIgnoreCase(fourCC)) {
            // video!
            trackMetaData.setHeight(Double.parseDouble(level.getAttribute("MaxHeight").getValue()));
            trackMetaData.setWidth(Double.parseDouble(level.getAttribute("MaxWidth").getValue()));
            trackMetaData.setTimescale(10000000L); // That's the default but it may be some other value
        }

        String url = ((Element) level.getParent()).getAttribute("Url").getValue();
        long time = 0;
        for (int i = 0; i < fragments.size(); i++) {
            URI fragment = manifestFile.resolve(url.replace("{bitrate}", bitRate).replace("{start time}", Long.toString(time)));
            this.fragments.add(new IsoFile(Channels.newChannel(fragment.toURL().openStream())));
            time += Long.parseLong(((Element) fragments.get(i)).getAttribute("d").getValue());
        }


    }

    @Override
    public SampleDescriptionBox getSampleDescriptionBox() {
        SampleDescriptionBox stsd = new SampleDescriptionBox();
        if ("avc1".equalsIgnoreCase(fourCC)) {
            VisualSampleEntry vse = new VisualSampleEntry("avc1");
            vse.setHorizresolution((int) trackMetaData.getWidth());
            vse.setHeight((int) trackMetaData.getHeight());


            AvcConfigurationBox avcC = new AvcConfigurationBox();
            vse.addBox(avcC);
            AvcConfigurationBox.AVCDecoderConfigurationRecord record = new AvcConfigurationBox.AVCDecoderConfigurationRecord();
            ByteBuffer spsAndPps = ByteBuffer.wrap(Hex.decodeHex(codecPrivateData));

            ByteBuffer allSequenceParameterSets = findNextNal(spsAndPps);
            List<byte[]> sequenceParameterSets = new LinkedList<byte[]>();
            allSequenceParameterSets.position(0);
            while (allSequenceParameterSets.position() <= allSequenceParameterSets.limit()) {
                int length = IsoTypeReader.readUInt16(allSequenceParameterSets);
                sequenceParameterSets.add((byte[]) allSequenceParameterSets.duplicate().slice().limit(length).array());
                allSequenceParameterSets.position(allSequenceParameterSets.position() + length);
            }

            ByteBuffer allPictureParameterSets = findNextNal(spsAndPps);
            List<byte[]> pictureParameterSets = new LinkedList<byte[]>();
            allPictureParameterSets.position(0);
            while (allPictureParameterSets.position() <= allPictureParameterSets.limit()) {
                int length = IsoTypeReader.readUInt16(allPictureParameterSets);
                pictureParameterSets.add((byte[]) allPictureParameterSets.duplicate().slice().limit(length).array());
                allPictureParameterSets.position(allPictureParameterSets.position() + length);
            }

            avcC.setSequenceParameterSets(sequenceParameterSets);
            avcC.setPictureParameterSets(pictureParameterSets);
            stsd.addBox(vse);
        }
        return stsd;
    }

    @Override
    public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
        List<TimeToSampleBox.Entry> entries = new LinkedList<TimeToSampleBox.Entry>();
        for (IsoFile fragment : fragments) {
            TrackRunBox trun = (TrackRunBox) Path.getPath(fragment, "/moof[0]/traf[0]/trun[0]");
            for (TrackRunBox.Entry entry : trun.getEntries()) {
                entries.add(new TimeToSampleBox.Entry(entry.getSampleDuration(), 1));
            }
        }
        return entries;

    }

    @Override
    public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
        List<CompositionTimeToSample.Entry> entries = new LinkedList<CompositionTimeToSample.Entry>();
        for (IsoFile fragment : fragments) {
            TrackRunBox trun = (TrackRunBox) Path.getPath(fragment, "/moof[0]/traf[0]/trun[0]");
            for (TrackRunBox.Entry entry : trun.getEntries()) {
                entries.add(new CompositionTimeToSample.Entry(entry.getSampleCompositionTimeOffset(), 1));
            }
        }
        return entries;
    }

    @Override
    public long[] getSyncSamples() {
        int numSyncSamples = 0;
        for (IsoFile fragment : fragments) {
            TrackRunBox trun = (TrackRunBox) Path.getPath(fragment, "/moof[0]/traf[0]/trun[0]");
            for (TrackRunBox.Entry entry : trun.getEntries()) {
                if (!entry.getSampleFlags().isSampleIsDifferenceSample()) {
                    numSyncSamples++;
                }
            }
        }
        long[] syncSamples = new long[numSyncSamples];
        int sampleNum = 1;
        int syncSampleNum = 0;
        for (IsoFile fragment : fragments) {
            TrackRunBox trun = (TrackRunBox) Path.getPath(fragment, "/moof[0]/traf[0]/trun[0]");
            for (TrackRunBox.Entry entry : trun.getEntries()) {
                if (!entry.getSampleFlags().isSampleIsDifferenceSample()) {
                    syncSamples[syncSampleNum++] = sampleNum;
                }
                sampleNum++;
            }
        }
        return syncSamples;

    }

    @Override
    public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
        return null; // no sample dependencies for us
    }

    @Override
    public TrackMetaData getTrackMetaData() {
        return trackMetaData;
    }

    @Override
    public String getHandler() {
        if ("avc1".equalsIgnoreCase(fourCC)) {
            return "vide";
        }
        throw new RuntimeException();
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean isInMovie() {
        return true;
    }

    @Override
    public boolean isInPreview() {
        return true;
    }

    @Override
    public boolean isInPoster() {
        return true;
    }

    @Override
    public List<ByteBuffer> getSamples() {
        List<ByteBuffer> samples = new ArrayList<ByteBuffer>();
        for (IsoFile fragment : fragments) {
            TrackFragmentBox traf = (TrackFragmentBox) Path.getPath(fragment, "/moof[0]/traf[0]");
            samples.addAll(new SampleList(traf));
        }
        return samples;
    }

    @Override
    public Box getMediaHeaderBox() {
        if ("avc1".equalsIgnoreCase(fourCC)) {
            return new VideoMediaHeaderBox();

        }
        throw new RuntimeException();
    }

    @Override
    public SubSampleInformationBox getSubsampleInformationBox() {
        return null; // no subsamples for us
    }

    private ByteBuffer findNextNal(ByteBuffer bb) {
        byte[] test = new byte[]{-1, -1, -1, -1};
        int start = -1;

        int c;
        while ((c = bb.get()) != -1) {
            test[0] = test[1];
            test[1] = test[2];
            test[2] = test[3];
            test[3] = (byte) c;
            if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) {

                if (start == -1) {
                    start = bb.position();
                    test = new byte[]{-1, -1, -1, -1};
                } else {
                    break;
                }

            }
        }
        if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) {
            bb.position(bb.position() - 4);
        }
        int length = bb.position() - start;
        ByteBuffer nu = bb.duplicate();
        nu.position(start);
        nu.slice();
        nu.limit(length);
        return nu;
    }

}