@Override public boolean prepare() throws ParserException { if (!prepared) { if (maybeSelfContained) { // Read up to the first sample. Once we're there, we know that the extractor must have // parsed a moov atom if the chunk contains one. NonBlockingInputStream inputStream = getNonBlockingInputStream(); Assertions.checkState(inputStream != null); int result = extractor.read(inputStream, null); prepared = (result & Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0; } else { // We know there isn't a moov atom. The extractor must have parsed one from a separate // initialization chunk. prepared = true; } if (prepared) { mediaFormat = extractor.getFormat(); Map<UUID, byte[]> extractorPsshInfo = extractor.getPsshInfo(); if (extractorPsshInfo != null) { psshInfo = extractorPsshInfo; } } } return prepared; }
@Override public boolean prepare() throws ParserException { if (!prepared) { if (maybeSelfContained) { // Read up to the first sample. Once we're there, we know that the extractor must have // parsed a moov atom if the chunk contains one. NonBlockingInputStream inputStream = getNonBlockingInputStream(); Assertions.checkState(inputStream != null); int result = extractor.read(inputStream, null); prepared = (result & Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0; } else { // We know there isn't a moov atom. The extractor must have parsed one from a separate // initialization chunk. prepared = true; } if (prepared) { mediaFormat = Assertions.checkNotNull(extractor.getFormat()); psshInfo = extractor.getPsshInfo(); } } return prepared; }
/** * @deprecated Use the other constructor, passing null as {@code psshInfo}. */ @Deprecated public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) { this(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex, extractor, null, maybeSelfContained, sampleOffsetUs); }
@Override public boolean read(SampleHolder holder) throws ParserException { NonBlockingInputStream inputStream = getNonBlockingInputStream(); Assertions.checkState(inputStream != null); int result = extractor.read(inputStream, holder); boolean sampleRead = (result & Extractor.RESULT_READ_SAMPLE) != 0; if (sampleRead) { holder.timeUs -= sampleOffsetUs; } return sampleRead; }
private DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher, MediaPresentationDescription initialManifest, int adaptationSetIndex, int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator, long liveEdgeLatencyUs) { this.manifestFetcher = manifestFetcher; this.currentManifest = initialManifest; this.adaptationSetIndex = adaptationSetIndex; this.representationIndices = representationIndices; this.dataSource = dataSource; this.evaluator = formatEvaluator; this.liveEdgeLatencyUs = liveEdgeLatencyUs; this.evaluation = new Evaluation(); this.headerBuilder = new StringBuilder(); psshInfo = getPsshInfo(currentManifest, adaptationSetIndex); Representation[] representations = getFilteredRepresentations(currentManifest, adaptationSetIndex, representationIndices); long periodDurationUs = (representations[0].periodDurationMs == TrackRenderer.UNKNOWN_TIME_US) ? TrackRenderer.UNKNOWN_TIME_US : representations[0].periodDurationMs * 1000; this.trackInfo = new TrackInfo(representations[0].format.mimeType, periodDurationUs); this.formats = new Format[representations.length]; this.representationHolders = new HashMap<String, RepresentationHolder>(); int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < representations.length; i++) { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); maxHeight = Math.max(formats[i].height, maxHeight); Extractor extractor = mimeTypeIsWebm(formats[i].mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor(); representationHolders.put(formats[i].id, new RepresentationHolder(representations[i], extractor)); } this.maxWidth = maxWidth; this.maxHeight = maxHeight; Arrays.sort(formats, new DecreasingBandwidthComparator()); }
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, Representation representation, Extractor extractor, DataSource dataSource, int trigger) { int expectedExtractorResult = Extractor.RESULT_END_OF_STREAM; long indexAnchor = 0; RangedUri requestUri; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. expectedExtractorResult |= Extractor.RESULT_READ_INIT; requestUri = initializationUri.attemptMerge(indexUri); if (requestUri != null) { expectedExtractorResult |= Extractor.RESULT_READ_INDEX; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } } else { requestUri = initializationUri; } } else { requestUri = indexUri; if (extractor.hasRelativeIndexOffsets()) { indexAnchor = indexUri.start + indexUri.length; } expectedExtractorResult |= Extractor.RESULT_READ_INDEX; } DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, representation.getCacheKey()); return new InitializationLoadable(dataSource, dataSpec, trigger, representation.format, extractor, expectedExtractorResult, indexAnchor); }
public InitializationLoadable(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, Extractor extractor, int expectedExtractorResult, long indexAnchor) { super(dataSource, dataSpec, format, trigger); this.extractor = extractor; this.expectedExtractorResult = expectedExtractorResult; this.indexAnchor = indexAnchor; this.uri = dataSpec.uri; }
@Override protected void consumeStream(NonBlockingInputStream stream) throws IOException { int result = extractor.read(stream, null); if (result != expectedExtractorResult) { throw new ParserException("Invalid extractor result. Expected " + expectedExtractorResult + ", got " + result); } if ((result & Extractor.RESULT_READ_INDEX) != 0) { representationHolders.get(format.id).segmentIndex = new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor); } }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, Extractor extractor, Map<UUID, byte[]> psshInfo, DataSource dataSource, int chunkIndex, boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) { int nextChunkIndex = isLast ? -1 : chunkIndex + 1; long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs; long offset = 0; DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey); // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. // To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs. return new Mp4MediaChunk(dataSource, dataSpec, formatInfo, trigger, chunkStartTimeUs, nextStartTimeUs, nextChunkIndex, extractor, psshInfo, false, -chunkStartTimeUs); }
/** * @param dataSource A {@link DataSource} suitable for loading the media data. * @param evaluator Selects from the available formats. * @param representations The representations to be considered by the source. */ public DashChunkSource(DataSource dataSource, FormatEvaluator evaluator, Representation... representations) { this.dataSource = dataSource; this.evaluator = evaluator; this.formats = new Format[representations.length]; this.extractors = new HashMap<String, Extractor>(); this.segmentIndexes = new HashMap<String, DashSegmentIndex>(); this.representations = new HashMap<String, Representation>(); this.trackInfo = new TrackInfo(representations[0].format.mimeType, representations[0].periodDurationMs * 1000); this.evaluation = new Evaluation(); int maxWidth = 0; int maxHeight = 0; for (int i = 0; i < representations.length; i++) { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); maxHeight = Math.max(formats[i].height, maxHeight); Extractor extractor = formats[i].mimeType.startsWith(MimeTypes.VIDEO_WEBM) ? new WebmExtractor() : new FragmentedMp4Extractor(); extractors.put(formats[i].id, extractor); this.representations.put(formats[i].id, representations[i]); DashSegmentIndex segmentIndex = representations[i].getIndex(); if (segmentIndex != null) { segmentIndexes.put(formats[i].id, segmentIndex); } } this.maxWidth = maxWidth; this.maxHeight = maxHeight; Arrays.sort(formats, new DecreasingBandwidthComparator()); }
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex, Extractor extractor, DataSource dataSource, int segmentNum, int trigger) { int lastSegmentNum = segmentIndex.getLastSegmentNum(); int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1) : startTimeUs + segmentIndex.getDurationUs(segmentNum); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, endTimeUs, nextSegmentNum, extractor, false, 0); }
@Override protected void consumeStream(NonBlockingInputStream stream) throws IOException { int result = extractor.read(stream, null); if (result != expectedExtractorResult) { throw new ParserException("Invalid extractor result. Expected " + expectedExtractorResult + ", got " + result); } if ((result & Extractor.RESULT_READ_INDEX) != 0) { segmentIndexes.put(format.id, new DashWrappingSegmentIndex(extractor.getIndex(), uri, indexAnchor)); } }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, Extractor extractor, DataSource dataSource, int chunkIndex, boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) { int nextChunkIndex = isLast ? -1 : chunkIndex + 1; long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs; long offset = 0; DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey); // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. // To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs. return new Mp4MediaChunk(dataSource, dataSpec, formatInfo, trigger, chunkStartTimeUs, nextStartTimeUs, nextChunkIndex, extractor, false, -chunkStartTimeUs); }
@Override public boolean sampleAvailable() throws ParserException { NonBlockingInputStream inputStream = getNonBlockingInputStream(); int result = extractor.read(inputStream, null); return (result & Extractor.RESULT_NEED_SAMPLE_HOLDER) != 0; }
public RepresentationHolder(Representation representation, Extractor extractor) { this.representation = representation; this.extractor = extractor; this.segmentIndex = representation.getIndex(); }
@Override public final void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs, long playbackPositionUs, ChunkOperationHolder out) { evaluation.queueSize = queue.size(); if (evaluation.format == null || !lastChunkWasInitialization) { evaluator.evaluate(queue, playbackPositionUs, formats, evaluation); } Format selectedFormat = evaluation.format; out.queueSize = evaluation.queueSize; if (selectedFormat == null) { out.chunk = null; return; } else if (out.queueSize == queue.size() && out.chunk != null && out.chunk.format.id.equals(selectedFormat.id)) { // We already have a chunk, and the evaluation hasn't changed either the format or the size // of the queue. Leave unchanged. return; } Representation selectedRepresentation = representations.get(selectedFormat.id); Extractor extractor = extractors.get(selectedRepresentation.format.id); RangedUri pendingInitializationUri = null; RangedUri pendingIndexUri = null; if (extractor.getFormat() == null) { pendingInitializationUri = selectedRepresentation.getInitializationUri(); } if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) { pendingIndexUri = selectedRepresentation.getIndexUri(); } if (pendingInitializationUri != null || pendingIndexUri != null) { // We have initialization and/or index requests to make. Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri, selectedRepresentation, extractor, dataSource, evaluation.trigger); lastChunkWasInitialization = true; out.chunk = initializationChunk; return; } int nextSegmentNum; DashSegmentIndex segmentIndex = segmentIndexes.get(selectedRepresentation.format.id); if (queue.isEmpty()) { nextSegmentNum = segmentIndex.getSegmentNum(seekPositionUs); } else { nextSegmentNum = queue.get(out.queueSize - 1).nextChunkIndex; } if (nextSegmentNum == -1) { out.chunk = null; return; } Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, segmentIndex, extractor, dataSource, nextSegmentNum, evaluation.trigger); lastChunkWasInitialization = false; out.chunk = nextMediaChunk; }
/** * @param dataSource A {@link DataSource} for loading the data. * @param dataSpec Defines the data to be loaded. * @param format The format of the stream to which this chunk belongs. * @param trigger The reason for this chunk being selected. * @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param extractor The extractor that will be used to extract the samples. * @param psshInfo Pssh data. May be null if pssh data is present within the stream, meaning it * can be obtained directly from {@code extractor}, or if no pssh data is required. * @param maybeSelfContained Set to true if this chunk might be self contained, meaning it might * contain a moov atom defining the media format of the chunk. This parameter can always be * safely set to true. Setting to false where the chunk is known to not be self contained may * improve startup latency. * @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor. */ public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, Extractor extractor, Map<UUID, byte[]> psshInfo, boolean maybeSelfContained, long sampleOffsetUs) { super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex); this.extractor = extractor; this.maybeSelfContained = maybeSelfContained; this.sampleOffsetUs = sampleOffsetUs; this.psshInfo = psshInfo; }
/** * @param dataSource A {@link DataSource} for loading the data. * @param dataSpec Defines the data to be loaded. * @param format The format of the stream to which this chunk belongs. * @param trigger The reason for this chunk being selected. * @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param extractor The extractor that will be used to extract the samples. * @param maybeSelfContained Set to true if this chunk might be self contained, meaning it might * contain a moov atom defining the media format of the chunk. This parameter can always be * safely set to true. Setting to false where the chunk is known to not be self contained may * improve startup latency. * @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor. */ public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) { super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex); this.extractor = extractor; this.maybeSelfContained = maybeSelfContained; this.sampleOffsetUs = sampleOffsetUs; }