Refactor editable video track related logic

This is to reuse same logic in depth/edit file format `interleave` mode.

PiperOrigin-RevId: 662117528
This commit is contained in:
sheenachhabra 2024-08-12 09:30:03 -07:00 committed by Copybara-Service
parent 62da288caf
commit 96f2c7ece7
3 changed files with 142 additions and 84 deletions

View File

@ -1049,6 +1049,16 @@ import java.util.List;
return BoxUtils.wrapIntoBox("trex", contents); return BoxUtils.wrapIntoBox("trex", contents);
} }
/** Returns the edvd box header. */
public static ByteBuffer getEdvdBoxHeader(long payloadSize) {
ByteBuffer edvdBoxHeader = ByteBuffer.allocate(LARGE_SIZE_BOX_HEADER_SIZE);
edvdBoxHeader.putInt(1); // indicating a 64-bit length field
edvdBoxHeader.put(Util.getUtf8Bytes("edvd"));
edvdBoxHeader.putLong(LARGE_SIZE_BOX_HEADER_SIZE + payloadSize); // the actual length
edvdBoxHeader.flip();
return edvdBoxHeader;
}
// TODO: b/317117431 - Change this method to getLastSampleDuration(). // TODO: b/317117431 - Change this method to getLastSampleDuration().
/** Adjusts the duration of the very last sample if needed. */ /** Adjusts the duration of the very last sample if needed. */
private static void adjustLastSampleDuration( private static void adjustLastSampleDuration(

View File

@ -18,13 +18,13 @@ package androidx.media3.muxer;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.container.MdtaMetadataEntry.EDITABLE_TRACKS_SAMPLES_LOCATION_IN_EDIT_DATA_MP4;
import static androidx.media3.container.MdtaMetadataEntry.TYPE_INDICATOR_8_BIT_UNSIGNED_INT;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_INVERSE;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_LINEAR;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_METADATA;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_SHARP;
import static androidx.media3.muxer.Boxes.LARGE_SIZE_BOX_HEADER_SIZE; import static androidx.media3.muxer.Boxes.LARGE_SIZE_BOX_HEADER_SIZE;
import static androidx.media3.muxer.Boxes.getEdvdBoxHeader;
import static androidx.media3.muxer.MuxerUtil.getEditableTracksLengthMetadata;
import static androidx.media3.muxer.MuxerUtil.getEditableTracksOffsetMetadata;
import static androidx.media3.muxer.MuxerUtil.isEditableVideoTrack;
import static androidx.media3.muxer.MuxerUtil.isMetadataSupported;
import static androidx.media3.muxer.MuxerUtil.populateEditableVideoTracksMetadata;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
@ -35,14 +35,12 @@ import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4LocationData; import androidx.media3.container.Mp4LocationData;
import androidx.media3.container.Mp4OrientationData; import androidx.media3.container.Mp4OrientationData;
import androidx.media3.container.Mp4TimestampData; import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.XmpData; import androidx.media3.container.XmpData;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.common.primitives.Longs;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -425,7 +423,7 @@ public final class Mp4Muxer implements Muxer {
*/ */
@Override @Override
public void addMetadataEntry(Metadata.Entry metadataEntry) { public void addMetadataEntry(Metadata.Entry metadataEntry) {
checkArgument(MuxerUtil.isMetadataSupported(metadataEntry), "Unsupported metadata"); checkArgument(isMetadataSupported(metadataEntry), "Unsupported metadata");
metadataCollector.addMetadata(metadataEntry); metadataCollector.addMetadata(metadataEntry);
} }
@ -464,14 +462,6 @@ public final class Mp4Muxer implements Muxer {
} }
} }
private static boolean isEditableVideoTrack(Format format) {
return (format.roleFlags & C.ROLE_FLAG_AUXILIARY) > 0
&& (format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_ORIGINAL
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_INVERSE
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_METADATA);
}
@EnsuresNonNull({"editableVideoMp4Writer"}) @EnsuresNonNull({"editableVideoMp4Writer"})
private void ensureSetupForEditableVideoTracks() throws FileNotFoundException { private void ensureSetupForEditableVideoTracks() throws FileNotFoundException {
if (editableVideoMp4Writer == null) { if (editableVideoMp4Writer == null) {
@ -494,80 +484,28 @@ public final class Mp4Muxer implements Muxer {
// Editable video tracks were not added. // Editable video tracks were not added.
return; return;
} }
populateEditableVideoTracksMetadata(
// Write editable tracks map. checkNotNull(editableVideoMetadataCollector),
// 1 byte version + 1 byte track count (n) + n bytes track types. metadataCollector.timestampData,
int totalTracks = editableVideoTracks.size(); /* samplesInterleaved= */ false,
int dataSize = 2 + totalTracks; editableVideoTracks);
byte[] data = new byte[dataSize];
data[0] = 1; // version
data[1] = (byte) totalTracks; // track count
for (int i = 0; i < totalTracks; i++) {
checkState(editableVideoTracks.get(i) instanceof Track);
Track track = (Track) editableVideoTracks.get(i);
int trackType;
switch (track.format.auxiliaryTrackType) {
case C.AUXILIARY_TRACK_TYPE_ORIGINAL:
trackType = EDITABLE_TRACK_TYPE_SHARP;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR:
trackType = EDITABLE_TRACK_TYPE_DEPTH_LINEAR;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_INVERSE:
trackType = EDITABLE_TRACK_TYPE_DEPTH_INVERSE;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_METADATA:
trackType = EDITABLE_TRACK_TYPE_DEPTH_METADATA;
break;
default:
throw new IllegalArgumentException(
"Unsupported auxiliary track type " + track.format.auxiliaryTrackType);
}
data[i + 2] = (byte) trackType;
}
checkNotNull(editableVideoMetadataCollector);
editableVideoMetadataCollector.addMetadata(
new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_SAMPLES_LOCATION,
new byte[] {EDITABLE_TRACKS_SAMPLES_LOCATION_IN_EDIT_DATA_MP4},
TYPE_INDICATOR_8_BIT_UNSIGNED_INT));
editableVideoMetadataCollector.addMetadata(
new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_MAP,
data,
MdtaMetadataEntry.TYPE_INDICATOR_RESERVED));
editableVideoMetadataCollector.addMetadata(metadataCollector.timestampData);
checkNotNull(editableVideoMp4Writer).finishWritingSamplesAndFinalizeMoovBox(); checkNotNull(editableVideoMp4Writer).finishWritingSamplesAndFinalizeMoovBox();
} }
private void finishWritingPrimaryVideoTracks() throws IOException { private void finishWritingPrimaryVideoTracks() throws IOException {
// The exact offset is known after writing all the data in mp4Writer. // The exact offset is known after writing all the data in mp4Writer.
@Nullable MdtaMetadataEntry placeholderEditableTrackOffset = getEditableTracksOffsetMetadata(0L);
MdtaMetadataEntry placeholderEditableTrackOffset =
new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_OFFSET,
new byte[8],
MdtaMetadataEntry.TYPE_INDICATOR_UNSIGNED_INT64);
if (editableVideoMp4Writer != null) { if (editableVideoMp4Writer != null) {
long editableVideoDataSize = checkNotNull(cacheFileOutputStream).getChannel().size(); long editableVideoDataSize = checkNotNull(cacheFileOutputStream).getChannel().size();
long edvdBoxSize = LARGE_SIZE_BOX_HEADER_SIZE + editableVideoDataSize; long edvdBoxSize = LARGE_SIZE_BOX_HEADER_SIZE + editableVideoDataSize;
metadataCollector.addMetadata( metadataCollector.addMetadata(getEditableTracksLengthMetadata(edvdBoxSize));
new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_LENGTH,
Longs.toByteArray(edvdBoxSize),
MdtaMetadataEntry.TYPE_INDICATOR_UNSIGNED_INT64));
metadataCollector.addMetadata(placeholderEditableTrackOffset); metadataCollector.addMetadata(placeholderEditableTrackOffset);
} }
mp4Writer.finishWritingSamplesAndFinalizeMoovBox(); mp4Writer.finishWritingSamplesAndFinalizeMoovBox();
if (editableVideoMp4Writer != null) { if (editableVideoMp4Writer != null) {
long primaryVideoDataSize = outputChannel.size(); long primaryVideoDataSize = outputChannel.size();
metadataCollector.removeMdtaMetadataEntry(placeholderEditableTrackOffset); metadataCollector.removeMdtaMetadataEntry(placeholderEditableTrackOffset);
metadataCollector.addMetadata( metadataCollector.addMetadata(getEditableTracksOffsetMetadata(primaryVideoDataSize));
new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_OFFSET,
Longs.toByteArray(primaryVideoDataSize),
MdtaMetadataEntry.TYPE_INDICATOR_UNSIGNED_INT64));
mp4Writer.finalizeMoovBox(); mp4Writer.finalizeMoovBox();
checkState( checkState(
outputChannel.size() == primaryVideoDataSize, outputChannel.size() == primaryVideoDataSize,
@ -582,13 +520,7 @@ public final class Mp4Muxer implements Muxer {
} }
outputChannel.position(outputChannel.size()); outputChannel.position(outputChannel.size());
FileInputStream inputStream = new FileInputStream(checkNotNull(cacheFilePath)); FileInputStream inputStream = new FileInputStream(checkNotNull(cacheFilePath));
ByteBuffer edvdBoxHeader = ByteBuffer.allocate(LARGE_SIZE_BOX_HEADER_SIZE); outputChannel.write(getEdvdBoxHeader(inputStream.getChannel().size()));
edvdBoxHeader.putInt(1); // indicating a 64-bit length field
edvdBoxHeader.put(Util.getUtf8Bytes("edvd"));
edvdBoxHeader.putLong(
LARGE_SIZE_BOX_HEADER_SIZE + inputStream.getChannel().size()); // the actual length
edvdBoxHeader.flip();
outputChannel.write(edvdBoxHeader);
ByteStreams.copy(inputStream, outputStream); ByteStreams.copy(inputStream, outputStream);
inputStream.close(); inputStream.close();
} }

View File

@ -15,6 +15,17 @@
*/ */
package androidx.media3.muxer; package androidx.media3.muxer;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.container.MdtaMetadataEntry.EDITABLE_TRACKS_SAMPLES_LOCATION_INTERLEAVED;
import static androidx.media3.container.MdtaMetadataEntry.EDITABLE_TRACKS_SAMPLES_LOCATION_IN_EDIT_DATA_MP4;
import static androidx.media3.container.MdtaMetadataEntry.TYPE_INDICATOR_8_BIT_UNSIGNED_INT;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_INVERSE;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_LINEAR;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_METADATA;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_SHARP;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.MdtaMetadataEntry;
@ -22,6 +33,8 @@ import androidx.media3.container.Mp4LocationData;
import androidx.media3.container.Mp4OrientationData; import androidx.media3.container.Mp4OrientationData;
import androidx.media3.container.Mp4TimestampData; import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.XmpData; import androidx.media3.container.XmpData;
import com.google.common.primitives.Longs;
import java.util.List;
/** Utility methods for muxer. */ /** Utility methods for muxer. */
@UnstableApi @UnstableApi
@ -42,6 +55,109 @@ public final class MuxerUtil {
|| metadata instanceof XmpData; || metadata instanceof XmpData;
} }
/**
* Returns whether the given {@linkplain Format track format} is an editable video track.
*
* <p>The {@linkplain Format track format} with {@link C#ROLE_FLAG_AUXILIARY} and the {@code
* auxiliaryTrackType} from the following are considered as an editable video track.
*
* <ul>
* <li>{@link C#AUXILIARY_TRACK_TYPE_ORIGINAL}
* <li>{@link C#AUXILIARY_TRACK_TYPE_DEPTH_LINEAR}
* <li>{@link C#AUXILIARY_TRACK_TYPE_DEPTH_INVERSE}
* <li>{@link C#AUXILIARY_TRACK_TYPE_DEPTH_METADATA}
* </ul>
*/
/* package */ static boolean isEditableVideoTrack(Format format) {
return (format.roleFlags & C.ROLE_FLAG_AUXILIARY) > 0
&& (format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_ORIGINAL
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_INVERSE
|| format.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_DEPTH_METADATA);
}
/** Returns a {@link MdtaMetadataEntry} for the editable tracks offset metadata. */
/* package */ static MdtaMetadataEntry getEditableTracksOffsetMetadata(long offset) {
return new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_OFFSET,
Longs.toByteArray(offset),
MdtaMetadataEntry.TYPE_INDICATOR_UNSIGNED_INT64);
}
/** Returns a {@link MdtaMetadataEntry} for the editable tracks length metadata. */
/* package */ static MdtaMetadataEntry getEditableTracksLengthMetadata(long length) {
return new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_LENGTH,
Longs.toByteArray(length),
MdtaMetadataEntry.TYPE_INDICATOR_UNSIGNED_INT64);
}
/**
* Populates editable video tracks metadata.
*
* @param metadataCollector The {@link MetadataCollector} to add the metadata to.
* @param timestampData The {@link Mp4TimestampData}.
* @param samplesInterleaved Whether editable video track samples are interleaved with the primary
* track samples.
* @param editableVideoTracks The editable video tracks.
*/
/* package */ static void populateEditableVideoTracksMetadata(
MetadataCollector metadataCollector,
Mp4TimestampData timestampData,
boolean samplesInterleaved,
List<Muxer.TrackToken> editableVideoTracks) {
metadataCollector.addMetadata(timestampData);
metadataCollector.addMetadata(getEditableTracksSamplesLocationMetadata(samplesInterleaved));
metadataCollector.addMetadata(getEditableTracksMapMetadata(editableVideoTracks));
}
private static MdtaMetadataEntry getEditableTracksSamplesLocationMetadata(
boolean samplesInterleaved) {
return new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_SAMPLES_LOCATION,
new byte[] {
samplesInterleaved
? EDITABLE_TRACKS_SAMPLES_LOCATION_INTERLEAVED
: EDITABLE_TRACKS_SAMPLES_LOCATION_IN_EDIT_DATA_MP4
},
TYPE_INDICATOR_8_BIT_UNSIGNED_INT);
}
private static MdtaMetadataEntry getEditableTracksMapMetadata(
List<Muxer.TrackToken> editableVideoTracks) {
// 1 byte version + 1 byte track count (n) + n bytes track types.
int totalTracks = editableVideoTracks.size();
int dataSize = 2 + totalTracks;
byte[] data = new byte[dataSize];
data[0] = 1; // version
data[1] = (byte) totalTracks; // track count
for (int i = 0; i < totalTracks; i++) {
checkState(editableVideoTracks.get(i) instanceof Track);
Track track = (Track) editableVideoTracks.get(i);
int trackType;
switch (track.format.auxiliaryTrackType) {
case C.AUXILIARY_TRACK_TYPE_ORIGINAL:
trackType = EDITABLE_TRACK_TYPE_SHARP;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR:
trackType = EDITABLE_TRACK_TYPE_DEPTH_LINEAR;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_INVERSE:
trackType = EDITABLE_TRACK_TYPE_DEPTH_INVERSE;
break;
case C.AUXILIARY_TRACK_TYPE_DEPTH_METADATA:
trackType = EDITABLE_TRACK_TYPE_DEPTH_METADATA;
break;
default:
throw new IllegalArgumentException(
"Unsupported editable track type " + track.format.auxiliaryTrackType);
}
data[i + 2] = (byte) trackType;
}
return new MdtaMetadataEntry(
MdtaMetadataEntry.KEY_EDITABLE_TRACKS_MAP, data, MdtaMetadataEntry.TYPE_INDICATOR_RESERVED);
}
private static boolean isMdtaMetadataEntrySupported(MdtaMetadataEntry mdtaMetadataEntry) { private static boolean isMdtaMetadataEntrySupported(MdtaMetadataEntry mdtaMetadataEntry) {
return mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING return mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING
|| mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32; || mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32;