mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add support for adding XMP data via transformer
PiperOrigin-RevId: 534801202
This commit is contained in:
parent
41492b6e29
commit
71facd825e
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/** Stores XMP data. */
|
||||||
|
@UnstableApi
|
||||||
|
public final class XmpData implements Metadata.Entry {
|
||||||
|
public final byte[] data;
|
||||||
|
|
||||||
|
/** Creates an instance. */
|
||||||
|
public XmpData(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XmpData(Parcel in) {
|
||||||
|
this.data = Util.castNonNull(in.createByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.equals(data, ((XmpData) obj).data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Arrays.hashCode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "XMP: " + Util.toHexString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parcelable implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByteArray(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<XmpData> CREATOR =
|
||||||
|
new Parcelable.Creator<XmpData>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmpData createFromParcel(Parcel in) {
|
||||||
|
return new XmpData(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmpData[] newArray(int size) {
|
||||||
|
return new XmpData[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -25,6 +25,7 @@ import androidx.media3.common.Metadata;
|
|||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
|
import androidx.media3.container.XmpData;
|
||||||
import androidx.media3.muxer.Mp4Muxer;
|
import androidx.media3.muxer.Mp4Muxer;
|
||||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -46,15 +47,16 @@ public final class InAppMuxer implements Muxer {
|
|||||||
public interface MetadataProvider {
|
public interface MetadataProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the list of {@link Metadata.Entry metadata entries}.
|
* Updates the list of {@linkplain Metadata.Entry metadata entries}.
|
||||||
*
|
*
|
||||||
* <p>A {@link Metadata.Entry} can be added or removed. To modify an existing {@link
|
* <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.
|
* Metadata.Entry}, first remove it and then add a new one.
|
||||||
*
|
*
|
||||||
* <p>List of supported {@link Metadata.Entry}:
|
* <p>List of supported {@linkplain Metadata.Entry metadata entries}:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link Mp4LocationData}
|
* <li>{@link Mp4LocationData}
|
||||||
|
* <li>{@link XmpData}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
||||||
@ -177,7 +179,7 @@ public final class InAppMuxer implements Muxer {
|
|||||||
Metadata.Entry entry = metadata.get(i);
|
Metadata.Entry entry = metadata.get(i);
|
||||||
// Keep only supported metadata.
|
// Keep only supported metadata.
|
||||||
// LINT.IfChange(added_metadata)
|
// LINT.IfChange(added_metadata)
|
||||||
if (entry instanceof Mp4LocationData) {
|
if (entry instanceof Mp4LocationData || entry instanceof XmpData) {
|
||||||
metadataEntries.add(entry);
|
metadataEntries.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,13 +187,6 @@ public final class InAppMuxer implements Muxer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release(boolean forCancellation) throws MuxerException {
|
public void release(boolean forCancellation) throws MuxerException {
|
||||||
if (metadataProvider != null) {
|
|
||||||
Set<Metadata.Entry> metadataEntriesCopy = new LinkedHashSet<>(metadataEntries);
|
|
||||||
metadataProvider.updateMetadataEntries(metadataEntriesCopy);
|
|
||||||
metadataEntries.clear();
|
|
||||||
metadataEntries.addAll(metadataEntriesCopy);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMetadata();
|
writeMetadata();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -207,11 +202,22 @@ public final class InAppMuxer implements Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeMetadata() {
|
private void writeMetadata() {
|
||||||
|
if (metadataProvider != null) {
|
||||||
|
Set<Metadata.Entry> metadataEntriesCopy = new LinkedHashSet<>(metadataEntries);
|
||||||
|
metadataProvider.updateMetadataEntries(metadataEntriesCopy);
|
||||||
|
metadataEntries.clear();
|
||||||
|
metadataEntries.addAll(metadataEntriesCopy);
|
||||||
|
}
|
||||||
|
|
||||||
for (Metadata.Entry entry : metadataEntries) {
|
for (Metadata.Entry entry : metadataEntries) {
|
||||||
// LINT.IfChange(written_metadata)
|
// LINT.IfChange(written_metadata)
|
||||||
if (entry instanceof Mp4LocationData) {
|
if (entry instanceof Mp4LocationData) {
|
||||||
mp4Muxer.setLocation(
|
mp4Muxer.setLocation(
|
||||||
((Mp4LocationData) entry).latitude, ((Mp4LocationData) entry).longitude);
|
((Mp4LocationData) entry).latitude, ((Mp4LocationData) entry).longitude);
|
||||||
|
} else if (entry instanceof XmpData) {
|
||||||
|
mp4Muxer.addXmp(ByteBuffer.wrap(((XmpData) entry).data));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unsupported Metadata.Entry " + entry.getClass().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -23,13 +24,13 @@ import androidx.media3.common.MediaItem;
|
|||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
|
import androidx.media3.container.XmpData;
|
||||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.FakeClock;
|
import androidx.media3.test.utils.FakeClock;
|
||||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@ -42,6 +43,7 @@ import org.junit.runner.RunWith;
|
|||||||
public class TransformerWithInAppMuxerEndToEndTest {
|
public class TransformerWithInAppMuxerEndToEndTest {
|
||||||
private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/";
|
private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/";
|
||||||
private static final String H264_MP4 = "mp4/sample.mp4";
|
private static final String H264_MP4 = "mp4/sample.mp4";
|
||||||
|
private static final String XMP_SAMPLE_DATA = "media/xmp/sample_datetime_xmp.xmp";
|
||||||
private Context context;
|
private Context context;
|
||||||
private String outputPath;
|
private String outputPath;
|
||||||
|
|
||||||
@ -58,7 +60,6 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transmux_withLocationMetadata_outputMatchedExpected() throws Exception {
|
public void transmux_withLocationMetadata_outputMatchedExpected() throws Exception {
|
||||||
String outputPath = Util.createTempFile(context, "TransformerTest").getPath();
|
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory(
|
new InAppMuxer.Factory(
|
||||||
DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS,
|
DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS,
|
||||||
@ -72,15 +73,8 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
|||||||
.setMuxerFactory(inAppMuxerFactory)
|
.setMuxerFactory(inAppMuxerFactory)
|
||||||
.build();
|
.build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + H264_MP4));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + H264_MP4));
|
||||||
EditedMediaItem editedMediaItem =
|
|
||||||
new EditedMediaItem.Builder(mediaItem)
|
|
||||||
.setEffects(
|
|
||||||
new Effects(
|
|
||||||
/* audioProcessors= */ ImmutableList.of(),
|
|
||||||
/* videoEffects= */ ImmutableList.of()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
transformer.start(editedMediaItem, outputPath);
|
transformer.start(mediaItem, outputPath);
|
||||||
TransformerTestRunner.runLooper(transformer);
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
FakeExtractorOutput fakeExtractorOutput =
|
FakeExtractorOutput fakeExtractorOutput =
|
||||||
@ -92,4 +86,25 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
|||||||
fakeExtractorOutput,
|
fakeExtractorOutput,
|
||||||
TestUtil.getDumpFileName(H264_MP4 + ".with_location_metadata"));
|
TestUtil.getDumpFileName(H264_MP4 + ".with_location_metadata"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void transmux_withXmpData_completesSuccessfully() throws Exception {
|
||||||
|
byte[] xmpData = androidx.media3.test.utils.TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
|
||||||
|
Muxer.Factory inAppMuxerFactory =
|
||||||
|
new InAppMuxer.Factory(
|
||||||
|
DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS,
|
||||||
|
metadataEntries -> metadataEntries.add(new XmpData(xmpData)));
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context)
|
||||||
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
|
.setMuxerFactory(inAppMuxerFactory)
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + H264_MP4));
|
||||||
|
|
||||||
|
transformer.start(mediaItem, outputPath);
|
||||||
|
ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
// TODO(b/270956881): Use FakeExtractorOutput once it starts dumping uuid box.
|
||||||
|
assertThat(exportResult.exportException).isNull();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user