/*
* Copyright 2012 Sebastian Annies, Hamburg
*
* 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.mp4parser.muxer;
import org.mp4parser.Container;
import org.mp4parser.boxes.iso14496.part12.*;
import org.mp4parser.boxes.samplegrouping.GroupEntry;
import org.mp4parser.boxes.samplegrouping.SampleGroupDescriptionBox;
import org.mp4parser.boxes.samplegrouping.SampleToGroupBox;
import org.mp4parser.muxer.samples.SampleList;
import org.mp4parser.tools.Mp4Arrays;
import org.mp4parser.tools.Path;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.mp4parser.tools.CastUtils.l2i;
/**
* Represents a single track of an MP4 file.
*/
public class Mp4TrackImpl extends AbstractTrack {
private List<Sample> samples;
private SampleDescriptionBox sampleDescriptionBox;
private long[] decodingTimes;
private List<CompositionTimeToSample.Entry> compositionTimeEntries;
private long[] syncSamples = null;
private List<SampleDependencyTypeBox.Entry> sampleDependencies;
private TrackMetaData trackMetaData = new TrackMetaData();
private String handler;
private SubSampleInformationBox subSampleInformationBox = null;
/**
* Creates a track from a TrackBox and potentially fragments. Use <b>fragements parameter
* only</b> to supply additional fragments that are not located in the main file.
*
* @param trackId ID of the track to extract
* @param isofile the parsed MP4 file
* @param randomAccess the RandomAccessSource to read the samples from
* @param name an arbitrary naem to identify track later - e.g. filename
*/
public Mp4TrackImpl(final long trackId, Container isofile, RandomAccessSource randomAccess, String name) {
super(name);
samples = new SampleList(trackId, isofile, randomAccess);
TrackBox trackBox = null;
for (TrackBox box : Path.<TrackBox>getPaths(isofile, "moov/trak")) {
if (box.getTrackHeaderBox().getTrackId() == trackId) {
trackBox = box;
break;
}
}
assert trackBox != null : "Could not find TrackBox with trackID " + trackId;
SampleTableBox stbl = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox();
handler = trackBox.getMediaBox().getHandlerBox().getHandlerType();
List<TimeToSampleBox.Entry> decodingTimeEntries = new ArrayList<TimeToSampleBox.Entry>();
compositionTimeEntries = new ArrayList<CompositionTimeToSample.Entry>();
sampleDependencies = new ArrayList<SampleDependencyTypeBox.Entry>();
decodingTimeEntries.addAll(stbl.getTimeToSampleBox().getEntries());
if (stbl.getCompositionTimeToSample() != null) {
compositionTimeEntries.addAll(stbl.getCompositionTimeToSample().getEntries());
}
if (stbl.getSampleDependencyTypeBox() != null) {
sampleDependencies.addAll(stbl.getSampleDependencyTypeBox().getEntries());
}
if (stbl.getSyncSampleBox() != null) {
syncSamples = stbl.getSyncSampleBox().getSampleNumber();
}
subSampleInformationBox = Path.getPath(stbl, "subs");
// gather all movie fragment boxes from the fragments
List<MovieFragmentBox> movieFragmentBoxes = new ArrayList<MovieFragmentBox>();
movieFragmentBoxes.addAll(isofile.getBoxes(MovieFragmentBox.class));
sampleDescriptionBox = stbl.getSampleDescriptionBox();
int lastSubsSample = 0;
final List<MovieExtendsBox> movieExtendsBoxes = Path.getPaths(isofile, "moov/mvex");
if (movieExtendsBoxes.size() > 0) {
for (MovieExtendsBox mvex : movieExtendsBoxes) {
final List<TrackExtendsBox> trackExtendsBoxes = mvex.getBoxes(TrackExtendsBox.class);
for (TrackExtendsBox trex : trackExtendsBoxes) {
if (trex.getTrackId() == trackId) {
List<SubSampleInformationBox> subss = Path.getPaths(isofile, "moof/traf/subs");
if (subss.size() > 0) {
subSampleInformationBox = new SubSampleInformationBox();
}
long sampleNumber = 1;
for (MovieFragmentBox movieFragmentBox : movieFragmentBoxes) {
List<TrackFragmentBox> trafs = movieFragmentBox.getBoxes(TrackFragmentBox.class);
for (TrackFragmentBox traf : trafs) {
if (traf.getTrackFragmentHeaderBox().getTrackId() == trackId) {
sampleGroups = getSampleGroups(
stbl.getBoxes(SampleGroupDescriptionBox.class), // global descriptions
Path.<SampleGroupDescriptionBox>getPaths((Container) traf, "sgpd"), // local description
Path.<SampleToGroupBox>getPaths((Container) traf, "sbgp"),
sampleGroups, sampleNumber - 1);
SubSampleInformationBox subs = Path.getPath(traf, "subs");
if (subs != null) {
long difFromLastFragment = sampleNumber - lastSubsSample - 1;
for (SubSampleInformationBox.SubSampleEntry subSampleEntry : subs.getEntries()) {
SubSampleInformationBox.SubSampleEntry se = new SubSampleInformationBox.SubSampleEntry();
se.getSubsampleEntries().addAll(subSampleEntry.getSubsampleEntries());
if (difFromLastFragment != 0) {
se.setSampleDelta(difFromLastFragment + subSampleEntry.getSampleDelta());
difFromLastFragment = 0;
} else {
se.setSampleDelta(subSampleEntry.getSampleDelta());
}
subSampleInformationBox.getEntries().add(se);
}
}
List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
for (TrackRunBox trun : truns) {
final TrackFragmentHeaderBox tfhd = traf.getTrackFragmentHeaderBox();
boolean first = true;
for (TrackRunBox.Entry entry : trun.getEntries()) {
if (trun.isSampleDurationPresent()) {
if (decodingTimeEntries.size() == 0 ||
decodingTimeEntries.get(decodingTimeEntries.size() - 1).getDelta() != entry.getSampleDuration()) {
decodingTimeEntries.add(new TimeToSampleBox.Entry(1, entry.getSampleDuration()));
} else {
TimeToSampleBox.Entry e = decodingTimeEntries.get(decodingTimeEntries.size() - 1);
e.setCount(e.getCount() + 1);
}
} else {
if (tfhd.hasDefaultSampleDuration()) {
decodingTimeEntries.add(new TimeToSampleBox.Entry(1, tfhd.getDefaultSampleDuration()));
} else {
decodingTimeEntries.add(new TimeToSampleBox.Entry(1, trex.getDefaultSampleDuration()));
}
}
if (trun.isSampleCompositionTimeOffsetPresent()) {
if (compositionTimeEntries.size() == 0 ||
compositionTimeEntries.get(compositionTimeEntries.size() - 1).getOffset() != entry.getSampleCompositionTimeOffset()) {
compositionTimeEntries.add(new CompositionTimeToSample.Entry(1, l2i(entry.getSampleCompositionTimeOffset())));
} else {
CompositionTimeToSample.Entry e = compositionTimeEntries.get(compositionTimeEntries.size() - 1);
e.setCount(e.getCount() + 1);
}
}
final SampleFlags sampleFlags;
if (trun.isSampleFlagsPresent()) {
sampleFlags = entry.getSampleFlags();
} else {
if (first && trun.isFirstSampleFlagsPresent()) {
sampleFlags = trun.getFirstSampleFlags();
} else {
if (tfhd.hasDefaultSampleFlags()) {
sampleFlags = tfhd.getDefaultSampleFlags();
} else {
sampleFlags = trex.getDefaultSampleFlags();
}
}
}
if (sampleFlags != null && !sampleFlags.isSampleIsDifferenceSample()) {
//iframe
syncSamples = Mp4Arrays.copyOfAndAppend(syncSamples, sampleNumber);
}
sampleNumber++;
first = false;
}
}
}
}
}
}
}
}
for (MovieFragmentBox movieFragmentBox : movieFragmentBoxes) {
for (TrackFragmentBox traf : movieFragmentBox.getBoxes(TrackFragmentBox.class)) {
if (traf.getTrackFragmentHeaderBox().getTrackId() == trackId) {
sampleGroups = getSampleGroups(
stbl.getBoxes(SampleGroupDescriptionBox.class),
Path.<SampleGroupDescriptionBox>getPaths((Container) traf, "sgpd"),
Path.<SampleToGroupBox>getPaths((Container) traf, "sbgp"), sampleGroups, 0);
}
}
}
} else {
sampleGroups = getSampleGroups(stbl.getBoxes(SampleGroupDescriptionBox.class), null, stbl.getBoxes(SampleToGroupBox.class), sampleGroups, 0);
}
decodingTimes = TimeToSampleBox.blowupTimeToSamples(decodingTimeEntries);
MediaHeaderBox mdhd = trackBox.getMediaBox().getMediaHeaderBox();
TrackHeaderBox tkhd = trackBox.getTrackHeaderBox();
trackMetaData.setTrackId(tkhd.getTrackId());
trackMetaData.setCreationTime(mdhd.getCreationTime());
trackMetaData.setLanguage(mdhd.getLanguage());
trackMetaData.setModificationTime(mdhd.getModificationTime());
trackMetaData.setTimescale(mdhd.getTimescale());
trackMetaData.setHeight(tkhd.getHeight());
trackMetaData.setWidth(tkhd.getWidth());
trackMetaData.setLayer(tkhd.getLayer());
trackMetaData.setMatrix(tkhd.getMatrix());
trackMetaData.setVolume(tkhd.getVolume());
EditListBox elst = Path.getPath(trackBox, "edts/elst");
MovieHeaderBox mvhd = Path.getPath(isofile, "moov/mvhd");
if (elst != null) {
assert mvhd != null;
for (EditListBox.Entry e : elst.getEntries()) {
edits.add(new Edit(e.getMediaTime(), mdhd.getTimescale(), e.getMediaRate(), (double) e.getSegmentDuration() / mvhd.getTimescale()));
}
}
}
private Map<GroupEntry, long[]> getSampleGroups(List<SampleGroupDescriptionBox> globalSgdbs, List<SampleGroupDescriptionBox> localSgdbs, List<SampleToGroupBox> sbgps,
Map<GroupEntry, long[]> sampleGroups, long startIndex) {
for (SampleToGroupBox sbgp : sbgps) {
int sampleNum = 0;
for (SampleToGroupBox.Entry entry : sbgp.getEntries()) {
if (entry.getGroupDescriptionIndex() > 0) {
GroupEntry groupEntry = null;
if (entry.getGroupDescriptionIndex() > 0xffff) {
for (SampleGroupDescriptionBox localSgdb : localSgdbs) {
if (localSgdb.getGroupingType().equals(sbgp.getGroupingType())) {
groupEntry = localSgdb.getGroupEntries().get((entry.getGroupDescriptionIndex() - 1) & 0xffff);
}
}
} else {
for (SampleGroupDescriptionBox globalSgdb : globalSgdbs) {
if (globalSgdb.getGroupingType().equals(sbgp.getGroupingType())) {
groupEntry = globalSgdb.getGroupEntries().get((entry.getGroupDescriptionIndex() - 1));
}
}
}
assert groupEntry != null;
long[] samples = sampleGroups.get(groupEntry);
if (samples == null) {
samples = new long[0];
}
long[] nuSamples = new long[l2i(entry.getSampleCount()) + samples.length];
System.arraycopy(samples, 0, nuSamples, 0, samples.length);
for (int i = 0; i < entry.getSampleCount(); i++) {
nuSamples[samples.length + i] = startIndex + sampleNum + i;
}
sampleGroups.put(groupEntry, nuSamples);
}
sampleNum += entry.getSampleCount();
}
}
return sampleGroups;
}
public void close() throws IOException {
}
public List<Sample> getSamples() {
return samples;
}
public synchronized long[] getSampleDurations() {
return decodingTimes;
}
public SampleDescriptionBox getSampleDescriptionBox() {
return sampleDescriptionBox;
}
public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
return compositionTimeEntries;
}
public long[] getSyncSamples() {
if (syncSamples == null || syncSamples.length == samples.size()) {
return null;
} else {
return syncSamples;
}
}
public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
return sampleDependencies;
}
public TrackMetaData getTrackMetaData() {
return trackMetaData;
}
public String getHandler() {
return handler;
}
public SubSampleInformationBox getSubsampleInformationBox() {
return subSampleInformationBox;
}
}