diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0d8bf3fdb5..ae006ebdde 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,38 +2,34 @@ ### dev-v2 (not yet released) +### 2.16.0 (2021-11-04) + * Core Library: + * Deprecate `SimpleExoPlayer`. All functionality has been moved to + `ExoPlayer` instead. `ExoPlayer.Builder` can be used instead of + `SimpleExoPlayer.Builder`. + * Add track selection methods to the `Player` interface, for example, + `Player.getCurrentTracksInfo` and `Player.setTrackSelectionParameters`. + These methods can be used instead of directly accessing the track + selector. * Enable MediaCodec asynchronous queueing by default on devices with API level >= 31. Add methods in `DefaultMediaCodecRendererFactory` and `DefaultRenderersFactory` to force enable or force disable asynchronous queueing ([6348](https://github.com/google/ExoPlayer/issues/6348)). - * Add 12 public method headers to `ExoPlayer` that exist in - `SimpleExoPlayer`, such that all public methods in `SimpleExoPlayer` are - overrides. + * Remove final dependency on `jcenter()`. + * Fix `mediaMetadata` being reset when media is repeated + ([#9458](https://github.com/google/ExoPlayer/issues/9458)). + * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values + input through the `MediaItem.MediaMetadata` are used above media derived + values. * Move `com.google.android.exoplayer2.device.DeviceInfo` to `com.google.android.exoplayer2.DeviceInfo`. * Move `com.google.android.exoplayer2.drm.DecryptionException` to `com.google.android.exoplayer2.decoder.CryptoException`. * Move `com.google.android.exoplayer2.upstream.cache.CachedRegionTracker` to `com.google.android.exoplayer2.upstream.CachedRegionTracker`. - * Remove `ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED`. Use - `GlUtil.glAssertionsEnabled` instead. * Move `Player.addListener(EventListener)` and `Player.removeListener(EventListener)` out of `Player` into subclasses. - * Fix `mediaMetadata` being reset when media is repeated - ([#9458](https://github.com/google/ExoPlayer/issues/9458)). - * Remove final dependency on `jcenter()`. - * Adjust `ExoPlayer` `MediaMetadata` update priority, such that values - input through the `MediaItem.MediaMetadata` are used above media derived - values. -* Video: - * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a - released `Surface` when playing without an app-provided `Surface` - ([#9476](https://github.com/google/ExoPlayer/issues/9476)). -* DRM: - * Log an error (instead of throwing `IllegalStateException`) when calling - `DefaultDrmSession#release()` on a fully released session - ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * Android 12 compatibility: * Keep `DownloadService` started and in the foreground whilst waiting for requirements to be met on Android 12. This is necessary due to new @@ -49,6 +45,14 @@ are not compatible with apps targeting Android 12, and will crash with an `IllegalArgumentException` when creating `PendingIntent`s ([#9181](https://github.com/google/ExoPlayer/issues/9181)). +* Video: + * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a + released `Surface` when playing without an app-provided `Surface` + ([#9476](https://github.com/google/ExoPlayer/issues/9476)). +* DRM: + * Log an error (instead of throwing `IllegalStateException`) when calling + `DefaultDrmSession#release()` on a fully released session + ([#9392](https://github.com/google/ExoPlayer/issues/9392)). * UI: * `SubtitleView` no longer implements `TextOutput`. `SubtitleView` implements `Player.Listener`, so can be registered to a player with @@ -64,6 +68,7 @@ * MP4: Avoid throwing `ArrayIndexOutOfBoundsException` when parsing invalid `colr` boxes produced by some device cameras ([#9332](https://github.com/google/ExoPlayer/issues/9332)). + * MP4: Parse HDR static metadata from the `clli` and `mdcv` boxes. * TS: Correctly handle HEVC tracks with pixel aspect ratios other than 1. * TS: Map stream type 0x80 to H262 ([#9472](https://github.com/google/ExoPlayer/issues/9472)). @@ -73,7 +78,7 @@ requirements for downloads to continue. In both cases, `DownloadService` will now remain started and in the foreground whilst waiting for requirements to be met. - * Modify `DownlaodService` behavior when running on Android 12 and above. + * Modify `DownloadService` behavior when running on Android 12 and above. See the "Android 12 compatibility" section above. * RTSP: * Support RFC4566 SDP attribute field grammar @@ -82,6 +87,12 @@ * Populate `Format.sampleMimeType`, `width` and `height` for image `AdaptationSet` elements ([#9500](https://github.com/google/ExoPlayer/issues/9500)). +* HLS: + * Fix rounding error in HLS playlists + ([#9575](https://github.com/google/ExoPlayer/issues/9575)). + * Fix `NoSuchElementException` thrown when an HLS manifest declares + `#EXT-X-RENDITION-REPORT` at the beginning of the playlist + ([#9592](https://github.com/google/ExoPlayer/issues/9592)). * RTMP extension: * Upgrade to `io.antmedia:rtmp_client`, which does not rely on `jcenter()` ([#9591](https://github.com/google/ExoPlayer/issues/9591)). @@ -89,6 +100,9 @@ * Rename `MediaSessionConnector.QueueNavigator#onCurrentWindowIndexChanged` to `onCurrentMediaItemIndexChanged`. +* Transformer: + * Avoid sending a duplicate timestamp to the encoder with the end of + stream buffer. * Remove deprecated symbols: * Remove `Renderer.VIDEO_SCALING_MODE_*` constants. Use identically named constants in `C` instead. diff --git a/constants.gradle b/constants.gradle index 400eba8c9f..bd4a545c4c 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.15.1' - releaseVersionCode = 2015001 + releaseVersion = '2.16.0' + releaseVersionCode = 2016000 minSdkVersion = 16 appTargetSdkVersion = 29 // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some diff --git a/docs/supported-formats.md b/docs/supported-formats.md index 8270866bcd..70b24416a8 100644 --- a/docs/supported-formats.md +++ b/docs/supported-formats.md @@ -92,7 +92,7 @@ FFmpeg decoder name. ## Standalone subtitle formats ## ExoPlayer supports standalone subtitle files in a variety of formats. Subtitle -files can be side-loaded as described on the [Media source page][]. +files can be side-loaded as described on the [media items page][]. | Container format | Supported | MIME type | |---------------------------|:------------:|:----------| @@ -101,7 +101,7 @@ files can be side-loaded as described on the [Media source page][]. | SubRip | YES | MimeTypes.APPLICATION_SUBRIP | | SubStationAlpha (SSA/ASS) | YES | MimeTypes.TEXT_SSA | -[Media source page]: {{ site.baseurl }}/media-sources.html#side-loading-a-subtitle-file +[media items page]: {{ site.baseurl }}/media-items.html#sideloading-subtitle-tracks ## HDR video playback ## diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index 13952abd72..9a40ba74de 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.upstream.DataSchemeDataSource; import com.google.android.exoplayer2.upstream.DataSourceUtil; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -134,6 +135,35 @@ import java.util.Set; } } + /** Stores configuration for DAI ad playback. */ + static final class DaiConfiguration { + + public final AdErrorEvent.AdErrorListener applicationAdErrorListener; + public final boolean debugModeEnabled; + + @Nullable public final List companionAdSlots; + @Nullable public final AdEvent.AdEventListener applicationAdEventListener; + @Nullable public final VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback; + @Nullable public final ImaSdkSettings imaSdkSettings; + + public DaiConfiguration( + AdErrorEvent.AdErrorListener applicationAdErrorListener, + @Nullable List companionAdSlots, + @Nullable AdEvent.AdEventListener applicationAdEventListener, + @Nullable VideoAdPlayer.VideoAdPlayerCallback applicationVideoAdPlayerCallback, + @Nullable ImaSdkSettings imaSdkSettings, + boolean debugModeEnabled) { + + this.applicationAdErrorListener = applicationAdErrorListener; + this.companionAdSlots = + companionAdSlots != null ? ImmutableList.copyOf(companionAdSlots) : null; + this.applicationAdEventListener = applicationAdEventListener; + this.applicationVideoAdPlayerCallback = applicationVideoAdPlayerCallback; + this.imaSdkSettings = imaSdkSettings; + this.debugModeEnabled = debugModeEnabled; + } + } + public static final int TIMEOUT_UNSET = -1; public static final int BITRATE_UNSET = -1; diff --git a/extensions/leanback/src/main/AndroidManifest.xml b/extensions/leanback/src/main/AndroidManifest.xml index e385551143..1649d3c386 100644 --- a/extensions/leanback/src/main/AndroidManifest.xml +++ b/extensions/leanback/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ limitations under the License. --> - + diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 9f13465861..4784575659 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -27,11 +27,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.15.1"; + public static final String VERSION = "2.16.0"; /** The version of the library expressed as {@code TAG + "/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.15.1"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.16.0"; /** * The version of the library expressed as an integer, for example 1002003. @@ -41,7 +41,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2015001; + public static final int VERSION_INT = 2016000; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 7fea1c5a43..e9d6c5829a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -891,7 +891,8 @@ public final class DownloadHelper { MediaItem mediaItem, DataSource.Factory dataSourceFactory, @Nullable DrmSessionManager drmSessionManager) { - return new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY) + return new DefaultMediaSourceFactory( + dataSourceFactory, ExtractorsFactory.EMPTY, /* serverSideDaiMediaSourceFactory= */ null) .setDrmSessionManager(drmSessionManager) .createMediaSource(mediaItem); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java index a98b8f4a1b..0cbae12170 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -119,6 +120,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { private final DataSource.Factory dataSourceFactory; private final DelegateFactoryLoader delegateFactoryLoader; + @Nullable private final MediaSourceFactory serverSideDaiMediaSourceFactory; @Nullable private AdsLoaderProvider adsLoaderProvider; @Nullable private AdViewProvider adViewProvider; @Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy; @@ -146,7 +148,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * its container. */ public DefaultMediaSourceFactory(Context context, ExtractorsFactory extractorsFactory) { - this(new DefaultDataSource.Factory(context), extractorsFactory); + this( + new DefaultDataSource.Factory(context), + extractorsFactory, + /* serverSideDaiMediaSourceFactory= */ null); } /** @@ -156,7 +161,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * for requesting media data. */ public DefaultMediaSourceFactory(DataSource.Factory dataSourceFactory) { - this(dataSourceFactory, new DefaultExtractorsFactory()); + this( + dataSourceFactory, + new DefaultExtractorsFactory(), + /* serverSideDaiMediaSourceFactory= */ null); } /** @@ -166,10 +174,17 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * for requesting media data. * @param extractorsFactory An {@link ExtractorsFactory} used to extract progressive media from * its container. + * @param serverSideDaiMediaSourceFactory A {@link MediaSourceFactory} for creating server side + * inserted ad media sources. */ public DefaultMediaSourceFactory( - DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { + DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, + @Nullable MediaSourceFactory serverSideDaiMediaSourceFactory) { this.dataSourceFactory = dataSourceFactory; + // Temporary until factory registration is agreed upon. + this.serverSideDaiMediaSourceFactory = serverSideDaiMediaSourceFactory; + delegateFactoryLoader = new DelegateFactoryLoader(dataSourceFactory, extractorsFactory); liveTargetOffsetMs = C.TIME_UNSET; liveMinOffsetMs = C.TIME_UNSET; @@ -333,7 +348,11 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { @Override public MediaSource createMediaSource(MediaItem mediaItem) { - checkNotNull(mediaItem.localConfiguration); + Assertions.checkNotNull(mediaItem.localConfiguration); + @Nullable String scheme = mediaItem.localConfiguration.uri.getScheme(); + if (scheme != null && scheme.equals("imadai")) { + return checkNotNull(serverSideDaiMediaSourceFactory).createMediaSource(mediaItem); + } @C.ContentType int type = Util.inferContentTypeForUriAndMimeType( diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index bc8633acc8..9c5de24b70 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -410,6 +410,12 @@ import java.util.List; @SuppressWarnings("ConstantCaseForConstants") public static final int TYPE_twos = 0x74776f73; + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_clli = 0x636c6c69; + + @SuppressWarnings("ConstantCaseForConstants") + public static final int TYPE_mdcv = 0x6d646376; + public final int type; public Atom(int type) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index bc5fa10fe3..442758716a 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -45,6 +45,8 @@ import com.google.android.exoplayer2.video.DolbyVisionConfig; import com.google.android.exoplayer2.video.HevcConfig; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -1061,6 +1063,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .build(); } + // hdrStaticInfo is allocated using allocate() in allocateHdrStaticInfo(). + @SuppressWarnings("ByteBufferBackingArray") private static void parseVideoSampleEntry( ParsableByteArray parent, int atomType, @@ -1112,7 +1116,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable String codecs = null; @Nullable byte[] projectionData = null; @C.StereoMode int stereoMode = Format.NO_VALUE; - @Nullable ColorInfo colorInfo = null; + + // HDR related metadata. + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; + // The format of HDR static info is defined in CTA-861-G:2017, Table 45. + @Nullable ByteBuffer hdrStaticInfo = null; + while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); @@ -1157,6 +1168,43 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } else if (childAtomType == Atom.TYPE_av1C) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_AV1; + } else if (childAtomType == Atom.TYPE_clli) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the clli box occupy the last 4 bytes of the HDR static info array. Note + // that each field is read in big endian and written in little endian. + hdrStaticInfo.position(21); + hdrStaticInfo.putShort(parent.readShort()); // max_content_light_level. + hdrStaticInfo.putShort(parent.readShort()); // max_pic_average_light_level. + } else if (childAtomType == Atom.TYPE_mdcv) { + if (hdrStaticInfo == null) { + hdrStaticInfo = allocateHdrStaticInfo(); + } + // The contents of the mdcv box occupy 20 bytes after the first byte of the HDR static info + // array. Note that each field is read in big endian and written in little endian. + short displayPrimariesGX = parent.readShort(); + short displayPrimariesGY = parent.readShort(); + short displayPrimariesBX = parent.readShort(); + short displayPrimariesBY = parent.readShort(); + short displayPrimariesRX = parent.readShort(); + short displayPrimariesRY = parent.readShort(); + short whitePointX = parent.readShort(); + short whitePointY = parent.readShort(); + long maxDisplayMasteringLuminance = parent.readUnsignedInt(); + long minDisplayMasteringLuminance = parent.readUnsignedInt(); + + hdrStaticInfo.position(1); + hdrStaticInfo.putShort(displayPrimariesRX); + hdrStaticInfo.putShort(displayPrimariesRY); + hdrStaticInfo.putShort(displayPrimariesGX); + hdrStaticInfo.putShort(displayPrimariesGY); + hdrStaticInfo.putShort(displayPrimariesBX); + hdrStaticInfo.putShort(displayPrimariesBY); + hdrStaticInfo.putShort(whitePointX); + hdrStaticInfo.putShort(whitePointY); + hdrStaticInfo.putShort((short) (maxDisplayMasteringLuminance / 10000)); + hdrStaticInfo.putShort((short) (minDisplayMasteringLuminance / 10000)); } else if (childAtomType == Atom.TYPE_d263) { ExtractorUtil.checkContainerInput(mimeType == null, /* message= */ null); mimeType = MimeTypes.VIDEO_H263; @@ -1211,12 +1259,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // size=18): https://github.com/google/ExoPlayer/issues/9332 boolean fullRangeFlag = childAtomSize == 19 && (parent.readUnsignedByte() & 0b10000000) != 0; - colorInfo = - new ColorInfo( - ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries), - fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED, - ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics), - /* hdrStaticInfo= */ null); + colorSpace = ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries); + colorRange = fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; + colorTransfer = + ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics); } else { Log.w(TAG, "Unsupported color type: " + Atom.getAtomTypeString(colorType)); } @@ -1229,7 +1275,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return; } - out.format = + Format.Builder formatBuilder = new Format.Builder() .setId(trackId) .setSampleMimeType(mimeType) @@ -1241,9 +1287,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; .setProjectionData(projectionData) .setStereoMode(stereoMode) .setInitializationData(initializationData) - .setDrmInitData(drmInitData) - .setColorInfo(colorInfo) - .build(); + .setDrmInitData(drmInitData); + if (colorSpace != Format.NO_VALUE + || colorRange != Format.NO_VALUE + || colorTransfer != Format.NO_VALUE + || hdrStaticInfo != null) { + // Note that if either mdcv or clli are missing, we leave the corresponding HDR static + // metadata bytes with value zero. See [Internal ref: b/194535665]. + formatBuilder.setColorInfo( + new ColorInfo( + colorSpace, + colorRange, + colorTransfer, + hdrStaticInfo != null ? hdrStaticInfo.array() : null)); + } + out.format = formatBuilder.build(); + } + + private static ByteBuffer allocateHdrStaticInfo() { + // For HDR static info, Android decoders expect a 25-byte array. The first byte is zero to + // represent Static Metadata Type 1, as per CTA-861-G:2017, Table 44. The following 24 bytes + // follow CTA-861-G:2017, Table 45. + return ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN); } private static void parseMetaDataSampleEntry( diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index 97d98b5fcc..6d3fc254b1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -61,12 +61,18 @@ public final class WavExtractor implements Extractor { @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_USE}) - @IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA}) + @IntDef({ + STATE_READING_FILE_TYPE, + STATE_READING_FORMAT, + STATE_SKIPPING_TO_SAMPLE_DATA, + STATE_READING_SAMPLE_DATA + }) private @interface State {} - private static final int STATE_READING_HEADER = 0; - private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1; - private static final int STATE_READING_SAMPLE_DATA = 2; + private static final int STATE_READING_FILE_TYPE = 0; + private static final int STATE_READING_FORMAT = 1; + private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 2; + private static final int STATE_READING_SAMPLE_DATA = 3; private @MonotonicNonNull ExtractorOutput extractorOutput; private @MonotonicNonNull TrackOutput trackOutput; @@ -76,14 +82,14 @@ public final class WavExtractor implements Extractor { private long dataEndPosition; public WavExtractor() { - state = STATE_READING_HEADER; + state = STATE_READING_FILE_TYPE; dataStartPosition = C.POSITION_UNSET; dataEndPosition = C.POSITION_UNSET; } @Override public boolean sniff(ExtractorInput input) throws IOException { - return WavHeaderReader.peek(input) != null; + return WavHeaderReader.checkFileType(input); } @Override @@ -95,7 +101,7 @@ public final class WavExtractor implements Extractor { @Override public void seek(long position, long timeUs) { - state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA; + state = position == 0 ? STATE_READING_FILE_TYPE : STATE_READING_SAMPLE_DATA; if (outputWriter != null) { outputWriter.reset(timeUs); } @@ -111,8 +117,11 @@ public final class WavExtractor implements Extractor { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { assertInitialized(); switch (state) { - case STATE_READING_HEADER: - readHeader(input); + case STATE_READING_FILE_TYPE: + readFileType(input); + return Extractor.RESULT_CONTINUE; + case STATE_READING_FORMAT: + readFormat(input); return Extractor.RESULT_CONTINUE; case STATE_SKIPPING_TO_SAMPLE_DATA: skipToSampleData(input); @@ -130,50 +139,54 @@ public final class WavExtractor implements Extractor { Util.castNonNull(extractorOutput); } - @RequiresNonNull({"extractorOutput", "trackOutput"}) - private void readHeader(ExtractorInput input) throws IOException { + private void readFileType(ExtractorInput input) throws IOException { Assertions.checkState(input.getPosition() == 0); if (dataStartPosition != C.POSITION_UNSET) { input.skipFully(dataStartPosition); state = STATE_READING_SAMPLE_DATA; return; } - WavHeader header = WavHeaderReader.peek(input); - if (header == null) { + if (!WavHeaderReader.checkFileType(input)) { // Should only happen if the media wasn't sniffed. throw ParserException.createForMalformedContainer( - "Unsupported or unrecognized wav header.", /* cause= */ null); + "Unsupported or unrecognized wav file type.", /* cause= */ null); } input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + state = STATE_READING_FORMAT; + } - if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { - outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); - } else if (header.formatType == WavUtil.TYPE_ALAW) { + @RequiresNonNull({"extractorOutput", "trackOutput"}) + private void readFormat(ExtractorInput input) throws IOException { + WavFormat wavFormat = WavHeaderReader.readFormat(input); + if (wavFormat.formatType == WavUtil.TYPE_IMA_ADPCM) { + outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, wavFormat); + } else if (wavFormat.formatType == WavUtil.TYPE_ALAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_ALAW, /* pcmEncoding= */ Format.NO_VALUE); - } else if (header.formatType == WavUtil.TYPE_MLAW) { + } else if (wavFormat.formatType == WavUtil.TYPE_MLAW) { outputWriter = new PassthroughOutputWriter( extractorOutput, trackOutput, - header, + wavFormat, MimeTypes.AUDIO_MLAW, /* pcmEncoding= */ Format.NO_VALUE); } else { @C.PcmEncoding - int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); + int pcmEncoding = + WavUtil.getPcmEncodingForType(wavFormat.formatType, wavFormat.bitsPerSample); if (pcmEncoding == C.ENCODING_INVALID) { throw ParserException.createForUnsupportedContainerFeature( - "Unsupported WAV format type: " + header.formatType); + "Unsupported WAV format type: " + wavFormat.formatType); } outputWriter = new PassthroughOutputWriter( - extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); + extractorOutput, trackOutput, wavFormat, MimeTypes.AUDIO_RAW, pcmEncoding); } state = STATE_SKIPPING_TO_SAMPLE_DATA; } @@ -234,7 +247,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; private final Format format; /** The target size of each output sample, in bytes. */ private final int targetSampleSizeBytes; @@ -256,33 +269,33 @@ public final class WavExtractor implements Extractor { public PassthroughOutputWriter( ExtractorOutput extractorOutput, TrackOutput trackOutput, - WavHeader header, + WavFormat wavFormat, String mimeType, @C.PcmEncoding int pcmEncoding) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; + this.wavFormat = wavFormat; - int bytesPerFrame = header.numChannels * header.bitsPerSample / 8; - // Validate the header. Blocks are expected to correspond to single frames. - if (header.blockSize != bytesPerFrame) { + int bytesPerFrame = wavFormat.numChannels * wavFormat.bitsPerSample / 8; + // Validate the WAV format. Blocks are expected to correspond to single frames. + if (wavFormat.blockSize != bytesPerFrame) { throw ParserException.createForMalformedContainer( - "Expected block size: " + bytesPerFrame + "; got: " + header.blockSize, + "Expected block size: " + bytesPerFrame + "; got: " + wavFormat.blockSize, /* cause= */ null); } - int constantBitrate = header.frameRateHz * bytesPerFrame * 8; + int constantBitrate = wavFormat.frameRateHz * bytesPerFrame * 8; targetSampleSizeBytes = - max(bytesPerFrame, header.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); + max(bytesPerFrame, wavFormat.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND); format = new Format.Builder() .setSampleMimeType(mimeType) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(targetSampleSizeBytes) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(pcmEncoding) .build(); } @@ -297,7 +310,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -318,13 +331,13 @@ public final class WavExtractor implements Extractor { // Write the corresponding sample metadata. Samples must be a whole number of frames. It's // possible that the number of pending output bytes is not a whole number of frames if the // stream ended unexpectedly. - int bytesPerFrame = header.blockSize; + int bytesPerFrame = wavFormat.blockSize; int pendingFrames = pendingOutputBytes / bytesPerFrame; if (pendingFrames > 0) { long timeUs = startTimeUs + Util.scaleLargeTimestamp( - outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = pendingFrames * bytesPerFrame; int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -354,7 +367,7 @@ public final class WavExtractor implements Extractor { private final ExtractorOutput extractorOutput; private final TrackOutput trackOutput; - private final WavHeader header; + private final WavFormat wavFormat; /** Number of frames per block of the input (yet to be decoded) data. */ private final int framesPerBlock; @@ -384,23 +397,26 @@ public final class WavExtractor implements Extractor { private long outputFrameCount; public ImaAdPcmOutputWriter( - ExtractorOutput extractorOutput, TrackOutput trackOutput, WavHeader header) + ExtractorOutput extractorOutput, TrackOutput trackOutput, WavFormat wavFormat) throws ParserException { this.extractorOutput = extractorOutput; this.trackOutput = trackOutput; - this.header = header; - targetSampleSizeFrames = max(1, header.frameRateHz / TARGET_SAMPLES_PER_SECOND); + this.wavFormat = wavFormat; + targetSampleSizeFrames = max(1, wavFormat.frameRateHz / TARGET_SAMPLES_PER_SECOND); - ParsableByteArray scratch = new ParsableByteArray(header.extraData); + ParsableByteArray scratch = new ParsableByteArray(wavFormat.extraData); scratch.readLittleEndianUnsignedShort(); framesPerBlock = scratch.readLittleEndianUnsignedShort(); - int numChannels = header.numChannels; - // Validate the header. This calculation is defined in "Microsoft Multimedia Standards Update + int numChannels = wavFormat.numChannels; + // Validate the WAV format. This calculation is defined in "Microsoft Multimedia Standards + // Update // - New Multimedia Types and Data Techniques" (1994). See the "IMA ADPCM Wave Type" and "DVI // ADPCM Wave Type" sections, and the calculation of wSamplesPerBlock in the latter. int expectedFramesPerBlock = - (((header.blockSize - (4 * numChannels)) * 8) / (header.bitsPerSample * numChannels)) + 1; + (((wavFormat.blockSize - (4 * numChannels)) * 8) + / (wavFormat.bitsPerSample * numChannels)) + + 1; if (framesPerBlock != expectedFramesPerBlock) { throw ParserException.createForMalformedContainer( "Expected frames per block: " + expectedFramesPerBlock + "; got: " + framesPerBlock, @@ -410,22 +426,22 @@ public final class WavExtractor implements Extractor { // Calculate the number of blocks we'll need to decode to obtain an output sample of the // target sample size, and allocate suitably sized buffers for input and decoded data. int maxBlocksToDecode = Util.ceilDivide(targetSampleSizeFrames, framesPerBlock); - inputData = new byte[maxBlocksToDecode * header.blockSize]; + inputData = new byte[maxBlocksToDecode * wavFormat.blockSize]; decodedData = new ParsableByteArray( maxBlocksToDecode * numOutputFramesToBytes(framesPerBlock, numChannels)); // Create the format. We calculate the bitrate of the data before decoding, since this is the // bitrate of the stream itself. - int constantBitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock; + int constantBitrate = wavFormat.frameRateHz * wavFormat.blockSize * 8 / framesPerBlock; format = new Format.Builder() .setSampleMimeType(MimeTypes.AUDIO_RAW) .setAverageBitrate(constantBitrate) .setPeakBitrate(constantBitrate) .setMaxInputSize(numOutputFramesToBytes(targetSampleSizeFrames, numChannels)) - .setChannelCount(header.numChannels) - .setSampleRate(header.frameRateHz) + .setChannelCount(wavFormat.numChannels) + .setSampleRate(wavFormat.frameRateHz) .setPcmEncoding(C.ENCODING_PCM_16BIT) .build(); } @@ -441,7 +457,7 @@ public final class WavExtractor implements Extractor { @Override public void init(int dataStartPosition, long dataEndPosition) { extractorOutput.seekMap( - new WavSeekMap(header, framesPerBlock, dataStartPosition, dataEndPosition)); + new WavSeekMap(wavFormat, framesPerBlock, dataStartPosition, dataEndPosition)); trackOutput.format(format); } @@ -453,7 +469,7 @@ public final class WavExtractor implements Extractor { targetSampleSizeFrames - numOutputBytesToFrames(pendingOutputBytes); // Calculate the whole number of blocks that we need to decode to obtain this many frames. int blocksToDecode = Util.ceilDivide(targetFramesRemaining, framesPerBlock); - int targetReadBytes = blocksToDecode * header.blockSize; + int targetReadBytes = blocksToDecode * wavFormat.blockSize; // Read input data until we've reached the target number of blocks, or the end of the data. boolean endOfSampleData = bytesLeft == 0; @@ -467,11 +483,11 @@ public final class WavExtractor implements Extractor { } } - int pendingBlockCount = pendingInputBytes / header.blockSize; + int pendingBlockCount = pendingInputBytes / wavFormat.blockSize; if (pendingBlockCount > 0) { // We have at least one whole block to decode. decode(inputData, pendingBlockCount, decodedData); - pendingInputBytes -= pendingBlockCount * header.blockSize; + pendingInputBytes -= pendingBlockCount * wavFormat.blockSize; // Write all of the decoded data to the track output. int decodedDataSize = decodedData.limit(); @@ -499,7 +515,8 @@ public final class WavExtractor implements Extractor { private void writeSampleMetadata(int sampleFrames) { long timeUs = startTimeUs - + Util.scaleLargeTimestamp(outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz); + + Util.scaleLargeTimestamp( + outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz); int size = numOutputFramesToBytes(sampleFrames); int offset = pendingOutputBytes - size; trackOutput.sampleMetadata( @@ -517,7 +534,7 @@ public final class WavExtractor implements Extractor { */ private void decode(byte[] input, int blockCount, ParsableByteArray output) { for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { - for (int channelIndex = 0; channelIndex < header.numChannels; channelIndex++) { + for (int channelIndex = 0; channelIndex < wavFormat.numChannels; channelIndex++) { decodeBlockForChannel(input, blockIndex, channelIndex, output.getData()); } } @@ -528,8 +545,8 @@ public final class WavExtractor implements Extractor { private void decodeBlockForChannel( byte[] input, int blockIndex, int channelIndex, byte[] output) { - int blockSize = header.blockSize; - int numChannels = header.numChannels; + int blockSize = wavFormat.blockSize; + int numChannels = wavFormat.numChannels; // The input data consists for a four byte header [Ci] for each of the N channels, followed // by interleaved data segments [Ci-DATAj], each of which are four bytes long. @@ -590,11 +607,11 @@ public final class WavExtractor implements Extractor { } private int numOutputBytesToFrames(int bytes) { - return bytes / (2 * header.numChannels); + return bytes / (2 * wavFormat.numChannels); } private int numOutputFramesToBytes(int frames) { - return numOutputFramesToBytes(frames, header.numChannels); + return numOutputFramesToBytes(frames, wavFormat.numChannels); } private static int numOutputFramesToBytes(int frames, int numChannels) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java similarity index 91% rename from library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java index ca34e32cc0..ca9e1d8dd7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavFormat.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.extractor.wav; -/** Header for a WAV file. */ -/* package */ final class WavHeader { +/** Format information for a WAV file. */ +/* package */ final class WavFormat { /** * The format type. Standard format types are the "WAVE form Registration Number" constants @@ -33,10 +33,10 @@ package com.google.android.exoplayer2.extractor.wav; public final int blockSize; /** Bits per sample for a single channel. */ public final int bitsPerSample; - /** Extra data appended to the format chunk of the header. */ + /** Extra data appended to the format chunk. */ public final byte[] extraData; - public WavHeader( + public WavFormat( int formatType, int numChannels, int frameRateHz, diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index 147fba9c53..4541a305d6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.extractor.wav; import android.util.Pair; -import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.WavUtil; @@ -27,45 +26,56 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; -/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ +/** Reads a WAV header from an input stream; supports resuming from input failures. */ /* package */ final class WavHeaderReader { private static final String TAG = "WavHeaderReader"; /** - * Peeks and returns a {@code WavHeader}. + * Returns whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * - * @param input Input stream to peek the WAV header from. - * @throws ParserException If the input file is an incorrect RIFF WAV. + * @param input The input stream to peek from. The position should point to the start of the + * stream. + * @return Whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE + * tag. * @throws IOException If peeking from the input fails. - * @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a - * supported WAV format. */ - @Nullable - public static WavHeader peek(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - - // Allocate a scratch buffer large enough to store the format chunk. - ParsableByteArray scratch = new ParsableByteArray(16); - + public static boolean checkFileType(ExtractorInput input) throws IOException { + ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES); // Attempt to read the RIFF chunk. ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); if (chunkHeader.id != WavUtil.RIFF_FOURCC) { - return null; + return false; } input.peekFully(scratch.getData(), 0, 4); scratch.setPosition(0); - int riffFormat = scratch.readInt(); - if (riffFormat != WavUtil.WAVE_FOURCC) { - Log.e(TAG, "Unsupported RIFF format: " + riffFormat); - return null; + int formType = scratch.readInt(); + if (formType != WavUtil.WAVE_FOURCC) { + Log.e(TAG, "Unsupported form type: " + formType); + return false; } + return true; + } + + /** + * Reads and returns a {@code WavFormat}. + * + * @param input Input stream to read the WAV format from. The position should point to the byte + * following the WAVE tag. + * @throws IOException If reading from the input fails. + * @return A new {@code WavFormat} read from {@code input}. + */ + public static WavFormat readFormat(ExtractorInput input) throws IOException { + // Allocate a scratch buffer large enough to store the format chunk. + ParsableByteArray scratch = new ParsableByteArray(16); + // Skip chunks until we find the format chunk. - chunkHeader = ChunkHeader.peek(input, scratch); + ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch); while (chunkHeader.id != WavUtil.FMT_FOURCC) { - input.advancePeekPosition((int) chunkHeader.size); + input.skipFully(ChunkHeader.SIZE_IN_BYTES + (int) chunkHeader.size); chunkHeader = ChunkHeader.peek(input, scratch); } @@ -88,7 +98,8 @@ import java.io.IOException; extraData = Util.EMPTY_BYTE_ARRAY; } - return new WavHeader( + input.skipFully((int) (input.getPeekPosition() - input.getPosition())); + return new WavFormat( audioFormatType, numChannels, frameRateHz, @@ -109,8 +120,6 @@ import java.io.IOException; * @throws IOException If reading from the input fails. */ public static Pair skipToSampleData(ExtractorInput input) throws IOException { - Assertions.checkNotNull(input); - // Make sure the peek position is set to the read position before we peek the first header. input.resetPeekPosition(); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java index 2a92c38431..1d5c8fdae1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavSeekMap.java @@ -22,18 +22,18 @@ import com.google.android.exoplayer2.util.Util; /* package */ final class WavSeekMap implements SeekMap { - private final WavHeader wavHeader; + private final WavFormat wavFormat; private final int framesPerBlock; private final long firstBlockPosition; private final long blockCount; private final long durationUs; public WavSeekMap( - WavHeader wavHeader, int framesPerBlock, long dataStartPosition, long dataEndPosition) { - this.wavHeader = wavHeader; + WavFormat wavFormat, int framesPerBlock, long dataStartPosition, long dataEndPosition) { + this.wavFormat = wavFormat; this.framesPerBlock = framesPerBlock; this.firstBlockPosition = dataStartPosition; - this.blockCount = (dataEndPosition - dataStartPosition) / wavHeader.blockSize; + this.blockCount = (dataEndPosition - dataStartPosition) / wavFormat.blockSize; durationUs = blockIndexToTimeUs(blockCount); } @@ -50,17 +50,17 @@ import com.google.android.exoplayer2.util.Util; @Override public SeekPoints getSeekPoints(long timeUs) { // Calculate the containing block index, constraining to valid indices. - long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); + long blockIndex = (timeUs * wavFormat.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock); blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1); - long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize); + long seekPosition = firstBlockPosition + (blockIndex * wavFormat.blockSize); long seekTimeUs = blockIndexToTimeUs(blockIndex); SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition); if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) { return new SeekPoints(seekPoint); } else { long secondBlockIndex = blockIndex + 1; - long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize); + long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavFormat.blockSize); long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex); SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition); return new SeekPoints(seekPoint, secondSeekPoint); @@ -69,6 +69,6 @@ import com.google.android.exoplayer2.util.Util; private long blockIndexToTimeUs(long blockIndex) { return Util.scaleLargeTimestamp( - blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavHeader.frameRateHz); + blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavFormat.frameRateHz); } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 911f3f477e..88aba133e3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -119,4 +119,10 @@ public final class Mp4ExtractorTest { ExtractorAsserts.assertBehavior( Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); } + + @Test + public void mp4SampleWithColrMdcvAndClli() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_colr_mdcv_and_clli.mp4", simulationConfig); + } } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java index 526f508953..a877d72b13 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannel.java @@ -39,6 +39,9 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.Socket; import java.nio.charset.Charset; import java.util.ArrayList; @@ -334,6 +337,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Processes RTSP messages line-by-line. */ private static final class MessageParser { + @Documented + @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_READING_FIRST_LINE, STATE_READING_HEADER, STATE_READING_BODY}) @interface ReadingState {} diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java index 8da9dcd2e1..7efa0eb780 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerAudioRenderer.java @@ -279,8 +279,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; int result = readSource(getFormatHolder(), decoderInputBuffer, /* readFlags= */ 0); switch (result) { case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; decoderInputBuffer.flip(); decoder.queueInputBuffer(decoderInputBuffer); return !decoderInputBuffer.isEndOfStream(); @@ -316,6 +316,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private void queueEndOfStreamToEncoder(MediaCodecAdapterWrapper encoder) { checkState(checkNotNull(encoderInputBuffer.data).position() == 0); + encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs; encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); encoderInputBuffer.flip(); // Queuing EOS should only occur with an empty buffer. diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java index 6e19f0b9f9..d3fe72d65b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerBaseRenderer.java @@ -48,8 +48,7 @@ import com.google.android.exoplayer2.util.MimeTypes; } @Override - protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) - throws ExoPlaybackException { + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { this.streamOffsetUs = offsetUs; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java index d14378754e..4692a6ca81 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerMuxingVideoRenderer.java @@ -117,8 +117,8 @@ import java.nio.ByteBuffer; muxerWrapper.endTrack(getTrackType()); return false; } - buffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), buffer.timeUs); + buffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(buffer.data); data.flip(); if (sampleTransformer != null) { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java index 931e985a5d..f4836e49df 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerTranscodingVideoRenderer.java @@ -320,8 +320,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; case C.RESULT_FORMAT_READ: throw new IllegalStateException("Format changes are not supported."); case C.RESULT_BUFFER_READ: - decoderInputBuffer.timeUs -= streamOffsetUs; mediaClock.updateTimeForTrackType(getTrackType(), decoderInputBuffer.timeUs); + decoderInputBuffer.timeUs -= streamOffsetUs; ByteBuffer data = checkNotNull(decoderInputBuffer.data); data.flip(); decoder.queueInputBuffer(decoderInputBuffer); diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.0.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump new file mode 100644 index 0000000000..1e1023afb0 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.1.dump @@ -0,0 +1,398 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 11156 + sample count = 30 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 1: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 2: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 3: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 4: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 5: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 6: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 7: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 8: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 9: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 10: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 11: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 12: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 13: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 14: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 15: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 16: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 17: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 18: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 19: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 20: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 21: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 22: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 23: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 24: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 25: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 26: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 27: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 28: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 29: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump new file mode 100644 index 0000000000..5b51396c83 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.2.dump @@ -0,0 +1,338 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 5567 + sample count = 15 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 1: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 2: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 3: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 4: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 5: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 6: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 7: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 8: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 9: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 10: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 11: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 12: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 13: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 14: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump new file mode 100644 index 0000000000..d66f9234a1 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.3.dump @@ -0,0 +1,282 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 374 + sample count = 1 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump new file mode 100644 index 0000000000..8ef4f19b16 --- /dev/null +++ b/testdata/src/test/assets/extractordumps/mp4/sample_with_colr_mdcv_and_clli.mp4.unknown_length.dump @@ -0,0 +1,454 @@ +seekMap: + isSeekable = true + duration = 1022000 + getPosition(0) = [[timeUs=0, position=48]] + getPosition(1) = [[timeUs=0, position=48]] + getPosition(511000) = [[timeUs=0, position=48]] + getPosition(1022000) = [[timeUs=0, position=48]] +numberOfTracks = 2 +track 0: + total output bytes = 266091 + sample count = 60 + format 0: + id = 1 + sampleMimeType = video/av01 + maxInputSize = 144656 + width = 1920 + height = 1080 + frameRate = 59.940056 + colorInfo: + colorSpace = 6 + colorRange = 2 + colorTransfer = 6 + hdrStaticInfo = length 25, hash 423AFC35 + sample 0: + time = 0 + flags = 1 + data = length 144626, hash 7C021D5F + sample 1: + time = 16683 + flags = 0 + data = length 4018, hash FA5E79FA + sample 2: + time = 33366 + flags = 0 + data = length 3, hash D5E0 + sample 3: + time = 50050 + flags = 0 + data = length 144, hash 4A868A2F + sample 4: + time = 66733 + flags = 0 + data = length 3, hash D5D0 + sample 5: + time = 83416 + flags = 0 + data = length 342, hash 5A2E1C3C + sample 6: + time = 100100 + flags = 0 + data = length 3, hash D610 + sample 7: + time = 116783 + flags = 0 + data = length 173, hash CFE014B3 + sample 8: + time = 133466 + flags = 0 + data = length 3, hash D5C0 + sample 9: + time = 150150 + flags = 0 + data = length 655, hash 3A7738B6 + sample 10: + time = 166833 + flags = 0 + data = length 3, hash D5D0 + sample 11: + time = 183516 + flags = 0 + data = length 208, hash E7D2035A + sample 12: + time = 200200 + flags = 0 + data = length 3, hash D600 + sample 13: + time = 216883 + flags = 0 + data = length 385, hash 4D025B28 + sample 14: + time = 233566 + flags = 0 + data = length 3, hash D5E0 + sample 15: + time = 250250 + flags = 0 + data = length 192, hash CC0BD164 + sample 16: + time = 266933 + flags = 0 + data = length 3, hash D5B0 + sample 17: + time = 283616 + flags = 0 + data = length 36989, hash C213D35E + sample 18: + time = 300300 + flags = 0 + data = length 3, hash D5C0 + sample 19: + time = 316983 + flags = 0 + data = length 213, hash 2BBA39D3 + sample 20: + time = 333666 + flags = 0 + data = length 3, hash D600 + sample 21: + time = 350350 + flags = 0 + data = length 474, hash 83D66E3F + sample 22: + time = 367033 + flags = 0 + data = length 3, hash D5E0 + sample 23: + time = 383716 + flags = 0 + data = length 246, hash CF512AF0 + sample 24: + time = 400400 + flags = 0 + data = length 3, hash D610 + sample 25: + time = 417083 + flags = 0 + data = length 880, hash 8BFDE683 + sample 26: + time = 433766 + flags = 0 + data = length 3, hash D5C0 + sample 27: + time = 450450 + flags = 0 + data = length 246, hash 16B70503 + sample 28: + time = 467133 + flags = 0 + data = length 3, hash D600 + sample 29: + time = 483816 + flags = 0 + data = length 402, hash 51B5FAC9 + sample 30: + time = 500500 + flags = 0 + data = length 3, hash D610 + sample 31: + time = 517183 + flags = 0 + data = length 199, hash 12005069 + sample 32: + time = 533866 + flags = 0 + data = length 3, hash D5D0 + sample 33: + time = 550550 + flags = 0 + data = length 32362, hash F9FE31F7 + sample 34: + time = 567233 + flags = 0 + data = length 3, hash D5E0 + sample 35: + time = 583916 + flags = 0 + data = length 215, hash 2D4E3DC4 + sample 36: + time = 600600 + flags = 0 + data = length 3, hash D600 + sample 37: + time = 617283 + flags = 0 + data = length 450, hash C1A95E3 + sample 38: + time = 633966 + flags = 0 + data = length 3, hash D610 + sample 39: + time = 650650 + flags = 0 + data = length 221, hash 964386D9 + sample 40: + time = 667333 + flags = 0 + data = length 3, hash D5F0 + sample 41: + time = 684016 + flags = 0 + data = length 853, hash 2B9E0AAF + sample 42: + time = 700700 + flags = 0 + data = length 3, hash D5E0 + sample 43: + time = 717383 + flags = 0 + data = length 236, hash 7E84BBAE + sample 44: + time = 734066 + flags = 0 + data = length 3, hash D600 + sample 45: + time = 750750 + flags = 0 + data = length 419, hash 619235F2 + sample 46: + time = 767433 + flags = 0 + data = length 3, hash D5F0 + sample 47: + time = 784116 + flags = 0 + data = length 194, hash D386F352 + sample 48: + time = 800800 + flags = 0 + data = length 3, hash D5A0 + sample 49: + time = 817483 + flags = 0 + data = length 38679, hash 17E63FCD + sample 50: + time = 834166 + flags = 0 + data = length 3, hash D610 + sample 51: + time = 850850 + flags = 0 + data = length 183, hash C8DD98E2 + sample 52: + time = 867533 + flags = 0 + data = length 3, hash D600 + sample 53: + time = 884216 + flags = 0 + data = length 457, hash 2B4E3476 + sample 54: + time = 900900 + flags = 0 + data = length 3, hash D5F0 + sample 55: + time = 917583 + flags = 0 + data = length 216, hash 7233540A + sample 56: + time = 934266 + flags = 0 + data = length 3, hash D5C0 + sample 57: + time = 950950 + flags = 0 + data = length 894, hash 7319F313 + sample 58: + time = 967633 + flags = 0 + data = length 3, hash D610 + sample 59: + time = 984316 + flags = 536870912 + data = length 233, hash DE4DBE67 +track 1: + total output bytes = 16638 + sample count = 44 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 441 + channelCount = 2 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: value=Lavf58.76.100] + initializationData: + data = length 16, hash CAA21BBF + sample 0: + time = 0 + flags = 1 + data = length 393, hash 706D1B6F + sample 1: + time = 23219 + flags = 1 + data = length 400, hash B48107D1 + sample 2: + time = 46439 + flags = 1 + data = length 398, hash E5F4E9C1 + sample 3: + time = 69659 + flags = 1 + data = length 400, hash 4317B40D + sample 4: + time = 92879 + flags = 1 + data = length 403, hash CB949D88 + sample 5: + time = 116099 + flags = 1 + data = length 411, hash 616C8F82 + sample 6: + time = 139319 + flags = 1 + data = length 392, hash 3BA50F06 + sample 7: + time = 162539 + flags = 1 + data = length 401, hash 1C62F82C + sample 8: + time = 185759 + flags = 1 + data = length 400, hash 180FEA17 + sample 9: + time = 208979 + flags = 1 + data = length 378, hash 2F6B0AE6 + sample 10: + time = 232199 + flags = 1 + data = length 375, hash 6AE86D08 + sample 11: + time = 255419 + flags = 1 + data = length 375, hash EF2FD9CC + sample 12: + time = 278639 + flags = 1 + data = length 374, hash 97B83243 + sample 13: + time = 301859 + flags = 1 + data = length 382, hash 8BD6191C + sample 14: + time = 325079 + flags = 1 + data = length 393, hash D5F53221 + sample 15: + time = 348299 + flags = 1 + data = length 375, hash 2437C16B + sample 16: + time = 371519 + flags = 1 + data = length 372, hash EE50108B + sample 17: + time = 394739 + flags = 1 + data = length 364, hash 9952E0FE + sample 18: + time = 417959 + flags = 1 + data = length 387, hash C4EC0E45 + sample 19: + time = 441179 + flags = 1 + data = length 384, hash 7DFB424F + sample 20: + time = 464399 + flags = 1 + data = length 370, hash 28619E43 + sample 21: + time = 487619 + flags = 1 + data = length 373, hash 440EB9E8 + sample 22: + time = 510839 + flags = 1 + data = length 363, hash B7655913 + sample 23: + time = 534058 + flags = 1 + data = length 362, hash A0690E92 + sample 24: + time = 557278 + flags = 1 + data = length 377, hash 41BF1244 + sample 25: + time = 580498 + flags = 1 + data = length 371, hash EE4124CD + sample 26: + time = 603718 + flags = 1 + data = length 372, hash 7A512168 + sample 27: + time = 626938 + flags = 1 + data = length 370, hash ED00D55C + sample 28: + time = 650158 + flags = 1 + data = length 356, hash 43F4FFCA + sample 29: + time = 673378 + flags = 1 + data = length 373, hash 1950F38C + sample 30: + time = 696598 + flags = 1 + data = length 366, hash 5F426A7A + sample 31: + time = 719818 + flags = 1 + data = length 371, hash FCC286D2 + sample 32: + time = 743038 + flags = 1 + data = length 366, hash CF6F5DD9 + sample 33: + time = 766258 + flags = 1 + data = length 386, hash 83E3B1E6 + sample 34: + time = 789478 + flags = 1 + data = length 369, hash 5BDF670B + sample 35: + time = 812698 + flags = 1 + data = length 367, hash DC847E4D + sample 36: + time = 835918 + flags = 1 + data = length 366, hash 8AC0C55C + sample 37: + time = 859138 + flags = 1 + data = length 375, hash C0D4BF4 + sample 38: + time = 882358 + flags = 1 + data = length 367, hash 6C5284E2 + sample 39: + time = 905578 + flags = 1 + data = length 380, hash BDFAB187 + sample 40: + time = 928798 + flags = 1 + data = length 372, hash CEF87EB6 + sample 41: + time = 952018 + flags = 1 + data = length 369, hash B0FF049B + sample 42: + time = 975238 + flags = 1 + data = length 366, hash BADD46E6 + sample 43: + time = 998458 + flags = 536870913 + data = length 374, hash 6102A531 +tracksEnded = true diff --git a/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 b/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 new file mode 100644 index 0000000000..608d6ec440 Binary files /dev/null and b/testdata/src/test/assets/media/mp4/sample_with_colr_mdcv_and_clli.mp4 differ