@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; String formatId = initializationChunk.format.id; RepresentationHolder representationHolder = representationHolders.get(formatId); if (initializationChunk.hasFormat()) { representationHolder.format = initializationChunk.getFormat(); } if (initializationChunk.hasSeekMap()) { representationHolder.segmentIndex = new DashWrappingSegmentIndex( (ChunkIndex) initializationChunk.getSeekMap(), initializationChunk.dataSpec.uri.toString(), representationHolder.representation.periodStartMs * 1000); } // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3. if (drmInitData == null && initializationChunk.hasDrmInitData()) { drmInitData = initializationChunk.getDrmInitData(); } } }
private void assertIndex(int timecodeScale, int cuePointCount) { ChunkIndex index = (ChunkIndex) extractorOutput.seekMap; assertEquals(cuePointCount, index.length); for (int i = 0; i < cuePointCount - 1; i++) { assertEquals(Util.scaleLargeTimestamp(10 * i, timecodeScale, 1000), index.timesUs[i]); assertEquals(Util.scaleLargeTimestamp(10, timecodeScale, 1000), index.durationsUs[i]); assertEquals(0, index.sizes[i]); } int lastIndex = cuePointCount - 1; long lastTimecode = 10 * lastIndex; long lastDurationTimecode = TEST_DURATION_TIMECODE - lastTimecode; assertEquals(Util.scaleLargeTimestamp(lastTimecode, timecodeScale, 1000), index.timesUs[lastIndex]); assertEquals(Util.scaleLargeTimestamp(lastDurationTimecode, timecodeScale, 1000), index.durationsUs[lastIndex]); }
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) { if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(leaf); } else if (leaf.type == Atom.TYPE_sidx) { ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); extractorOutput.seekMap(segmentIndex); haveOutputSeekMap = true; } }
/** * Builds a {@link SeekMap} from the recently gathered Cues information. * * @return The built {@link SeekMap}. May be {@link SeekMap#UNSEEKABLE} if cues information was * missing or incomplete. */ private SeekMap buildSeekMap() { if (segmentContentPosition == UNKNOWN || durationUs == C.UNKNOWN_TIME_US || cueTimesUs == null || cueTimesUs.size() == 0 || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { // Cues information is missing or incomplete. cueTimesUs = null; cueClusterPositions = null; return SeekMap.UNSEEKABLE; } int cuePointsSize = cueTimesUs.size(); int[] sizes = new int[cuePointsSize]; long[] offsets = new long[cuePointsSize]; long[] durationsUs = new long[cuePointsSize]; long[] timesUs = new long[cuePointsSize]; for (int i = 0; i < cuePointsSize; i++) { timesUs[i] = cueTimesUs.get(i); offsets[i] = segmentContentPosition + cueClusterPositions.get(i); } for (int i = 0; i < cuePointsSize - 1; i++) { sizes[i] = (int) (offsets[i + 1] - offsets[i]); durationsUs[i] = timesUs[i + 1] - timesUs[i]; } sizes[cuePointsSize - 1] = (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; cueTimesUs = null; cueClusterPositions = null; return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(leaf); } else if (leaf.type == Atom.TYPE_sidx) { ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); extractorOutput.seekMap(segmentIndex); haveOutputSeekMap = true; } else if (leaf.type == Atom.TYPE_emsg) { parseEmsg(leaf.data, inputPosition); } }
@Override public void onChunkLoadCompleted(Chunk chunk) { if (chunk instanceof InitializationChunk) { InitializationChunk initializationChunk = (InitializationChunk) chunk; String formatId = initializationChunk.format.id; PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId); if (periodHolder == null) { // period for this initialization chunk may no longer be on the manifest return; } RepresentationHolder representationHolder = periodHolder.representationHolders.get(formatId); if (initializationChunk.hasFormat()) { representationHolder.mediaFormat = initializationChunk.getFormat(); } // The null check avoids overwriting an index obtained from the manifest with one obtained // from the stream. If the manifest defines an index then the stream shouldn't, but in cases // where it does we should ignore it. if (representationHolder.segmentIndex == null && initializationChunk.hasSeekMap()) { representationHolder.segmentIndex = new DashWrappingSegmentIndex( (ChunkIndex) initializationChunk.getSeekMap(), initializationChunk.dataSpec.uri.toString()); } // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3. if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) { periodHolder.drmInitData = initializationChunk.getDrmInitData(); } } }
/** * Parses a sidx atom (defined in 14496-12). */ private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition) { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw new IllegalStateException("Unhandled indirect reference"); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7fffffff & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
/** * @param chunkIndex The {@link ChunkIndex} to wrap. * @param uri The URI where the data is located. * @param startTimeUs The start time of the index, in microseconds. */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex, String uri, long startTimeUs) { this.chunkIndex = chunkIndex; this.uri = uri; this.startTimeUs = startTimeUs; }
/** * Parses a sidx atom (defined in 14496-12). */ private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition) throws ParserException { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw new ParserException("Unhandled indirect reference"); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7FFFFFFF & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return new ChunkIndex(sizes, offsets, durationsUs, timesUs); }
/** * @param chunkIndex The {@link ChunkIndex} to wrap. * @param uri The URI where the data is located. */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex, String uri) { this.chunkIndex = chunkIndex; this.uri = uri; }