Replace setModificationTime API with setTimestampData in Mp4Muxer

The new API will take both `creation time` and `modification time`.

Till now, Mp4Muxer wrote `modification time` in both
`creation time` and `modification time` field, which was
incorrect.

PiperOrigin-RevId: 605590623
This commit is contained in:
sheenachhabra 2024-02-09 04:13:28 -08:00 committed by Copybara-Service
parent a31a384393
commit 8a758c2ed7
36 changed files with 193 additions and 147 deletions

View File

@ -18,7 +18,6 @@ package androidx.media3.container;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi;
import com.google.common.primitives.Longs;
@ -29,6 +28,13 @@ public final class Mp4TimestampData implements Metadata.Entry {
/** Represents an unset or unknown timescale. */
public static final int TIMESCALE_UNSET = -1;
/**
* The delta between a Unix epoch timestamp (in milliseconds since midnight, January 1, 1970) and
* an MP4 timestamp (in seconds since midnight, January 1, 1904).
*/
private static final int UNIX_EPOCH_TO_MP4_TIME_DELTA_SECONDS =
((1970 - 1904) * 365 + 17 /* leap year */) * (24 * 60 * 60);
/** The creation timestamp. */
public final long creationTimestampSeconds;
@ -41,13 +47,16 @@ public final class Mp4TimestampData implements Metadata.Entry {
/**
* Creates an instance.
*
* <p>The {@link #timescale} is set to {@link Mp4TimestampData#TIMESCALE_UNSET}.
*
* @param creationTimestampSeconds The creation time UTC in seconds since midnight, January 1,
* 1904. The {@link #modificationTimestampSeconds} is set to {@link C#TIME_UNSET} and {@link
* #timescale} is set to {@link Mp4TimestampData#TIMESCALE_UNSET}.
* 1904.
* @param modificationTimestampSeconds The modification time UTC in seconds since midnight,
* January 1, 1904.
*/
public Mp4TimestampData(long creationTimestampSeconds) {
public Mp4TimestampData(long creationTimestampSeconds, long modificationTimestampSeconds) {
this.creationTimestampSeconds = creationTimestampSeconds;
this.modificationTimestampSeconds = C.TIME_UNSET;
this.modificationTimestampSeconds = modificationTimestampSeconds;
this.timescale = TIMESCALE_UNSET;
}
@ -73,6 +82,14 @@ public final class Mp4TimestampData implements Metadata.Entry {
this.timescale = in.readLong();
}
/**
* Returns an MP4 timestamp (in seconds since midnight, January 1, 1904) from a Unix epoch
* timestamp (in milliseconds since midnight, January 1, 1970).
*/
public static long unixTimeToMp4TimeSeconds(long unixTimestampMs) {
return (unixTimestampMs / 1_000L) + UNIX_EPOCH_TO_MP4_TIME_DELTA_SECONDS;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {

View File

@ -1,43 +0,0 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.container;
import androidx.media3.common.util.UnstableApi;
/** Utilities for MP4 container. */
@UnstableApi
public final class Mp4Util {
private static final int UNIX_EPOCH_TO_MP4_TIME_DELTA_SECONDS =
((1970 - 1904) * 365 + 17 /* leap year */) * (24 * 60 * 60);
private Mp4Util() {}
/**
* Returns an MP4 timestamp (in seconds since midnight, January 1, 1904) from a Unix epoch
* timestamp (in milliseconds since midnight, January 1, 1970).
*/
public static long unixTimeToMp4TimeSeconds(long unixTimestampMs) {
return (unixTimestampMs / 1000L + UNIX_EPOCH_TO_MP4_TIME_DELTA_SECONDS);
}
/**
* Returns a Unix epoch timestamp (in milliseconds since midnight, January 1, 1970) from an MP4
* timestamp (in seconds since midnight, January 1, 1904).
*/
public static long mp4TimeToUnixTimeMs(long mp4TimestampSeconds) {
return (mp4TimestampSeconds - UNIX_EPOCH_TO_MP4_TIME_DELTA_SECONDS) * 1000L;
}
}

View File

@ -23,6 +23,7 @@ import android.media.MediaCodec;
import android.media.MediaExtractor;
import androidx.annotation.Nullable;
import androidx.media3.common.util.MediaFormatUtil;
import androidx.media3.container.Mp4TimestampData;
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.test.utils.DumpFileAsserts;
@ -85,7 +86,10 @@ public class Mp4MuxerEndToEndTest {
try {
mp4Muxer = new Mp4Muxer.Builder(checkNotNull(outputStream)).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(mp4Muxer, checkNotNull(inputFile));
} finally {
if (mp4Muxer != null) {
@ -106,7 +110,10 @@ public class Mp4MuxerEndToEndTest {
// ensure some data has been written after taking all the inputs but before closing the muxer.
assumeTrue(checkNotNull(inputFile).equals(H265_HDR10_MP4));
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(checkNotNull(outputStream)).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(mp4Muxer, inputFile);
// Muxer not closed.
@ -132,7 +139,10 @@ public class Mp4MuxerEndToEndTest {
try {
mp4Muxer =
new Mp4Muxer.Builder(checkNotNull(outputStream)).setFragmentedMp4Enabled(true).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(mp4Muxer, inputFile);
} finally {
if (mp4Muxer != null) {
@ -160,7 +170,10 @@ public class Mp4MuxerEndToEndTest {
try {
mp4Muxer =
new Mp4Muxer.Builder(checkNotNull(outputStream)).setFragmentedMp4Enabled(true).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(mp4Muxer, inputFile);
} finally {
if (mp4Muxer != null) {

View File

@ -93,14 +93,15 @@ import java.util.Locale;
public static ByteBuffer tkhd(
int trackId,
int trackDurationVu,
int creationTimestampSeconds,
int modificationTimestampSeconds,
int orientation,
Format format) {
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
contents.putInt(0x00000007); // version and flags; allow presentation, etc.
contents.putInt(modificationTimestampSeconds); // creation_time
contents.putInt(modificationTimestampSeconds); // modification_time
contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32)
contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32)
contents.putInt(trackId);
contents.putInt(0); // reserved
@ -132,12 +133,15 @@ import java.util.Locale;
* <p>This is the movie header for the entire MP4 file.
*/
public static ByteBuffer mvhd(
int nextEmptyTrackId, int modificationTimestampSeconds, long videoDurationUs) {
int nextEmptyTrackId,
int creationTimestampSeconds,
int modificationTimestampSeconds,
long videoDurationUs) {
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
contents.putInt(0); // version and flags
contents.putInt(modificationTimestampSeconds); // creation_time
contents.putInt(modificationTimestampSeconds); // modification_time
contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32)
contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32)
contents.putInt((int) MVHD_TIMEBASE); // The per-track timescales might be different.
contents.putInt(
(int) Mp4Utils.vuFromUs(videoDurationUs, MVHD_TIMEBASE)); // Duration of the entire video.
@ -175,13 +179,14 @@ import java.util.Locale;
public static ByteBuffer mdhd(
long trackDurationVu,
int videoUnitTimebase,
int creationTimestampSeconds,
int modificationTimestampSeconds,
@Nullable String languageCode) {
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
contents.putInt(0x0); // version and flags
contents.putInt(modificationTimestampSeconds); // creation_time
contents.putInt(modificationTimestampSeconds); // modification_time
contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32)
contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32)
contents.putInt(videoUnitTimebase);

View File

@ -15,11 +15,10 @@
*/
package androidx.media3.muxer;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.muxer.Mp4Utils.UNSIGNED_INT_MAX_VALUE;
import static androidx.media3.container.Mp4TimestampData.unixTimeToMp4TimeSeconds;
import androidx.media3.container.Mp4Util;
import androidx.media3.container.Mp4TimestampData;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
@ -30,14 +29,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public int orientation;
public @MonotonicNonNull Mp4Location location;
public Map<String, Object> metadataPairs;
public int modificationTimestampSeconds;
public Mp4TimestampData timestampData;
public @MonotonicNonNull ByteBuffer xmpData;
public MetadataCollector() {
orientation = 0;
metadataPairs = new LinkedHashMap<>();
modificationTimestampSeconds =
(int) Mp4Util.unixTimeToMp4TimeSeconds(System.currentTimeMillis());
long currentTimeInMp4TimeSeconds = unixTimeToMp4TimeSeconds(System.currentTimeMillis());
timestampData =
new Mp4TimestampData(
/* creationTimestampSeconds= */ currentTimeInMp4TimeSeconds,
/* modificationTimestampSeconds= */ currentTimeInMp4TimeSeconds);
}
public void addXmp(ByteBuffer xmpData) {
@ -61,10 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
metadataPairs.put(key, value);
}
public void setModificationTime(long unixTimestampMs) {
long timestampSeconds = Mp4Util.unixTimeToMp4TimeSeconds(unixTimestampMs);
checkArgument(
timestampSeconds <= UNSIGNED_INT_MAX_VALUE, "Only 32-bit long timestamp supported");
this.modificationTimestampSeconds = (int) timestampSeconds;
public void setTimestampData(Mp4TimestampData timestampData) {
this.timestampData = timestampData;
}
}

View File

@ -60,6 +60,12 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
@SuppressWarnings("InlinedApi")
public ByteBuffer moovMetadataHeader(
List<? extends TrackMetadataProvider> tracks, long minInputPtsUs, boolean isFragmentedMp4) {
// The timestamp will always fit into a 32-bit integer. This is already validated in the
// Mp4Muxer.setTimestampData() API. The value after type casting might be negative, but it is
// still valid because it is meant to be read as an unsigned integer.
int creationTimestampSeconds = (int) metadataCollector.timestampData.creationTimestampSeconds;
int modificationTimestampSeconds =
(int) metadataCollector.timestampData.modificationTimestampSeconds;
List<ByteBuffer> trakBoxes = new ArrayList<>();
List<ByteBuffer> trexBoxes = new ArrayList<>();
@ -149,14 +155,16 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
// Using the time base of the entire file, not that of the track; otherwise,
// Quicktime will stretch the audio accordingly, see b/158120042.
(int) Mp4Utils.vuFromUs(trackDurationUs, MVHD_TIMEBASE),
metadataCollector.modificationTimestampSeconds,
creationTimestampSeconds,
modificationTimestampSeconds,
metadataCollector.orientation,
format),
Boxes.mdia(
Boxes.mdhd(
trackDurationInTrackUnitsVu,
track.videoUnitTimebase(),
metadataCollector.modificationTimestampSeconds,
creationTimestampSeconds,
modificationTimestampSeconds,
languageCode),
Boxes.hdlr(handlerType, handlerName),
Boxes.minf(mhdBox, Boxes.dinf(Boxes.dref(Boxes.localUrl())), stblBox)));
@ -168,7 +176,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
}
ByteBuffer mvhdBox =
Boxes.mvhd(nextTrackId, metadataCollector.modificationTimestampSeconds, videoDurationUs);
Boxes.mvhd(
nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs);
ByteBuffer udtaBox = Boxes.udta(metadataCollector.location);
ByteBuffer metaBox =
metadataCollector.metadataPairs.isEmpty()

View File

@ -15,7 +15,9 @@
*/
package androidx.media3.muxer;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.muxer.Mp4Utils.UNSIGNED_INT_MAX_VALUE;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.MediaCodec.BufferInfo;
@ -25,6 +27,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4TimestampData;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.FileOutputStream;
@ -233,12 +236,17 @@ public final class Mp4Muxer {
}
/**
* Sets the file modification time.
* Sets the timestamp data (creation time and modification time) for the output file.
*
* @param timestampMs The modification time UTC in milliseconds since the Unix epoch.
* <p>If this method is not called, the file creation time and modification time will be when the
* {@link Mp4Muxer} was {@linkplain Builder#build() created}.
*/
public void setModificationTime(long timestampMs) {
metadataCollector.setModificationTime(timestampMs);
public void setTimestampData(Mp4TimestampData timestampData) {
checkArgument(
timestampData.creationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE
&& timestampData.modificationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE,
"Only 32-bit long timestamp is supported");
metadataCollector.setTimestampData(timestampData);
}
/**

View File

@ -66,7 +66,8 @@ public class BoxesTest {
Boxes.tkhd(
/* trackId= */ 1,
/* trackDurationVu= */ 5_000_000,
/* modificationTimestampSeconds= */ 1_000_000_000,
/* creationTimestampSeconds= */ 1_000_000_000,
/* modificationTimestampSeconds= */ 2_000_000_000,
/* orientation= */ 90,
FAKE_VIDEO_FORMAT);
@ -81,7 +82,8 @@ public class BoxesTest {
Boxes.tkhd(
/* trackId= */ 1,
/* trackDurationVu= */ 5_000_000,
/* modificationTimestampSeconds= */ 1_000_000_000,
/* creationTimestampSeconds= */ 1_000_000_000,
/* modificationTimestampSeconds= */ 2_000_000_000,
/* orientation= */ 90,
FAKE_AUDIO_FORMAT);
@ -95,7 +97,8 @@ public class BoxesTest {
ByteBuffer mvhdBox =
Boxes.mvhd(
/* nextEmptyTrackId= */ 3,
/* modificationTimestampSeconds= */ 1_000_000_000,
/* creationTimestampSeconds= */ 1_000_000_000,
/* modificationTimestampSeconds= */ 2_000_000_000,
/* videoDurationUs= */ 5_000_000);
DumpableMp4Box dumpableBox = new DumpableMp4Box(mvhdBox);
@ -108,7 +111,8 @@ public class BoxesTest {
Boxes.mdhd(
/* trackDurationVu= */ 5_000_000,
VU_TIMEBASE,
/* modificationTimestampSeconds= */ 1_000_000_000,
/* creationTimestampSeconds= */ 1_000_000_000,
/* modificationTimestampSeconds= */ 2_000_000_000,
/* languageCode= */ "und");
DumpableMp4Box dumpableBox = new DumpableMp4Box(mdhdBox);

View File

@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows;
import android.media.MediaCodec.BufferInfo;
import android.util.Pair;
import androidx.media3.container.Mp4TimestampData;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.muxer.Mp4Muxer.TrackToken;
import androidx.media3.test.utils.DumpFileAsserts;
@ -63,7 +64,10 @@ public class Mp4MuxerEndToEndTest {
public void createMp4File_withSameTracksOffset_matchesExpected() throws IOException {
String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
Pair<ByteBuffer, BufferInfo> track1Sample1 =
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
Pair<ByteBuffer, BufferInfo> track1Sample2 =
@ -103,7 +107,10 @@ public class Mp4MuxerEndToEndTest {
public void createMp4File_withDifferentTracksOffset_matchesExpected() throws IOException {
String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
Pair<ByteBuffer, BufferInfo> track1Sample1 =
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
Pair<ByteBuffer, BufferInfo> track1Sample2 =
@ -162,7 +169,10 @@ public class Mp4MuxerEndToEndTest {
public void createMp4File_withOneTrackEmpty_doesNotWriteEmptyTrack() throws Exception {
String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L);
mp4Muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
Pair<ByteBuffer, BufferInfo> track1Sample1 =
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
Pair<ByteBuffer, BufferInfo> track1Sample2 =

View File

@ -20,6 +20,7 @@ import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
import android.content.Context;
import android.media.MediaCodec.BufferInfo;
import android.util.Pair;
import androidx.media3.container.Mp4TimestampData;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.muxer.Mp4Muxer.TrackToken;
import androidx.media3.test.utils.DumpFileAsserts;
@ -53,7 +54,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally {
@ -75,7 +79,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -99,7 +106,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -123,7 +133,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -147,7 +160,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
muxer.setLocation(33.0f, -120f);
@ -170,7 +186,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally {
@ -192,7 +211,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
muxer.setCaptureFps(120.0f);
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -215,7 +237,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
muxer.addMetadata("SomeStringKey", "Some Random String");
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -238,7 +263,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
muxer.addMetadata("SomeStringKey", 10.0f);
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
@ -261,7 +289,10 @@ public class Mp4MuxerMetadataTest {
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
try {
muxer.setModificationTime(/* timestampMs= */ 5_000_000L);
muxer.setTimestampData(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L));
Context context = ApplicationProvider.getApplicationContext();
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
ByteBuffer xmp = ByteBuffer.wrap(xmpBytes);

View File

@ -1,2 +1,2 @@
tkhd (92 bytes):
Data = length 84, hash 8F9E5354
Data = length 84, hash C3AC4BE9

View File

@ -13,7 +13,7 @@ track 0:
id = 1
sampleMimeType = application/meta
maxInputSize = 161
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
sample 0:
time = 0
flags = 1
@ -39,7 +39,7 @@ track 1:
channelCount = 2
sampleRate = 48000
language = ```
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 2, hash 560
sample 0:
@ -71,7 +71,7 @@ track 2:
colorTransfer = 3
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 85, hash 6F3CAA16
sample 0:

View File

@ -23,7 +23,7 @@ track 0:
colorTransfer = 6
lumaBitdepth = 10
chromaBitdepth = 10
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 99, hash 99842E5A
sample 0:
@ -547,7 +547,7 @@ track 1:
channelCount = 2
sampleRate = 48000
language = ```
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 2, hash 560
sample 0:

View File

@ -2,13 +2,13 @@ ftyp (28 bytes):
Data = length 20, hash EF896440
moov (1209 bytes):
mvhd (108 bytes):
Data = length 100, hash 5CF3AC6F
Data = length 100, hash 2FE65289
trak (610 bytes):
tkhd (92 bytes):
Data = length 84, hash 112173F4
Data = length 84, hash 7E478E0E
mdia (510 bytes):
mdhd (32 bytes):
Data = length 24, hash 42753A93
Data = length 24, hash 87E287AD
hdlr (44 bytes):
Data = length 36, hash A0852FF2
minf (426 bytes):
@ -31,10 +31,10 @@ moov (1209 bytes):
Data = length 8, hash 94446F01
trak (411 bytes):
tkhd (92 bytes):
Data = length 84, hash 34D7906B
Data = length 84, hash A1FDAA85
mdia (311 bytes):
mdhd (32 bytes):
Data = length 24, hash EA3D1FE6
Data = length 24, hash 2FAA6D00
hdlr (44 bytes):
Data = length 36, hash 49FC755F
minf (227 bytes):

View File

@ -1,2 +1,2 @@
mdhd (32 bytes):
Data = length 24, hash D0792E76
Data = length 24, hash 71B9FC8B

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -21,7 +21,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -21,7 +21,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -21,7 +21,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -21,7 +21,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682
@ -48,7 +48,7 @@ track 1:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[mdta: key=SomeStringKey, value=10.0, Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[mdta: key=SomeStringKey, value=10.0, Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[mdta: key=com.android.capture.fps, value=120.0, Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[mdta: key=com.android.capture.fps, value=120.0, Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[xyz: latitude=33.0, longitude=-120.0, Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[xyz: latitude=33.0, longitude=-120.0, Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -21,7 +21,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682
@ -48,7 +48,7 @@ track 1:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -20,7 +20,7 @@ track 0:
colorRange = 1
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[mdta: key=SomeStringKey, value=Some Random String, Mp4Timestamp: creation time=2082849800, modification time=2082849800, timescale=10000]
metadata = entries=[mdta: key=SomeStringKey, value=Some Random String, Mp4Timestamp: creation time=1000000, modification time=5000000, timescale=10000]
initializationData:
data = length 28, hash 410B510
data = length 9, hash FBADD682

View File

@ -4,13 +4,13 @@ mdat (71 bytes):
Data = length 55, hash 6B19F4A7
moov (658 bytes):
mvhd (108 bytes):
Data = length 100, hash A5ADE288
Data = length 100, hash 2613A5C
trak (542 bytes):
tkhd (92 bytes):
Data = length 84, hash 8893F5BB
Data = length 84, hash 3D79758F
mdia (442 bytes):
mdhd (32 bytes):
Data = length 24, hash 50217AD
Data = length 24, hash 41542D81
hdlr (44 bytes):
Data = length 36, hash A0852FF2
minf (358 bytes):

View File

@ -4,13 +4,13 @@ mdat (126 bytes):
Data = length 110, hash 48173D41
moov (674 bytes):
mvhd (108 bytes):
Data = length 100, hash 3D6D026F
Data = length 100, hash 105FA889
trak (558 bytes):
tkhd (92 bytes):
Data = length 84, hash 3EFBEC22
Data = length 84, hash AC22063C
mdia (458 bytes):
mdhd (32 bytes):
Data = length 24, hash 42F40E1C
Data = length 24, hash 88615B36
hdlr (44 bytes):
Data = length 36, hash A0852FF2
minf (374 bytes):

View File

@ -1,2 +1,2 @@
mvhd (108 bytes):
Data = length 100, hash 22E47B06
Data = length 100, hash 1EE0A99B

View File

@ -23,7 +23,7 @@ track 0:
colorTransfer = 6
lumaBitdepth = 10
chromaBitdepth = 10
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 99, hash 99842E5A
sample 0:
@ -415,7 +415,7 @@ track 1:
channelCount = 2
sampleRate = 48000
language = ```
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 2, hash 560
sample 0:

View File

@ -19,7 +19,7 @@ track 0:
colorInfo:
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
sample 0:
time = 0
flags = 1
@ -153,7 +153,7 @@ track 1:
channelCount = 1
sampleRate = 44100
language = und
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 5, hash 2B7623A
sample 0:

View File

@ -17,7 +17,7 @@ track 0:
channelCount = 1
sampleRate = 44100
language = und
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 2, hash 5F7
sample 0:
@ -217,7 +217,7 @@ track 1:
colorTransfer = 3
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[Mp4Timestamp: creation time=2083344800, modification time=2083344800, timescale=10000]
metadata = entries=[Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000]
initializationData:
data = length 23, hash 33E412EE
data = length 9, hash FBAFBC0C

View File

@ -1,2 +1,2 @@
tkhd (92 bytes):
Data = length 84, hash D09E9E0B
Data = length 84, hash 4AC96A0

View File

@ -23,7 +23,7 @@ track 0:
colorTransfer = 3
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[mdta: key=com.android.version, value=13, xyz: latitude=40.68, longitude=-74.4999, Mp4Timestamp: creation time=2000000000, modification time=2000000000, timescale=10000]
metadata = entries=[mdta: key=com.android.version, value=13, xyz: latitude=40.68, longitude=-74.4999, Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000]
initializationData:
data = length 23, hash 33E412EE
data = length 9, hash FBAFBC0C
@ -158,7 +158,7 @@ track 1:
channelCount = 1
sampleRate = 44100
language = und
metadata = entries=[mdta: key=com.android.version, value=13, xyz: latitude=40.68, longitude=-74.4999, Mp4Timestamp: creation time=2000000000, modification time=2000000000, timescale=10000]
metadata = entries=[mdta: key=com.android.version, value=13, xyz: latitude=40.68, longitude=-74.4999, Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000]
initializationData:
data = length 2, hash 5F7
sample 0:

View File

@ -28,7 +28,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4LocationData;
import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.Mp4Util;
import androidx.media3.container.XmpData;
import androidx.media3.muxer.Mp4Muxer;
import androidx.media3.muxer.Mp4Muxer.TrackToken;
@ -290,9 +289,7 @@ public final class InAppMuxer implements Muxer {
} else if (entry instanceof XmpData) {
mp4Muxer.addXmp(ByteBuffer.wrap(((XmpData) entry).data));
} else if (entry instanceof Mp4TimestampData) {
// TODO: b/285281716 - Use creation time specific API.
mp4Muxer.setModificationTime(
Mp4Util.mp4TimeToUnixTimeMs(((Mp4TimestampData) entry).creationTimestampSeconds));
mp4Muxer.setTimestampData((Mp4TimestampData) entry);
} else if (entry instanceof MdtaMetadataEntry) {
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
if (mdtaMetadataEntry.key.equals(MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS)) {

View File

@ -16,7 +16,6 @@
package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.container.Mp4TimestampData.TIMESCALE_UNSET;
import static androidx.media3.test.utils.FileUtil.retrieveTrackFormat;
import static com.google.common.truth.Truth.assertThat;
@ -70,9 +69,8 @@ public class TransformerWithInAppMuxerEndToEndTest {
// Add timestamp to make output file deterministic.
metadataEntries.add(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 2_000_000_000L,
/* modificationTimestampSeconds= */ 2_000_000_000L,
TIMESCALE_UNSET)))
/* creationTimestampSeconds= */ 3_000_000_000L,
/* modificationTimestampSeconds= */ 4_000_000_000L)))
.build();
Transformer transformer =
@ -183,12 +181,10 @@ public class TransformerWithInAppMuxerEndToEndTest {
@Test
public void transmux_withTimestampData_writesSameTimestampData() throws Exception {
// TODO: b/285281716 - Use different value for modification timestamp.
Mp4TimestampData expectedTimestampData =
new Mp4TimestampData(
/* creationTimestampSeconds= */ 2_000_000_000L,
/* modificationTimestampSeconds= */ 2_000_000_000L,
TIMESCALE_UNSET);
/* creationTimestampSeconds= */ 3_000_000_000L,
/* modificationTimestampSeconds= */ 4_000_000_000L);
Muxer.Factory inAppMuxerFactory =
new InAppMuxer.Factory.Builder()
.setMetadataProvider(metadataEntries -> metadataEntries.add(expectedTimestampData))