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.util.UnstableApi;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
import androidx.media3.container.XmpData;
|
||||
import androidx.media3.muxer.Mp4Muxer;
|
||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -46,15 +47,16 @@ public final class InAppMuxer implements Muxer {
|
||||
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
|
||||
* 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>
|
||||
* <li>{@link Mp4LocationData}
|
||||
* <li>{@link XmpData}
|
||||
* </ul>
|
||||
*/
|
||||
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
||||
@ -177,7 +179,7 @@ public final class InAppMuxer implements Muxer {
|
||||
Metadata.Entry entry = metadata.get(i);
|
||||
// Keep only supported metadata.
|
||||
// LINT.IfChange(added_metadata)
|
||||
if (entry instanceof Mp4LocationData) {
|
||||
if (entry instanceof Mp4LocationData || entry instanceof XmpData) {
|
||||
metadataEntries.add(entry);
|
||||
}
|
||||
}
|
||||
@ -185,13 +187,6 @@ public final class InAppMuxer implements Muxer {
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
try {
|
||||
@ -207,11 +202,22 @@ public final class InAppMuxer implements Muxer {
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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 {
|
||||
throw new IllegalStateException("Unsupported Metadata.Entry " + entry.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
@ -23,13 +24,13 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
import androidx.media3.container.XmpData;
|
||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import org.junit.After;
|
||||
@ -42,6 +43,7 @@ import org.junit.runner.RunWith;
|
||||
public class TransformerWithInAppMuxerEndToEndTest {
|
||||
private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/";
|
||||
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 String outputPath;
|
||||
|
||||
@ -58,7 +60,6 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
||||
|
||||
@Test
|
||||
public void transmux_withLocationMetadata_outputMatchedExpected() throws Exception {
|
||||
String outputPath = Util.createTempFile(context, "TransformerTest").getPath();
|
||||
Muxer.Factory inAppMuxerFactory =
|
||||
new InAppMuxer.Factory(
|
||||
DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS,
|
||||
@ -72,15 +73,8 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
||||
.setMuxerFactory(inAppMuxerFactory)
|
||||
.build();
|
||||
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);
|
||||
|
||||
FakeExtractorOutput fakeExtractorOutput =
|
||||
@ -92,4 +86,25 @@ public class TransformerWithInAppMuxerEndToEndTest {
|
||||
fakeExtractorOutput,
|
||||
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