package com.castlabs.dash.dashfragmenter; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.builder.Fragmenter; import com.googlecode.mp4parser.util.Mp4Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.googlecode.mp4parser.util.CastUtils.l2i; import static java.util.Arrays.binarySearch; public class BetterTrackGroupFragmenter implements Fragmenter { private final Map<Track, long[]> allTracks = new HashMap<>(); public BetterTrackGroupFragmenter(double targetDuration, List<Track> allTracks) { if (allTracks.size() > 1) { for (Track track : allTracks) { if (track.getSyncSamples() == null) { throw new RuntimeException("BetterTrackGroupFragmenter cannot be used to fragment groups of tracks without syncsamples"); } } } long[] timeScale = new long[allTracks.size()]; long[][] syncSamples = new long[allTracks.size()][]; long[][] sampleDuration = new long[allTracks.size()][]; for (int i = 0; i < allTracks.size(); i++) { syncSamples[i] = allTracks.get(i).getSyncSamples(); sampleDuration[i] = allTracks.get(i).getSampleDurations(); timeScale[i] = allTracks.get(i).getTrackMetaData().getTimescale(); } long[][] fragmentSamples = getCommonSyncSamples(timeScale, syncSamples, sampleDuration, targetDuration); for (int i = 0; i < fragmentSamples.length; i++) { this.allTracks.put(allTracks.get(i), fragmentSamples[i]); } } protected static long[][] getCommonSyncSamples(long[] timeScale, long[][] syncSamples, long[][] sampleDuration, double targetDuration) { long[][] fragmentSamples = new long[syncSamples.length][]; int[] currentSyncSample = new int[syncSamples.length]; long[] currentTicks = new long[syncSamples.length]; double nextTarget = 0; do { // Check if we found a common sync sample if (hasFoundNextCommonSyncSample(currentTicks, timeScale, nextTarget)) { for (int i = 0; i < currentSyncSample.length; i++) { fragmentSamples[i] = Mp4Arrays.copyOfAndAppend(fragmentSamples[i] ,syncSamples[i][currentSyncSample[i]]); nextTarget = (double)currentTicks[0] / (double)timeScale[0]; } nextTarget += targetDuration; } // Advance the least advanced track int j = 0; for (int i = 0; i < currentTicks.length; i++) { if ((double)currentTicks[i]/timeScale[i] < (double)currentTicks[j]/timeScale[j]) { j = i; } } if (syncSamples[j].length > currentSyncSample[j] + 1) { int start = currentSyncSample[j]; int end = ++currentSyncSample[j]; for (int i = l2i(syncSamples[j][start]); i < syncSamples[j][end]; i++) { currentTicks[j] += sampleDuration[j][i]; } } else { break; } } while (true); return fragmentSamples; } private static boolean hasFoundNextCommonSyncSample(long[] tickss, long[] timescales, double nextTarget) { double currentTime = (double)tickss[0] / timescales[0]; for (int i = 0; i < tickss.length; i++) { long ticks = tickss[i]; long timescale = timescales[i]; if (ticks < nextTarget * timescale) { // not yet far enough! return false; } if (Math.abs(currentTime - ((double)tickss[i] / timescales[i])) > 0.0001) { return false; } } return true; } public long[] sampleNumbers(Track track) { if (!allTracks.containsKey(track)) { throw new IllegalArgumentException("track argument needs to be contained allTracks constructor argument"); } return allTracks.get(track); } }