private static String buildTrackName(MediaFormat format) { if (format.adaptive) { return "auto"; } String trackName; if (MimeTypes.isVideo(format.mimeType)) { trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)); } else if (MimeTypes.isAudio(format.mimeType)) { trackName = joinWithSeparator( joinWithSeparator(joinWithSeparator(buildLanguageString(format), buildAudioPropertyString(format)), buildBitrateString(format)), buildTrackIdString(format)); } else { trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format), buildBitrateString(format)), buildTrackIdString(format)); } return trackName.length() == 0 ? "unknown" : trackName; }
@Override protected boolean doPrepare(long positionUs) throws ExoPlaybackException { boolean sourcePrepared = mSource.prepare(positionUs); if (!sourcePrepared) { return false; } int trackCount = mSource.getTrackCount(); for (int i = 0; i < trackCount; ++i) { MediaFormat trackFormat = mSource.getFormat(i); if (handlesMimeType(trackFormat.mimeType)) { mTrackIndex = i; clearDecodeState(); return true; } } // TODO: Check this case. (Source do not have the proper mime type.) return true; }
@Override public void init(@NonNull List<String> ids, @NonNull List<MediaFormat> mediaFormats) throws IOException { mTrackCount = ids.size(); if (mTrackCount <= 0) { throw new IOException("No tracks to initialize"); } mIds = ids; mTrackSelected = new boolean[mTrackCount]; mReadSampleQueues = new ArrayList<>(); mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason, mBufferManager, mSamplePool, mIoCallback); for (int i = 0; i < mTrackCount; ++i) { mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); } mSampleChunkIoHelper.init(); }
/** * Creates {@link SampleChunk} I/O handler. * * @param ids track names * @param mediaFormats {@link android.media.MediaFormat} for each track * @param bufferReason reason to be buffered * @param bufferManager manager of {@link SampleChunk} collections * @param samplePool allocator for a sample * @param ioCallback listeners for I/O events */ public SampleChunkIoHelper(List<String> ids, List<MediaFormat> mediaFormats, @BufferReason int bufferReason, BufferManager bufferManager, SamplePool samplePool, IoCallback ioCallback) { mTrackCount = ids.size(); mIds = ids; mMediaFormats = mediaFormats; mBufferReason = bufferReason; mBufferManager = bufferManager; mSamplePool = samplePool; mIoCallback = ioCallback; mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; mWriteEndPositionUs = new long[mTrackCount]; mReadIoStates = new SampleChunk.IoState[mTrackCount]; mWriteIoStates = new SampleChunk.IoState[mTrackCount]; for (int i = 0; i < mTrackCount; ++i) { mWriteEndPositionUs[i] = RecordingSampleBuffer.CHUNK_DURATION_US; mReadIoStates[i] = new SampleChunk.IoState(); mWriteIoStates[i] = new SampleChunk.IoState(); } }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) { return RESULT_END_OF_INPUT; } if (seeker == null) { setupSeeker(input); extractorOutput.seekMap(seeker); MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType, MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), synchronizedHeader.channels, synchronizedHeader.sampleRate, null, null); if (gaplessInfo != null) { mediaFormat = mediaFormat.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding); } trackOutput.format(mediaFormat); } return readSample(input); }
public void testParsesValidMp4File() throws Exception { TestUtil.consumeTestData(extractor, getTestInputData(true /* includeStss */, false /* mp4vFormat */)); // The seek map is correct. assertSeekMap(extractorOutput.seekMap, true); // The video and audio formats are set correctly. assertEquals(2, extractorOutput.trackOutputs.size()); MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format; MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format; assertEquals(MimeTypes.VIDEO_H264, videoFormat.mimeType); assertEquals(VIDEO_WIDTH, videoFormat.width); assertEquals(VIDEO_HEIGHT, videoFormat.height); assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType); // The timestamps and sizes are set correctly. FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0); videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length); for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) { byte[] sampleData = getOutputSampleData(i, true); int sampleFlags = SAMPLE_IS_SYNC[i] ? C.SAMPLE_FLAG_SYNC : 0; long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]); videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null); } }
/** * Returns the AC-3 format given {@code data} containing the EC3SpecificBox according to * ETSI TS 102 366 Annex F. */ public static MediaFormat parseAnnexFEAc3Format(ParsableByteArray data) { data.skipBytes(2); // Skip data_rate and num_ind_sub. // Read only the first substream. // TODO: Read later substreams? // fscod (sample rate code) int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATES[fscod]; int nextByte = data.readUnsignedByte(); // Map acmod (audio coding mode) onto a channel count. int channelCount = CHANNEL_COUNTS[(nextByte & 0x0E) >> 1]; // lfeon (low frequency effects on) if ((nextByte & 0x01) != 0) { channelCount++; } return MediaFormat.createAudioFormat(MimeTypes.AUDIO_EC3, MediaFormat.NO_VALUE, channelCount, sampleRate, null); }
private static MediaFormat getTrackFormat(int adaptationSetType, Format format, String mediaMimeType, long durationUs) { switch (adaptationSetType) { case AdaptationSet.TYPE_VIDEO: return MediaFormat.createVideoFormat(format.id, mediaMimeType, format.bitrate, MediaFormat.NO_VALUE, durationUs, format.width, format.height, null); case AdaptationSet.TYPE_AUDIO: return MediaFormat.createAudioFormat(format.id, mediaMimeType, format.bitrate, MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, null, format.language); case AdaptationSet.TYPE_TEXT: return MediaFormat.createTextFormat(format.id, mediaMimeType, format.bitrate, durationUs, format.language); default: return null; } }
/** * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. * * @param data The EC3SpecificBox to parse. * @param trackId The track identifier to set on the format, or null. * @param durationUs The duration to set on the format, in microseconds. * @param language The language to set on the format. * @return The E-AC-3 format parsed from data in the header. */ public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, long durationUs, String language) { data.skipBytes(2); // data_rate, num_ind_sub // Read only the first substream. // TODO: Read later substreams? int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; int nextByte = data.readUnsignedByte(); int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; if ((nextByte & 0x01) != 0) { // lfeon channelCount++; } return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); }
@Override public void adaptiveTrack(SmoothStreamingManifest manifest, int element, int[] trackIndices) { if (adaptiveFormatEvaluator == null) { // Do nothing. return; } MediaFormat maxHeightMediaFormat = null; StreamElement streamElement = manifest.streamElements[element]; int maxWidth = -1; int maxHeight = -1; Format[] formats = new Format[trackIndices.length]; for (int i = 0; i < formats.length; i++) { int manifestTrackIndex = trackIndices[i]; formats[i] = streamElement.tracks[manifestTrackIndex].format; MediaFormat mediaFormat = initManifestTrack(manifest, element, manifestTrackIndex); if (maxHeightMediaFormat == null || mediaFormat.height > maxHeight) { maxHeightMediaFormat = mediaFormat; } maxWidth = Math.max(maxWidth, mediaFormat.width); maxHeight = Math.max(maxHeight, mediaFormat.height); } Arrays.sort(formats, new DecreasingBandwidthComparator()); MediaFormat adaptiveMediaFormat = maxHeightMediaFormat.copyAsAdaptive(null); tracks.add(new ExposedTrack(adaptiveMediaFormat, element, formats, maxWidth, maxHeight)); }
@Override protected void parsePayload(ParsableByteArray data, long timeUs) { int packetType = data.readUnsignedByte(); // Parse sequence header just in case it was not done before. if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { byte[] audioSpecifiConfig = new byte[data.bytesLeft()]; data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecifiConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), audioParams.second, audioParams.first, Collections.singletonList(audioSpecifiConfig), null); output.format(mediaFormat); hasOutputFormat = true; } else if (packetType == AAC_PACKET_TYPE_AAC_RAW) { // Sample audio AAC frames int bytesToWrite = data.bytesLeft(); output.sampleData(data, bytesToWrite); output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, bytesToWrite, 0, null); } }
private List<TvTrackInfo> getAllTracks() { String trackId; List<TvTrackInfo> tracks = new ArrayList<>(); int[] trackTypes = { DemoPlayer.TYPE_AUDIO, DemoPlayer.TYPE_VIDEO, DemoPlayer.TYPE_TEXT }; for (int trackType : trackTypes) { int count = mPlayer.getTrackCount(trackType); for (int i = 0; i < count; i++) { MediaFormat format = mPlayer.getTrackFormat(trackType, i); trackId = getTrackId(trackType, i); TvTrackInfo.Builder builder = new TvTrackInfo.Builder(trackType, trackId); if (trackType == DemoPlayer.TYPE_VIDEO) { builder.setVideoWidth(format.width); builder.setVideoHeight(format.height); } else if (trackType == DemoPlayer.TYPE_AUDIO) { builder.setAudioChannelCount(format.channelCount); builder.setAudioSampleRate(format.sampleRate); if (format.language != null) { builder.setLanguage(format.language); } } else if (trackType == DemoPlayer.TYPE_TEXT) { if (format.language != null) { builder.setLanguage(format.language); } } tracks.add(builder.build()); } } return tracks; }
@Override public void setQuality(View v) { PopupMenu popup = new PopupMenu(activity, v); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { player.setSelectedTrack(0, (item.getItemId() - 1)); return false; } }); ArrayList<Integer> formats = new ArrayList<>(); Menu menu = popup.getMenu(); menu.add(Menu.NONE, 0, 0, "Bitrate"); for (int i = 0; i < player.getTrackCount(0); i++) { MediaFormat format = player.getTrackFormat(0, i); if (MimeTypes.isVideo(format.mimeType)) { Log.e("dsa", format.bitrate + ""); if (format.adaptive) { menu.add(1, (i + 1), (i + 1), "Auto"); } else { if (!formats.contains(format.bitrate)) { menu.add(1, (i + 1), (i + 1), (format.bitrate) / 1000 + " kbps"); formats.add(format.bitrate); } } } } menu.setGroupCheckable(1, true, true); menu.findItem((player.getSelectedTrack(0) + 1)).setChecked(true); popup.show(); }
@Override public void onPlayerStateChanged(boolean playWhenReady, int state) { if (mListener == null) { return; } mListener.onStateChanged(playWhenReady, state); if (state == ExoPlayer.STATE_READY && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0 && playWhenReady) { MediaFormat format = mPlayer.getTrackFormat(TRACK_TYPE_VIDEO, 0); mListener.onVideoSizeChanged(format.width, format.height, format.pixelWidthHeightRatio); } }
@Override public boolean prepare() throws IOException { if(!mSampleExtractor.prepare()) { return false; } List<MediaFormat> formats = mSampleExtractor.getTrackFormats(); int trackCount = formats.size(); mTrackFormats.clear(); mReachedEos.clear(); for (int i = 0; i < trackCount; ++i) { mTrackFormats.add(formats.get(i)); mReachedEos.add(false); String mime = formats.get(i).mimeType; if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) { mVideoTrackIndex = i; if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) { mCcParser = new Mpeg2CcParser(); } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) { mCcParser = new H264CcParser(); } } } if (mVideoTrackIndex != -1) { mCea708TextTrackIndex = trackCount; } if (mCea708TextTrackIndex >= 0) { mTrackFormats.add(MediaFormat.createTextFormat(null, MIMETYPE_TEXT_CEA_708, 0, mTrackFormats.get(0).durationUs, "")); } return true; }
private int getParserIndex(MediaFormat mediaFormat) { for (int i = 0; i < subtitleParsers.length; i++) { if (subtitleParsers[i].canParse(mediaFormat.mimeType)) { return i; } } return -1; }
private boolean prepare() { if (mSampleSourceReader == null) { mSampleSourceReader = mSampleSource.register(); } if(!mSampleSourceReader.prepare(0)) { return false; } if (mTrackFormats == null) { int trackCount = mSampleSourceReader.getTrackCount(); mTrackMetEos = new boolean[trackCount]; List<MediaFormat> trackFormats = new ArrayList<>(); for (int i = 0; i < trackCount; i++) { trackFormats.add(mSampleSourceReader.getFormat(i)); mSampleSourceReader.enable(i, 0); } mTrackFormats = trackFormats; List<String> ids = new ArrayList<>(); for (int i = 0; i < mTrackFormats.size(); i++) { ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i)); } try { mSampleBuffer.init(ids, mTrackFormats); } catch (IOException e) { // In this case, we will not schedule any further operation. // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will // call release() eventually. mExceptionOnPrepare = e; return false; } } return true; }
/** * Finishes I/O operations and releases all the resources. * @throws IOException */ public void release() throws IOException { if (mIoHandler == null) { return; } // Finishes all I/O operations. ConditionVariable conditionVariable = new ConditionVariable(); mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_RELEASE, conditionVariable)); conditionVariable.block(); for (int i = 0; i < mTrackCount; ++i) { mBufferManager.unregisterChunkEvictedListener(mIds.get(i)); } try { if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) { // Saves meta information for recording. Pair<String, android.media.MediaFormat> audio = null, video = null; for (int i = 0; i < mTrackCount; ++i) { android.media.MediaFormat format = mMediaFormats.get(i).getFrameworkMediaFormatV16(); format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs); if (audio == null && MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { audio = new Pair<>(mIds.get(i), format); } else if (video == null && MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { video = new Pair<>(mIds.get(i), format); } if (audio != null && video != null) { break; } } mBufferManager.writeMetaFiles(audio, video); } } finally { mBufferManager.release(); mIoHandler.getLooper().quitSafely(); } }
/** * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. * * @param data The AC3SpecificBox to parse. * @param trackId The track identifier to set on the format, or null. * @param durationUs The duration to set on the format, in microseconds. * @param language The language to set on the format. * @return The AC-3 format parsed from data in the header. */ public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId, long durationUs, String language) { int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; int nextByte = data.readUnsignedByte(); int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3]; if ((nextByte & 0x04) != 0) { // lfeon channelCount++; } return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); }
@Override protected boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException { // TODO: Use MediaCodecList.findDecoderForFormat on API 23. String mimeType = mediaFormat.mimeType; return MimeTypes.isVideo(mimeType) && (MimeTypes.VIDEO_UNKNOWN.equals(mimeType) || MediaCodecUtil.getDecoderInfo(mimeType, false) != null); }
@Override protected void configureCodec(MediaCodec codec, String codecName, boolean codecIsAdaptive, android.media.MediaFormat format, MediaCrypto crypto) { maybeSetMaxInputSize(format, codecIsAdaptive); codec.configure(format, surface, crypto, 0); codec.setVideoScalingMode(videoScalingMode); }
@Override protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { super.onInputFormatChanged(holder); pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1 : holder.format.pixelWidthHeightRatio; pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0 : holder.format.rotationDegrees; }
@SuppressLint("InlinedApi") private void maybeSetMaxInputSize(android.media.MediaFormat format, boolean codecIsAdaptive) { if (!MimeTypes.VIDEO_H264.equals(format.getString(android.media.MediaFormat.KEY_MIME))) { // Only set a max input size for H264 for now. return; } if (format.containsKey(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)) { // Already set. The source of the format may know better, so do nothing. return; } if ("BRAVIA 4K 2015".equals(Util.MODEL)) { // The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video // maximum input size, so use the default value. return; } int maxHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT); if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) { maxHeight = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_HEIGHT)); } int maxWidth = format.getInteger(android.media.MediaFormat.KEY_WIDTH); if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_WIDTH)) { maxWidth = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_WIDTH)); } // H264 requires compression ratio of at least 2, and uses macroblocks. int maxInputSize = ((maxWidth + 15) / 16) * ((maxHeight + 15) / 16) * 192; format.setInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); }
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs, int adaptiveMaxWidth, int adaptiveMaxHeight) { if (format == null) { return null; } if (sampleOffsetUs != 0 && format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) { format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs); } if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) { format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight); } return format; }
/** * Parses the sample header. */ private void parseHeader() { adtsScratch.setPosition(0); if (!hasOutputFormat) { int audioObjectType = adtsScratch.readBits(2) + 1; int sampleRateIndex = adtsScratch.readBits(4); adtsScratch.skipBits(1); int channelConfig = adtsScratch.readBits(3); byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig( audioObjectType, sampleRateIndex, channelConfig); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig)); frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; output.format(mediaFormat); hasOutputFormat = true; } else { adtsScratch.skipBits(10); } adtsScratch.skipBits(4); sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE; if (hasCrc) { sampleSize -= CRC_SIZE; } }
public ExposedTrack(MediaFormat trackFormat, int elementIndex, Format fixedFormat) { this.trackFormat = trackFormat; this.elementIndex = elementIndex; this.fixedFormat = fixedFormat; this.adaptiveFormats = null; this.adaptiveMaxWidth = MediaFormat.NO_VALUE; this.adaptiveMaxHeight = MediaFormat.NO_VALUE; }
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs) { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdDataHolder holder = new StsdDataHolder(numberOfEntries); for (int i = 0; i < numberOfEntries; i++) { int childStartPosition = stsd.getPosition(); int childAtomSize = stsd.readInt(); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = stsd.readInt(); if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 || childAtomType == Atom.TYPE_s263) { parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca || childAtomType == Atom.TYPE_ac_3) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_TTML) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, durationUs); } else if (childAtomType == Atom.TYPE_tx3g) { holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, durationUs); } stsd.setPosition(childStartPosition + childAtomSize); } return holder; }
@Override public void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex, int adaptationSetIndex, int[] representationIndices) { if (adaptiveFormatEvaluator == null) { Log.w(TAG, "Skipping adaptive track (missing format evaluator)"); return; } AdaptationSet adaptationSet = manifest.getPeriod(periodIndex).adaptationSets.get( adaptationSetIndex); int maxWidth = 0; int maxHeight = 0; Format maxHeightRepresentationFormat = null; Format[] representationFormats = new Format[representationIndices.length]; for (int i = 0; i < representationFormats.length; i++) { Format format = adaptationSet.representations.get(representationIndices[i]).format; if (maxHeightRepresentationFormat == null || format.height > maxHeight) { maxHeightRepresentationFormat = format; } maxWidth = Math.max(maxWidth, format.width); maxHeight = Math.max(maxHeight, format.height); representationFormats[i] = format; } Arrays.sort(representationFormats, new DecreasingBandwidthComparator()); long trackDurationUs = live ? C.UNKNOWN_TIME_US : manifest.duration * 1000; String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat); if (mediaMimeType == null) { Log.w(TAG, "Skipped adaptive track (unknown media mime type)"); return; } MediaFormat trackFormat = getTrackFormat(adaptationSet.type, maxHeightRepresentationFormat, mediaMimeType, trackDurationUs); if (trackFormat == null) { Log.w(TAG, "Skipped adaptive track (unknown media format)"); return; } tracks.add(new ExposedTrack(trackFormat.copyAsAdaptive(null), adaptationSetIndex, representationFormats, maxWidth, maxHeight)); }
private void assertH264VideoFormat(int trackNumber, int timecodeScale) { MediaFormat format = getTrackOutput(trackNumber).format; assertEquals(Util.scaleLargeTimestamp(TEST_DURATION_TIMECODE, timecodeScale, 1000), format.durationUs); assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_H264, format.mimeType); }
/** * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to * ETSI TS 102 366 Annex F. */ public static MediaFormat parseAnnexFAc3Format(ParsableByteArray data) { // fscod (sample rate code) int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATES[fscod]; int nextByte = data.readUnsignedByte(); // Map acmod (audio coding mode) onto a channel count. int channelCount = CHANNEL_COUNTS[(nextByte & 0x38) >> 3]; // lfeon (low frequency effects on) if ((nextByte & 0x04) != 0) { channelCount++; } return MediaFormat.createAudioFormat(MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, channelCount, sampleRate, null); }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, int segmentNum, int trigger) { Representation representation = representationHolder.representation; DashSegmentIndex segmentIndex = representationHolder.segmentIndex; long startTimeUs = segmentIndex.getTimeUs(segmentNum); long endTimeUs = startTimeUs + segmentIndex.getDurationUs(segmentNum); int absoluteSegmentNum = segmentNum + representationHolder.segmentNumShift; boolean isLastSegment = !currentManifest.dynamic && segmentNum == segmentIndex.getLastSegmentNum(); RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum); DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); long sampleOffsetUs = representation.periodStartMs * 1000 - representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { if (representationHolder.vttHeaderOffsetUs != sampleOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(C.WEBVTT_EXO_HEADER).append("=") .append(C.WEBVTT_EXO_HEADER_OFFSET).append(sampleOffsetUs) .append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); representationHolder.vttHeaderOffsetUs = sampleOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper, representationHolder.format, drmInitData, true); } }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource, int chunkIndex, boolean isLast, long chunkStartTimeUs, long chunkEndTimeUs, int trigger, MediaFormat mediaFormat) { 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 ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, isLast, chunkStartTimeUs, extractorWrapper, mediaFormat, drmInitData, true); }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger, MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) { 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 ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat, adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID); }
public ExposedTrack(MediaFormat trackFormat, int elementIndex, Format[] adaptiveFormats, int adaptiveMaxWidth, int adaptiveMaxHeight) { this.trackFormat = trackFormat; this.elementIndex = elementIndex; this.adaptiveFormats = adaptiveFormats; this.adaptiveMaxWidth = adaptiveMaxWidth; this.adaptiveMaxHeight = adaptiveMaxHeight; this.fixedFormat = null; }
private TrackOutput buildTrackOutput(long subsampleOffsetUs) { TrackOutput trackOutput = output.track(0); trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, "en", subsampleOffsetUs)); output.endTracks(); return trackOutput; }
/** * Attempts to read the remaining two bytes of the frame header. * <p> * If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME}, * the media format is output if this has not previously occurred, the four header bytes are * output as sample data, and the position of the source is advanced to the byte that immediately * follows the header. * <p> * If a frame header is read in full but cannot be parsed then the state is changed to * {@link #STATE_READING_HEADER}. * <p> * If a frame header is not read in full then the position of the source is advanced to the limit, * and the method should be called again with the next source to continue the read. * * @param source The source from which to read. */ private void readHeaderRemainder(ParsableByteArray source) { int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead); source.readBytes(headerScratch.data, frameBytesRead, bytesToRead); frameBytesRead += bytesToRead; if (frameBytesRead < HEADER_SIZE) { // We haven't read the whole header yet. return; } headerScratch.setPosition(0); boolean parsedHeader = MpegAudioHeader.populateHeader(headerScratch.readInt(), header); if (!parsedHeader) { // We thought we'd located a frame header, but we hadn't. frameBytesRead = 0; state = STATE_READING_HEADER; return; } frameSize = header.frameSize; if (!hasOutputFormat) { frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType, MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, header.channels, header.sampleRate, null, null); output.format(mediaFormat); hasOutputFormat = true; } headerScratch.setPosition(0); output.sampleData(headerScratch, HEADER_SIZE); state = STATE_READING_FRAME; }
private void assertVp9VideoFormat(int trackNumber, int timecodeScale) { MediaFormat format = getTrackOutput(trackNumber).format; assertEquals(Util.scaleLargeTimestamp(TEST_DURATION_TIMECODE, timecodeScale, 1000), format.durationUs); assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_VP9, format.mimeType); }
public MediaFormat getTrackFormat(int type, int index) { return player.getTrackFormat(type, index); }