Add unified addMetadata() method in Mp4Muxer
PiperOrigin-RevId: 610710011
This commit is contained in:
parent
0e0e1c4f1a
commit
94e0a27a81
@ -32,6 +32,7 @@ import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.container.MdtaMetadataEntry;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
import androidx.media3.container.NalUnitUtil;
|
||||
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
||||
@ -395,14 +396,13 @@ import java.util.Locale;
|
||||
*
|
||||
* <p>This box contains a list of metadata keys.
|
||||
*/
|
||||
public static ByteBuffer keys(List<String> keyNames) {
|
||||
// This should be an adaptive size here; we don't yet care since it's usually small.
|
||||
public static ByteBuffer keys(List<MdtaMetadataEntry> mdtaMetadataEntries) {
|
||||
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
||||
contents.putInt(0x0); // version and flags.
|
||||
contents.putInt(keyNames.size()); // num of entries
|
||||
contents.putInt(0x0); // version and flags
|
||||
contents.putInt(mdtaMetadataEntries.size()); // Entry count
|
||||
|
||||
for (int i = 0; i < keyNames.size(); i++) {
|
||||
ByteBuffer keyNameBuffer = ByteBuffer.wrap(Util.getUtf8Bytes(keyNames.get(i)));
|
||||
for (int i = 0; i < mdtaMetadataEntries.size(); i++) {
|
||||
ByteBuffer keyNameBuffer = ByteBuffer.wrap(Util.getUtf8Bytes(mdtaMetadataEntries.get(i).key));
|
||||
contents.put(BoxUtils.wrapIntoBox("mdta", keyNameBuffer));
|
||||
}
|
||||
|
||||
@ -415,31 +415,18 @@ import java.util.Locale;
|
||||
*
|
||||
* <p>This box contains a list of metadata values.
|
||||
*/
|
||||
public static ByteBuffer ilst(List<Object> values) {
|
||||
// This should be an adaptive size here; we don't yet care since it's usually small.
|
||||
public static ByteBuffer ilst(List<MdtaMetadataEntry> mdtaMetadataEntries) {
|
||||
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
for (int i = 0; i < mdtaMetadataEntries.size(); i++) {
|
||||
int keyId = i + 1;
|
||||
Object value = values.get(i);
|
||||
MdtaMetadataEntry currentMdtaMetadataEntry = mdtaMetadataEntries.get(i);
|
||||
|
||||
ByteBuffer valueContents;
|
||||
if (value instanceof String) {
|
||||
String valueString = (String) value;
|
||||
byte[] valueBytes = Util.getUtf8Bytes(valueString);
|
||||
valueContents = ByteBuffer.allocate(valueBytes.length + 8);
|
||||
valueContents.putInt(1); // type code for UTF-8 string
|
||||
valueContents.putInt(0); // default country / language
|
||||
valueContents.put(valueBytes);
|
||||
|
||||
} else if (value instanceof Float) {
|
||||
valueContents = ByteBuffer.allocate(12);
|
||||
valueContents.putInt(23); // float32
|
||||
valueContents.putInt(0); // language / country
|
||||
valueContents.putFloat((float) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown metadata type: " + value.getClass());
|
||||
}
|
||||
ByteBuffer valueContents =
|
||||
ByteBuffer.allocate(2 * BYTES_PER_INTEGER + currentMdtaMetadataEntry.value.length);
|
||||
valueContents.putInt(currentMdtaMetadataEntry.typeIndicator);
|
||||
valueContents.putInt(currentMdtaMetadataEntry.localeIndicator);
|
||||
valueContents.put(currentMdtaMetadataEntry.value);
|
||||
|
||||
valueContents.flip();
|
||||
ByteBuffer valueBox = BoxUtils.wrapIntoBox("data", valueContents);
|
||||
|
@ -15,27 +15,28 @@
|
||||
*/
|
||||
package androidx.media3.muxer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.container.Mp4TimestampData.unixTimeToMp4TimeSeconds;
|
||||
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.container.MdtaMetadataEntry;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
import androidx.media3.container.Mp4TimestampData;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import androidx.media3.container.XmpData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** Collects and provides metadata: location, FPS, XMP data, etc. */
|
||||
/* package */ final class MetadataCollector {
|
||||
public int orientation;
|
||||
public @MonotonicNonNull Mp4LocationData locationData;
|
||||
public Map<String, Object> metadataPairs;
|
||||
public List<MdtaMetadataEntry> metadataEntries;
|
||||
public Mp4TimestampData timestampData;
|
||||
public @MonotonicNonNull ByteBuffer xmpData;
|
||||
public @MonotonicNonNull XmpData xmpData;
|
||||
|
||||
public MetadataCollector() {
|
||||
orientation = 0;
|
||||
metadataPairs = new LinkedHashMap<>();
|
||||
metadataEntries = new ArrayList<>();
|
||||
long currentTimeInMp4TimeSeconds = unixTimeToMp4TimeSeconds(System.currentTimeMillis());
|
||||
timestampData =
|
||||
new Mp4TimestampData(
|
||||
@ -43,28 +44,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/* modificationTimestampSeconds= */ currentTimeInMp4TimeSeconds);
|
||||
}
|
||||
|
||||
public void addXmp(ByteBuffer xmpData) {
|
||||
checkState(this.xmpData == null);
|
||||
this.xmpData = xmpData;
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public void setLocation(float latitude, float longitude) {
|
||||
locationData = new Mp4LocationData(latitude, longitude);
|
||||
public void addMetadata(Metadata.Entry metadata) {
|
||||
if (metadata instanceof Mp4LocationData) {
|
||||
locationData = (Mp4LocationData) metadata;
|
||||
} else if (metadata instanceof Mp4TimestampData) {
|
||||
timestampData = (Mp4TimestampData) metadata;
|
||||
} else if (metadata instanceof MdtaMetadataEntry) {
|
||||
metadataEntries.add((MdtaMetadataEntry) metadata);
|
||||
} else if (metadata instanceof XmpData) {
|
||||
xmpData = (XmpData) metadata;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported metadata");
|
||||
}
|
||||
|
||||
public void setCaptureFps(float captureFps) {
|
||||
metadataPairs.put("com.android.capture.fps", captureFps);
|
||||
}
|
||||
|
||||
public void addMetadata(String key, Object value) {
|
||||
metadataPairs.put(key, value);
|
||||
}
|
||||
|
||||
public void setTimestampData(Mp4TimestampData timestampData) {
|
||||
this.timestampData = timestampData;
|
||||
}
|
||||
}
|
||||
|
@ -180,12 +180,12 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs);
|
||||
ByteBuffer udtaBox = Boxes.udta(metadataCollector.locationData);
|
||||
ByteBuffer metaBox =
|
||||
metadataCollector.metadataPairs.isEmpty()
|
||||
metadataCollector.metadataEntries.isEmpty()
|
||||
? ByteBuffer.allocate(0)
|
||||
: Boxes.meta(
|
||||
Boxes.hdlr(/* handlerType= */ "mdta", /* handlerName= */ ""),
|
||||
Boxes.keys(Lists.newArrayList(metadataCollector.metadataPairs.keySet())),
|
||||
Boxes.ilst(Lists.newArrayList(metadataCollector.metadataPairs.values())));
|
||||
Boxes.keys(Lists.newArrayList(metadataCollector.metadataEntries)),
|
||||
Boxes.ilst(Lists.newArrayList(metadataCollector.metadataEntries)));
|
||||
|
||||
ByteBuffer moovBox;
|
||||
moovBox =
|
||||
@ -199,7 +199,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
// Also add XMP if needed
|
||||
if (metadataCollector.xmpData != null) {
|
||||
return BoxUtils.concatenateBuffers(
|
||||
moovBox, Boxes.uuid(Boxes.XMP_UUID, metadataCollector.xmpData.duplicate()));
|
||||
moovBox, Boxes.uuid(Boxes.XMP_UUID, ByteBuffer.wrap(metadataCollector.xmpData.data)));
|
||||
} else {
|
||||
// No need for another copy if there is no XMP to be appended.
|
||||
return moovBox;
|
||||
|
@ -25,9 +25,14 @@ import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
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.XmpData;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.FileOutputStream;
|
||||
@ -205,6 +210,20 @@ public final class Mp4Muxer {
|
||||
this.metadataCollector = metadataCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given {@link Metadata.Entry metadata} is supported.
|
||||
*
|
||||
* <p>For the list of supported metadata refer to {@link Mp4Muxer#addMetadata(Metadata.Entry)}.
|
||||
*/
|
||||
public static boolean isMetadataSupported(Metadata.Entry metadata) {
|
||||
return metadata instanceof Mp4LocationData
|
||||
|| (metadata instanceof Mp4TimestampData
|
||||
&& isMp4TimestampDataSupported((Mp4TimestampData) metadata))
|
||||
|| (metadata instanceof MdtaMetadataEntry
|
||||
&& isMdtaMetadataEntrySupported((MdtaMetadataEntry) metadata))
|
||||
|| metadata instanceof XmpData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation hint for the video playback.
|
||||
*
|
||||
@ -215,57 +234,85 @@ public final class Mp4Muxer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location.
|
||||
*
|
||||
* @param latitude The latitude, in degrees. Its value must be in the range [-90, 90].
|
||||
* @param longitude The longitude, in degrees. Its value must be in the range [-180, 180].
|
||||
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link Mp4LocationData} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setLocation(
|
||||
@FloatRange(from = -90.0, to = 90.0) float latitude,
|
||||
@FloatRange(from = -180.0, to = 180.0) float longitude) {
|
||||
metadataCollector.setLocation(latitude, longitude);
|
||||
addMetadata(new Mp4LocationData(latitude, longitude));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the capture frame rate.
|
||||
*
|
||||
* @param captureFps The frame rate.
|
||||
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link MdtaMetadataEntry} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCaptureFps(float captureFps) {
|
||||
metadataCollector.setCaptureFps(captureFps);
|
||||
addMetadata(
|
||||
new MdtaMetadataEntry(
|
||||
MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS,
|
||||
Util.toByteArray(captureFps),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp data (creation time and modification time) for the output file.
|
||||
*
|
||||
* <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}.
|
||||
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link Mp4TimestampData} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
addMetadata(timestampData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom metadata.
|
||||
*
|
||||
* @param key The metadata key in {@link String} format.
|
||||
* @param value The metadata value in {@link String} or {@link Float} format.
|
||||
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link MdtaMetadataEntry} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addMetadata(String key, Object value) {
|
||||
metadataCollector.addMetadata(key, value);
|
||||
MdtaMetadataEntry mdtaMetadataEntry = null;
|
||||
if (value instanceof String) {
|
||||
mdtaMetadataEntry =
|
||||
new MdtaMetadataEntry(
|
||||
key, Util.getUtf8Bytes((String) value), MdtaMetadataEntry.TYPE_INDICATOR_STRING);
|
||||
} else if (value instanceof Float) {
|
||||
mdtaMetadataEntry =
|
||||
new MdtaMetadataEntry(
|
||||
key, Util.toByteArray((Float) value), MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported metadata");
|
||||
}
|
||||
addMetadata(mdtaMetadataEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds xmp data.
|
||||
* Adds metadata for the output file.
|
||||
*
|
||||
* @param xmp The xmp {@link ByteBuffer}.
|
||||
* <p>List of supported {@linkplain Metadata.Entry metadata entries}:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Mp4LocationData}
|
||||
* <li>{@link Mp4TimestampData}
|
||||
* <li>{@link MdtaMetadataEntry}: Only {@linkplain MdtaMetadataEntry#TYPE_INDICATOR_STRING
|
||||
* string type} or {@linkplain MdtaMetadataEntry#TYPE_INDICATOR_FLOAT32 float type} value is
|
||||
* supported.
|
||||
* <li>{@link XmpData}
|
||||
* </ul>
|
||||
*
|
||||
* @param metadata The {@linkplain Metadata.Entry metadata}. An {@link IllegalArgumentException}
|
||||
* is throw if the {@linkplain Metadata.Entry metadata} is not supported.
|
||||
*/
|
||||
public void addMetadata(Metadata.Entry metadata) {
|
||||
checkArgument(isMetadataSupported(metadata), "Unsupported metadata");
|
||||
metadataCollector.addMetadata(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link XmpData} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addXmp(ByteBuffer xmp) {
|
||||
metadataCollector.addXmp(xmp);
|
||||
byte[] xmpData = new byte[xmp.remaining()];
|
||||
xmp.get(xmpData, 0, xmpData.length);
|
||||
addMetadata(new XmpData(xmpData));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -308,4 +355,14 @@ public final class Mp4Muxer {
|
||||
public void close() throws IOException {
|
||||
mp4Writer.close();
|
||||
}
|
||||
|
||||
private static boolean isMdtaMetadataEntrySupported(MdtaMetadataEntry mdtaMetadataEntry) {
|
||||
return mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING
|
||||
|| mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32;
|
||||
}
|
||||
|
||||
private static boolean isMp4TimestampDataSupported(Mp4TimestampData timestampData) {
|
||||
return timestampData.creationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE
|
||||
&& timestampData.modificationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ import android.media.MediaCodec;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.container.MdtaMetadataEntry;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
@ -173,9 +175,19 @@ public class BoxesTest {
|
||||
|
||||
@Test
|
||||
public void createKeysBox_matchesExpected() throws Exception {
|
||||
List<String> keyNames = ImmutableList.of("com.android.version", "com.android.capture.fps");
|
||||
List<MdtaMetadataEntry> metadataEntries = new ArrayList<>();
|
||||
metadataEntries.add(
|
||||
new MdtaMetadataEntry(
|
||||
"com.android.version",
|
||||
Util.getUtf8Bytes("11"),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||
metadataEntries.add(
|
||||
new MdtaMetadataEntry(
|
||||
"com.android.capture.fps",
|
||||
Util.toByteArray(120.0f),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||
|
||||
ByteBuffer keysBox = Boxes.keys(keyNames);
|
||||
ByteBuffer keysBox = Boxes.keys(metadataEntries);
|
||||
|
||||
DumpableMp4Box dumpableBox = new DumpableMp4Box(keysBox);
|
||||
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("keys_box"));
|
||||
@ -183,9 +195,19 @@ public class BoxesTest {
|
||||
|
||||
@Test
|
||||
public void createIlstBox_matchesExpected() throws Exception {
|
||||
List<Object> values = ImmutableList.of("11", 120.0f);
|
||||
List<MdtaMetadataEntry> metadataEntries = new ArrayList<>();
|
||||
metadataEntries.add(
|
||||
new MdtaMetadataEntry(
|
||||
"com.android.version",
|
||||
Util.getUtf8Bytes("11"),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||
metadataEntries.add(
|
||||
new MdtaMetadataEntry(
|
||||
"com.android.capture.fps",
|
||||
Util.toByteArray(120.0f),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||
|
||||
ByteBuffer ilstBox = Boxes.ilst(values);
|
||||
ByteBuffer ilstBox = Boxes.ilst(metadataEntries);
|
||||
|
||||
DumpableMp4Box dumpableBox = new DumpableMp4Box(ilstBox);
|
||||
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("ilst_box"));
|
||||
|
@ -24,7 +24,11 @@ import static org.junit.Assert.assertThrows;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.util.Pair;
|
||||
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.XmpData;
|
||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
@ -56,7 +60,11 @@ public class Mp4MuxerEndToEndTest {
|
||||
try {
|
||||
mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||
mp4Muxer.setOrientation(90);
|
||||
mp4Muxer.addMetadata("key", "value");
|
||||
mp4Muxer.addMetadata(
|
||||
new MdtaMetadataEntry(
|
||||
"key",
|
||||
/* value= */ Util.getUtf8Bytes("value"),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||
} finally {
|
||||
mp4Muxer.close();
|
||||
}
|
||||
@ -69,7 +77,7 @@ 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.setTimestampData(
|
||||
mp4Muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 100_000_000L,
|
||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||
@ -112,7 +120,7 @@ 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.setTimestampData(
|
||||
mp4Muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 100_000_000L,
|
||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||
@ -174,7 +182,7 @@ 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.setTimestampData(
|
||||
mp4Muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 100_000_000L,
|
||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||
@ -209,18 +217,26 @@ public class Mp4MuxerEndToEndTest {
|
||||
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
||||
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
|
||||
ByteBuffer xmp = ByteBuffer.wrap(xmpBytes);
|
||||
|
||||
try {
|
||||
muxer.setOrientation(90);
|
||||
muxer.setLocation(/* latitude= */ 33.0f, /* longitude= */ -120f);
|
||||
muxer.setCaptureFps(120.0f);
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(new Mp4LocationData(/* latitude= */ 33.0f, /* longitude= */ -120f));
|
||||
float captureFps = 120.0f;
|
||||
muxer.addMetadata(
|
||||
new MdtaMetadataEntry(
|
||||
MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS,
|
||||
/* value= */ Util.toByteArray(captureFps),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
muxer.addMetadata("StringKey1", "StringValue");
|
||||
muxer.addXmp(xmp);
|
||||
muxer.addMetadata(
|
||||
new MdtaMetadataEntry(
|
||||
"StringKey1",
|
||||
/* value= */ Util.getUtf8Bytes("StringValue"),
|
||||
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||
muxer.addMetadata(new XmpData(xmpBytes));
|
||||
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
|
||||
} finally {
|
||||
|
@ -52,7 +52,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -77,7 +77,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -104,7 +104,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -131,7 +131,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -158,7 +158,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -184,7 +184,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -209,7 +209,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -235,7 +235,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
@ -261,7 +261,7 @@ public class Mp4MuxerMetadataTest {
|
||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||
|
||||
try {
|
||||
muxer.setTimestampData(
|
||||
muxer.addMetadata(
|
||||
new Mp4TimestampData(
|
||||
/* creationTimestampSeconds= */ 1_000_000L,
|
||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||
|
@ -24,11 +24,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
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.XmpData;
|
||||
import androidx.media3.muxer.Mp4Muxer;
|
||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -56,14 +51,7 @@ public final class InAppMuxer implements Muxer {
|
||||
* <p>A {@link Metadata.Entry} can be added or removed. To modify an existing {@link
|
||||
* Metadata.Entry}, first remove it and then add a new one.
|
||||
*
|
||||
* <p>List of supported {@linkplain Metadata.Entry metadata entries}:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Mp4LocationData}
|
||||
* <li>{@link XmpData}
|
||||
* <li>{@link Mp4TimestampData}
|
||||
* <li>{@link MdtaMetadataEntry}
|
||||
* </ul>
|
||||
* <p>For the list of supported metadata refer to {@link Mp4Muxer#addMetadata(Metadata.Entry)}.
|
||||
*/
|
||||
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
||||
}
|
||||
@ -241,17 +229,7 @@ public final class InAppMuxer implements Muxer {
|
||||
public void addMetadata(Metadata metadata) {
|
||||
for (int i = 0; i < metadata.length(); i++) {
|
||||
Metadata.Entry entry = metadata.get(i);
|
||||
// Keep only supported metadata.
|
||||
// LINT.IfChange(added_metadata)
|
||||
if (entry instanceof Mp4LocationData
|
||||
|| entry instanceof XmpData
|
||||
|| entry instanceof Mp4TimestampData
|
||||
|| (entry instanceof MdtaMetadataEntry
|
||||
&& (((MdtaMetadataEntry) entry).key.equals(MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS)
|
||||
|| ((MdtaMetadataEntry) entry).typeIndicator
|
||||
== MdtaMetadataEntry.TYPE_INDICATOR_STRING
|
||||
|| ((MdtaMetadataEntry) entry).typeIndicator
|
||||
== MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32))) {
|
||||
if (Mp4Muxer.isMetadataSupported(entry)) {
|
||||
metadataEntries.add(entry);
|
||||
}
|
||||
}
|
||||
@ -282,29 +260,7 @@ public final class InAppMuxer implements Muxer {
|
||||
}
|
||||
|
||||
for (Metadata.Entry entry : metadataEntries) {
|
||||
// LINT.IfChange(written_metadata)
|
||||
if (entry instanceof Mp4LocationData) {
|
||||
mp4Muxer.setLocation(
|
||||
((Mp4LocationData) entry).latitude, ((Mp4LocationData) entry).longitude);
|
||||
} else if (entry instanceof XmpData) {
|
||||
mp4Muxer.addXmp(ByteBuffer.wrap(((XmpData) entry).data));
|
||||
} else if (entry instanceof Mp4TimestampData) {
|
||||
mp4Muxer.setTimestampData((Mp4TimestampData) entry);
|
||||
} else if (entry instanceof MdtaMetadataEntry) {
|
||||
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
|
||||
if (mdtaMetadataEntry.key.equals(MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS)) {
|
||||
byte[] captureFps = mdtaMetadataEntry.value;
|
||||
mp4Muxer.setCaptureFps(ByteBuffer.wrap(captureFps).getFloat());
|
||||
} else if (mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING) {
|
||||
mp4Muxer.addMetadata(mdtaMetadataEntry.key, Util.fromUtf8Bytes(mdtaMetadataEntry.value));
|
||||
} else if (mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32) {
|
||||
mp4Muxer.addMetadata(mdtaMetadataEntry.key, Util.toFloat(mdtaMetadataEntry.value));
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported MdtaMetadataEntry " + mdtaMetadataEntry.key);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported Metadata.Entry " + entry.getClass().getName());
|
||||
}
|
||||
mp4Muxer.addMetadata(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user