Remove TrackToken and replace with int trackId

This is to make API simpler and to have parity with `MediaMuxer`

PiperOrigin-RevId: 712954669
This commit is contained in:
sheenachhabra 2025-01-07 10:13:42 -08:00 committed by Copybara-Service
parent 8dca430e42
commit bcae7abde9
17 changed files with 227 additions and 232 deletions

View File

@ -38,6 +38,8 @@
* Muxers: * Muxers:
* Renamed `setSampleCopyEnabled()` method to `setSampleCopyingEnabled()` * Renamed `setSampleCopyEnabled()` method to `setSampleCopyingEnabled()`
in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`. in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`.
* `Mp4Muxer.addTrack()` and `FragmentedMp4Muxer.addTrack()` now return an
`int` track id instead of a `TrackToken`.
* IMA extension: * IMA extension:
* Session: * Session:
* Fix bug where calling a `Player` method on a `MediaController` connected * Fix bug where calling a `Player` method on a `MediaController` connected

View File

@ -44,11 +44,11 @@ import java.util.List;
context.getResources().getAssets().openFd(MP4_FILE_ASSET_DIRECTORY + inputFileName); context.getResources().getAssets().openFd(MP4_FILE_ASSET_DIRECTORY + inputFileName);
extractor.setDataSource(fd); extractor.setDataSource(fd);
List<Muxer.TrackToken> addedTracks = new ArrayList<>(); List<Integer> addedTracks = new ArrayList<>();
for (int i = 0; i < extractor.getTrackCount(); i++) { for (int i = 0; i < extractor.getTrackCount(); i++) {
Muxer.TrackToken trackToken = int trackId =
muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i)));
addedTracks.add(trackToken); addedTracks.add(trackId);
extractor.selectTrack(i); extractor.selectTrack(i);
} }

View File

@ -19,6 +19,7 @@ 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 android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.util.SparseArray;
import androidx.media3.common.Format; 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;
@ -64,10 +65,9 @@ import java.nio.ByteBuffer;
* <p>To create a fragmented MP4 file, the caller must: * <p>To create a fragmented MP4 file, the caller must:
* *
* <ul> * <ul>
* <li>Add tracks using {@link #addTrack(Format)} which will return a {@link Mp4Muxer.TrackToken}. * <li>Add tracks using {@link #addTrack(Format)} which will return a track id.
* <li>Use the associated {@link Mp4Muxer.TrackToken} when {@linkplain * <li>Use the associated track id when {@linkplain #writeSampleData(int, ByteBuffer, BufferInfo)
* #writeSampleData(Mp4Muxer.TrackToken, ByteBuffer, BufferInfo) writing samples} for that * writing samples} for that track.
* track.
* <li>{@link #close} the muxer when all data has been written. * <li>{@link #close} the muxer when all data has been written.
* </ul> * </ul>
* *
@ -76,8 +76,8 @@ import java.nio.ByteBuffer;
* <ul> * <ul>
* <li>All tracks must be added before writing any samples. * <li>All tracks must be added before writing any samples.
* <li>The caller is responsible for ensuring that samples of different track types are well * <li>The caller is responsible for ensuring that samples of different track types are well
* interleaved by calling {@link #writeSampleData(Mp4Muxer.TrackToken, ByteBuffer, * interleaved by calling {@link #writeSampleData(int, ByteBuffer, BufferInfo)} in an order
* BufferInfo)} in an order that interleaves samples from different tracks. * that interleaves samples from different tracks.
* </ul> * </ul>
*/ */
@UnstableApi @UnstableApi
@ -121,10 +121,10 @@ public final class FragmentedMp4Muxer implements Muxer {
/** /**
* Sets whether to enable the sample copy. * Sets whether to enable the sample copy.
* *
* <p>If the sample copy is enabled, {@link #writeSampleData(TrackToken, ByteBuffer, * <p>If the sample copy is enabled, {@link #writeSampleData(int, ByteBuffer, BufferInfo)}
* BufferInfo)} copies the input {@link ByteBuffer} and {@link BufferInfo} before it returns, so * copies the input {@link ByteBuffer} and {@link BufferInfo} before it returns, so it is safe
* it is safe to reuse them immediately. Otherwise, the muxer takes ownership of the {@link * to reuse them immediately. Otherwise, the muxer takes ownership of the {@link ByteBuffer} and
* ByteBuffer} and the {@link BufferInfo} and the caller must not modify them. * the {@link BufferInfo} and the caller must not modify them.
* *
* <p>The default value is {@code true}. * <p>The default value is {@code true}.
*/ */
@ -142,6 +142,7 @@ public final class FragmentedMp4Muxer implements Muxer {
private final FragmentedMp4Writer fragmentedMp4Writer; private final FragmentedMp4Writer fragmentedMp4Writer;
private final MetadataCollector metadataCollector; private final MetadataCollector metadataCollector;
private final SparseArray<Track> trackIdToTrack;
private FragmentedMp4Muxer( private FragmentedMp4Muxer(
OutputStream outputStream, long fragmentDurationMs, boolean sampleCopyEnabled) { OutputStream outputStream, long fragmentDurationMs, boolean sampleCopyEnabled) {
@ -154,11 +155,14 @@ public final class FragmentedMp4Muxer implements Muxer {
AnnexBToAvccConverter.DEFAULT, AnnexBToAvccConverter.DEFAULT,
fragmentDurationMs, fragmentDurationMs,
sampleCopyEnabled); sampleCopyEnabled);
trackIdToTrack = new SparseArray<>();
} }
@Override @Override
public TrackToken addTrack(Format format) { public int addTrack(Format format) {
return fragmentedMp4Writer.addTrack(/* sortKey= */ 1, format); Track track = fragmentedMp4Writer.addTrack(/* sortKey= */ 1, format);
trackIdToTrack.append(track.id, track);
return track.id;
} }
/** /**
@ -171,7 +175,7 @@ public final class FragmentedMp4Muxer implements Muxer {
* *
* <p>Note: Out of order B-frames are currently not supported. * <p>Note: Out of order B-frames are currently not supported.
* *
* @param trackToken The {@link TrackToken} for which this sample is being written. * @param trackId The track id for which this sample is being written.
* @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link * @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link
* Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled. Otherwise, the * Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled. Otherwise, the
* position of the buffer is updated but the caller retains ownership. * position of the buffer is updated but the caller retains ownership.
@ -179,10 +183,10 @@ public final class FragmentedMp4Muxer implements Muxer {
* @throws MuxerException If there is any error while writing data to the disk. * @throws MuxerException If there is any error while writing data to the disk.
*/ */
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
try { try {
fragmentedMp4Writer.writeSampleData(trackToken, byteBuffer, bufferInfo); fragmentedMp4Writer.writeSampleData(trackIdToTrack.get(trackId), byteBuffer, bufferInfo);
} catch (IOException e) { } catch (IOException e) {
throw new MuxerException( throw new MuxerException(
"Failed to write sample for presentationTimeUs=" "Failed to write sample for presentationTimeUs="

View File

@ -33,7 +33,6 @@ import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.muxer.Muxer.TrackToken;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -122,6 +121,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean headerCreated; private boolean headerCreated;
private long minInputPresentationTimeUs; private long minInputPresentationTimeUs;
private long maxTrackDurationUs; private long maxTrackDurationUs;
private int nextTrackId;
/** /**
* Creates an instance. * Creates an instance.
@ -153,8 +153,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
currentFragmentSequenceNumber = 1; currentFragmentSequenceNumber = 1;
} }
public TrackToken addTrack(int sortKey, Format format) { public Track addTrack(int sortKey, Format format) {
Track track = new Track(format, sampleCopyEnabled); Track track = new Track(nextTrackId++, format, sampleCopyEnabled);
tracks.add(track); tracks.add(track);
if (MimeTypes.isVideo(format.sampleMimeType)) { if (MimeTypes.isVideo(format.sampleMimeType)) {
videoTrack = track; videoTrack = track;
@ -162,15 +162,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return track; return track;
} }
public void writeSampleData( public void writeSampleData(Track track, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
TrackToken token, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
throws IOException { throws IOException {
checkArgument(token instanceof Track);
if (!headerCreated) { if (!headerCreated) {
createHeader(); createHeader();
headerCreated = true; headerCreated = true;
} }
Track track = (Track) token;
if (shouldFlushPendingSamples(track, bufferInfo)) { if (shouldFlushPendingSamples(track, bufferInfo)) {
createFragment(); createFragment();
} }

View File

@ -89,9 +89,9 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
* <p>To create an MP4 container file, the caller must: * <p>To create an MP4 container file, the caller must:
* *
* <ul> * <ul>
* <li>Add tracks using {@link #addTrack(int, Format)} which will return a {@link TrackToken}. * <li>Add tracks using {@link #addTrack(int, Format)} which will return a track id.
* <li>Use the associated {@link TrackToken} when {@linkplain #writeSampleData(TrackToken, * <li>Use the associated track id when {@linkplain #writeSampleData(int, ByteBuffer, BufferInfo)
* ByteBuffer, BufferInfo) writing samples} for that track. * writing samples} for that track.
* <li>{@link #close} the muxer when all data has been written. * <li>{@link #close} the muxer when all data has been written.
* </ul> * </ul>
* *
@ -100,8 +100,8 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
* <ul> * <ul>
* <li>Tracks can be added at any point, even after writing some samples to other tracks. * <li>Tracks can be added at any point, even after writing some samples to other tracks.
* <li>The caller is responsible for ensuring that samples of different track types are well * <li>The caller is responsible for ensuring that samples of different track types are well
* interleaved by calling {@link #writeSampleData(TrackToken, ByteBuffer, BufferInfo)} in an * interleaved by calling {@link #writeSampleData(int, ByteBuffer, BufferInfo)} in an order
* order that interleaves samples from different tracks. * that interleaves samples from different tracks.
* <li>When writing a file, if an error occurs and the muxer is not closed, then the output MP4 * <li>When writing a file, if an error occurs and the muxer is not closed, then the output MP4
* file may still have some partial data. * file may still have some partial data.
* </ul> * </ul>
@ -252,10 +252,10 @@ public final class Mp4Muxer implements Muxer {
/** /**
* Sets whether to enable the sample copy. * Sets whether to enable the sample copy.
* *
* <p>If the sample copy is enabled, {@link #writeSampleData(TrackToken, ByteBuffer, * <p>If the sample copy is enabled, {@link #writeSampleData(int, ByteBuffer, BufferInfo)}
* BufferInfo)} copies the input {@link ByteBuffer} and {@link BufferInfo} before it returns, so * copies the input {@link ByteBuffer} and {@link BufferInfo} before it returns, so it is safe
* it is safe to reuse them immediately. Otherwise, the muxer takes ownership of the {@link * to reuse them immediately. Otherwise, the muxer takes ownership of the {@link ByteBuffer} and
* ByteBuffer} and the {@link BufferInfo} and the caller must not modify them. * the {@link BufferInfo} and the caller must not modify them.
* *
* <p>The default value is {@code true}. * <p>The default value is {@code true}.
*/ */
@ -269,7 +269,7 @@ public final class Mp4Muxer implements Muxer {
* Sets whether to enable sample batching. * Sets whether to enable sample batching.
* *
* <p>If sample batching is enabled, samples are written in batches for each track, otherwise * <p>If sample batching is enabled, samples are written in batches for each track, otherwise
* samples are written as they {@linkplain #writeSampleData(TrackToken, ByteBuffer, BufferInfo) * samples are written as they {@linkplain #writeSampleData(int, ByteBuffer, BufferInfo)
* arrive}. * arrive}.
* *
* <p>The default value is {@code true}. * <p>The default value is {@code true}.
@ -348,6 +348,7 @@ public final class Mp4Muxer implements Muxer {
@Nullable private final Mp4AtFileParameters mp4AtFileParameters; @Nullable private final Mp4AtFileParameters mp4AtFileParameters;
private final MetadataCollector metadataCollector; private final MetadataCollector metadataCollector;
private final Mp4Writer mp4Writer; private final Mp4Writer mp4Writer;
private final List<Track> trackIdToTrack;
private final List<Track> auxiliaryTracks; private final List<Track> auxiliaryTracks;
@Nullable private String cacheFilePath; @Nullable private String cacheFilePath;
@ -355,6 +356,8 @@ public final class Mp4Muxer implements Muxer {
@Nullable private MetadataCollector auxiliaryTracksMetadataCollector; @Nullable private MetadataCollector auxiliaryTracksMetadataCollector;
@Nullable private Mp4Writer auxiliaryTracksMp4Writer; @Nullable private Mp4Writer auxiliaryTracksMp4Writer;
private int nextTrackId;
private Mp4Muxer( private Mp4Muxer(
FileOutputStream outputStream, FileOutputStream outputStream,
@LastSampleDurationBehavior int lastFrameDurationBehavior, @LastSampleDurationBehavior int lastFrameDurationBehavior,
@ -383,6 +386,7 @@ public final class Mp4Muxer implements Muxer {
sampleCopyEnabled, sampleCopyEnabled,
sampleBatchingEnabled, sampleBatchingEnabled,
attemptStreamableOutputEnabled); attemptStreamableOutputEnabled);
trackIdToTrack = new ArrayList<>();
auxiliaryTracks = new ArrayList<>(); auxiliaryTracks = new ArrayList<>();
} }
@ -395,11 +399,12 @@ public final class Mp4Muxer implements Muxer {
* <p>The order of tracks remains same in which they are added. * <p>The order of tracks remains same in which they are added.
* *
* @param format The {@link Format} for the track. * @param format The {@link Format} for the track.
* @return A unique {@link TrackToken}. It should be used in {@link #writeSampleData}. * @return A unique track id. The track id is non-negative. It should be used in {@link
* #writeSampleData}.
* @throws MuxerException If an error occurs while adding track. * @throws MuxerException If an error occurs while adding track.
*/ */
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
return addTrack(/* sortKey= */ 1, format); return addTrack(/* sortKey= */ 1, format);
} }
@ -410,31 +415,37 @@ public final class Mp4Muxer implements Muxer {
* other tracks. * other tracks.
* *
* <p>The final order of tracks is determined by the provided sort key. Tracks with a lower sort * <p>The final order of tracks is determined by the provided sort key. Tracks with a lower sort
* key will always have a lower track id than tracks with a higher sort key. Ordering between * key will be written before tracks with a higher sort key. Ordering between tracks with the same
* tracks with the same sort key is not specified. * sort key is not specified.
* *
* @param sortKey The key used for sorting the track list. * @param sortKey The key used for sorting the track list.
* @param format The {@link Format} for the track. * @param format The {@link Format} for the track.
* @return A unique {@link TrackToken}. It should be used in {@link #writeSampleData}. * @return A unique track id. The track id is non-negative. It should be used in {@link
* #writeSampleData}.
* @throws MuxerException If an error occurs while adding track. * @throws MuxerException If an error occurs while adding track.
*/ */
public TrackToken addTrack(int sortKey, Format format) throws MuxerException { public int addTrack(int sortKey, Format format) throws MuxerException {
Track track;
if (outputFileFormat == FILE_FORMAT_MP4_WITH_AUXILIARY_TRACKS_EXTENSION if (outputFileFormat == FILE_FORMAT_MP4_WITH_AUXILIARY_TRACKS_EXTENSION
&& isAuxiliaryTrack(format)) { && isAuxiliaryTrack(format)) {
if (checkNotNull(mp4AtFileParameters).shouldInterleaveSamples) { if (checkNotNull(mp4AtFileParameters).shouldInterleaveSamples) {
// Auxiliary tracks are handled by the primary Mp4Writer. // Auxiliary tracks are handled by the primary Mp4Writer.
return mp4Writer.addAuxiliaryTrack(sortKey, format); track = mp4Writer.addAuxiliaryTrack(nextTrackId++, sortKey, format);
} else {
// Auxiliary tracks are handled by the auxiliary tracks Mp4Writer.
try {
ensureSetupForAuxiliaryTracks();
} catch (FileNotFoundException e) {
throw new MuxerException("Cache file not found", e);
}
track = auxiliaryTracksMp4Writer.addTrack(nextTrackId++, sortKey, format);
auxiliaryTracks.add(track);
} }
try { } else {
ensureSetupForAuxiliaryTracks(); track = mp4Writer.addTrack(nextTrackId++, sortKey, format);
} catch (FileNotFoundException e) {
throw new MuxerException("Cache file not found", e);
}
Track track = auxiliaryTracksMp4Writer.addTrack(sortKey, format);
auxiliaryTracks.add(track);
return track;
} }
return mp4Writer.addTrack(sortKey, format); trackIdToTrack.add(track);
return track.id;
} }
/** /**
@ -442,13 +453,13 @@ public final class Mp4Muxer implements Muxer {
* *
* <p>When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean) * <p>When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean)
* enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified * enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified
* after calling the {@link #writeSampleData(TrackToken, ByteBuffer, BufferInfo)} method, unless * after calling the {@link #writeSampleData(int, ByteBuffer, BufferInfo)} method, unless sample
* sample copying is also {@linkplain Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}. * copying is also {@linkplain Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}. This
* This ensures data integrity within the batch. If sample copying is {@linkplain * ensures data integrity within the batch. If sample copying is {@linkplain
* Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}, it's safe to modify the data after * Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}, it's safe to modify the data after
* the method returns, as the muxer internally creates a sample copy. * the method returns, as the muxer internally creates a sample copy.
* *
* @param trackToken The {@link TrackToken} for which this sample is being written. * @param trackId The track id for which this sample is being written.
* @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link * @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link
* Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled. Otherwise, the * Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled. Otherwise, the
* position of the buffer is updated but the caller retains ownership. * position of the buffer is updated but the caller retains ownership.
@ -456,12 +467,11 @@ public final class Mp4Muxer implements Muxer {
* @throws MuxerException If an error occurs while writing data to the output file. * @throws MuxerException If an error occurs while writing data to the output file.
*/ */
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
checkState(trackToken instanceof Track); Track track = trackIdToTrack.get(trackId);
Track track = (Track) trackToken;
try { try {
if (auxiliaryTracks.contains(trackToken)) { if (auxiliaryTracks.contains(track)) {
checkNotNull(auxiliaryTracksMp4Writer).writeSampleData(track, byteBuffer, bufferInfo); checkNotNull(auxiliaryTracksMp4Writer).writeSampleData(track, byteBuffer, bufferInfo);
} else { } else {
mp4Writer.writeSampleData(track, byteBuffer, bufferInfo); mp4Writer.writeSampleData(track, byteBuffer, bufferInfo);

View File

@ -111,12 +111,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Adds a track of the given {@link Format}. * Adds a track of the given {@link Format}.
* *
* @param trackId The track id for the track.
* @param sortKey The key used for sorting the track list. * @param sortKey The key used for sorting the track list.
* @param format The {@link Format} for the track. * @param format The {@link Format} for the track.
* @return A unique {@link Track}. It should be used in {@link #writeSampleData}. * @return A unique {@link Track}. It should be used in {@link #writeSampleData}.
*/ */
public Track addTrack(int sortKey, Format format) { public Track addTrack(int trackId, int sortKey, Format format) {
Track track = new Track(format, sortKey, sampleCopyEnabled); Track track = new Track(trackId, format, sortKey, sampleCopyEnabled);
tracks.add(track); tracks.add(track);
Collections.sort(tracks, (a, b) -> Integer.compare(a.sortKey, b.sortKey)); Collections.sort(tracks, (a, b) -> Integer.compare(a.sortKey, b.sortKey));
return track; return track;
@ -127,12 +128,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
* *
* <p>See {@link MuxerUtil#isAuxiliaryTrack(Format)} for auxiliary tracks. * <p>See {@link MuxerUtil#isAuxiliaryTrack(Format)} for auxiliary tracks.
* *
* @param trackId The track id for the track.
* @param sortKey The key used for sorting the track list. * @param sortKey The key used for sorting the track list.
* @param format The {@link Format} for the track. * @param format The {@link Format} for the track.
* @return A unique {@link Track}. It should be used in {@link #writeSampleData}. * @return A unique {@link Track}. It should be used in {@link #writeSampleData}.
*/ */
public Track addAuxiliaryTrack(int sortKey, Format format) { public Track addAuxiliaryTrack(int trackId, int sortKey, Format format) {
Track track = new Track(format, sortKey, sampleCopyEnabled); Track track = new Track(trackId, format, sortKey, sampleCopyEnabled);
auxiliaryTracks.add(track); auxiliaryTracks.add(track);
Collections.sort(auxiliaryTracks, (a, b) -> Integer.compare(a.sortKey, b.sortKey)); Collections.sort(auxiliaryTracks, (a, b) -> Integer.compare(a.sortKey, b.sortKey));
return track; return track;

View File

@ -44,29 +44,24 @@ public interface Muxer {
ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType); ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType);
} }
/** A token representing an added track. */
interface TrackToken {}
/** /**
* Adds a track of the given media format. * Adds a track of the given media format.
* *
* @param format The {@link Format} of the track. * @param format The {@link Format} of the track.
* @return The {@link TrackToken} for this track, which should be passed to {@link * @return A track id for this track, which should be passed to {@link #writeSampleData}.
* #writeSampleData}.
* @throws MuxerException If the muxer encounters a problem while adding the track. * @throws MuxerException If the muxer encounters a problem while adding the track.
*/ */
TrackToken addTrack(Format format) throws MuxerException; int addTrack(Format format) throws MuxerException;
/** /**
* Writes encoded sample data. * Writes encoded sample data.
* *
* @param trackToken The {@link TrackToken} of the track, previously returned by {@link * @param trackId The track id, previously returned by {@link #addTrack(Format)}.
* #addTrack(Format)}.
* @param byteBuffer A buffer containing the sample data to write to the container. * @param byteBuffer A buffer containing the sample data to write to the container.
* @param bufferInfo The {@link BufferInfo} of the sample. * @param bufferInfo The {@link BufferInfo} of the sample.
* @throws MuxerException If the muxer fails to write the sample. * @throws MuxerException If the muxer fails to write the sample.
*/ */
void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException; throws MuxerException;
/** Adds {@linkplain Metadata.Entry metadata} about the output file. */ /** Adds {@linkplain Metadata.Entry metadata} about the output file. */

View File

@ -22,7 +22,6 @@ import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.muxer.Muxer.TrackToken;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,7 +29,8 @@ import java.util.Deque;
import java.util.List; import java.util.List;
/** Represents a single track (audio, video, metadata etc.). */ /** Represents a single track (audio, video, metadata etc.). */
/* package */ final class Track implements TrackToken { /* package */ final class Track {
public final int id;
public final Format format; public final Format format;
public final int sortKey; public final int sortKey;
public final List<BufferInfo> writtenSamples; public final List<BufferInfo> writtenSamples;
@ -44,18 +44,20 @@ import java.util.List;
private final boolean sampleCopyEnabled; private final boolean sampleCopyEnabled;
/** Creates an instance with {@code sortKey} set to 1. */ /** Creates an instance with {@code sortKey} set to 1. */
public Track(Format format, boolean sampleCopyEnabled) { public Track(int trackId, Format format, boolean sampleCopyEnabled) {
this(format, /* sortKey= */ 1, sampleCopyEnabled); this(trackId, format, /* sortKey= */ 1, sampleCopyEnabled);
} }
/** /**
* Creates an instance. * Creates an instance.
* *
* @param trackId A unique id for the track.
* @param format The {@link Format} for the track. * @param format The {@link Format} for the track.
* @param sortKey The key used for sorting the track list. * @param sortKey The key used for sorting the track list.
* @param sampleCopyEnabled Whether sample copying is enabled. * @param sampleCopyEnabled Whether sample copying is enabled.
*/ */
public Track(Format format, int sortKey, boolean sampleCopyEnabled) { public Track(int trackId, Format format, int sortKey, boolean sampleCopyEnabled) {
id = trackId;
this.format = format; this.format = format;
this.sortKey = sortKey; this.sortKey = sortKey;
this.sampleCopyEnabled = sampleCopyEnabled; this.sampleCopyEnabled = sampleCopyEnabled;

View File

@ -35,7 +35,6 @@ import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.XmpData; import androidx.media3.container.XmpData;
import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.muxer.Muxer.TrackToken;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.DumpableMp4Box; import androidx.media3.test.utils.DumpableMp4Box;
import androidx.media3.test.utils.FakeExtractorOutput; import androidx.media3.test.utils.FakeExtractorOutput;
@ -83,8 +82,8 @@ public class Mp4MuxerEndToEndTest {
/* value= */ Util.getUtf8Bytes("StringValue"), /* value= */ Util.getUtf8Bytes("StringValue"),
MdtaMetadataEntry.TYPE_INDICATOR_STRING)); MdtaMetadataEntry.TYPE_INDICATOR_STRING));
muxer.addMetadataEntry(new XmpData(xmpBytes)); muxer.addMetadataEntry(new XmpData(xmpBytes));
TrackToken token = muxer.addTrack(FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -136,12 +135,12 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
// Add same track again but with different samples. // Add same track again but with different samples.
TrackToken track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second); mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second);
mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second); mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second);
} finally { } finally {
@ -180,12 +179,12 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
// Add same track again but with different samples. // Add same track again but with different samples.
TrackToken track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second); mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second);
mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second); mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second);
} finally { } finally {
@ -218,7 +217,7 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second); mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second);
@ -257,7 +256,7 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 2_000L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 2_000L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second); mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second);
@ -301,12 +300,12 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 23_001_033_192L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 23_001_033_192L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second); mp4Muxer.writeSampleData(track1, track1Sample3.first, track1Sample3.second);
mp4Muxer.writeSampleData(track1, track1Sample4.first, track1Sample4.second); mp4Muxer.writeSampleData(track1, track1Sample4.first, track1Sample4.second);
TrackToken track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track2 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second); mp4Muxer.writeSampleData(track2, track2Sample1.first, track2Sample1.second);
mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second); mp4Muxer.writeSampleData(track2, track2Sample2.first, track2Sample2.second);
mp4Muxer.writeSampleData(track2, track2Sample3.first, track2Sample3.second); mp4Muxer.writeSampleData(track2, track2Sample3.first, track2Sample3.second);
@ -337,7 +336,7 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
try { try {
TrackToken track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track1 = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second); mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second); mp4Muxer.writeSampleData(track1, track1Sample2.first, track1Sample2.second);
// Add same track again but without any samples. // Add same track again but without any samples.
@ -365,11 +364,11 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
for (int i = 0; i < 50_000; i++) { for (int i = 0; i < 50_000; i++) {
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo = Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} }
} finally { } finally {
muxer.close(); muxer.close();
@ -397,11 +396,11 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
for (int i = 0; i < 1_000; i++) { for (int i = 0; i < 1_000; i++) {
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo = Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} }
} finally { } finally {
muxer.close(); muxer.close();
@ -448,24 +447,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -490,24 +489,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -540,24 +539,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -591,24 +590,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -642,24 +641,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -693,24 +692,24 @@ public class Mp4MuxerEndToEndTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken primaryVideoTrackToken = muxer.addTrack(FAKE_VIDEO_FORMAT); int primaryVideoTrackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
TrackToken sharpVideoTrackToken = int sharpVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_ORIGINAL)
.build()); .build());
TrackToken depthLinearVideoTrackToken = int depthLinearVideoTrackId =
muxer.addTrack( muxer.addTrack(
FAKE_VIDEO_FORMAT FAKE_VIDEO_FORMAT
.buildUpon() .buildUpon()
.setRoleFlags(C.ROLE_FLAG_AUXILIARY) .setRoleFlags(C.ROLE_FLAG_AUXILIARY)
.setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR) .setAuxiliaryTrackType(C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR)
.build()); .build());
writeFakeSamples(muxer, primaryVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, primaryVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, sharpVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, sharpVideoTrackId, /* sampleCount= */ 5);
writeFakeSamples(muxer, depthLinearVideoTrackToken, /* sampleCount= */ 5); writeFakeSamples(muxer, depthLinearVideoTrackId, /* sampleCount= */ 5);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -751,7 +750,7 @@ public class Mp4MuxerEndToEndTest {
long expectedDurationUs = 1_000L; long expectedDurationUs = 1_000L;
try { try {
TrackToken track = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track, sample1.first, sample1.second); mp4Muxer.writeSampleData(track, sample1.first, sample1.second);
mp4Muxer.writeSampleData(track, sample2.first, sample2.second); mp4Muxer.writeSampleData(track, sample2.first, sample2.second);
mp4Muxer.writeSampleData(track, sample3.first, sample3.second); mp4Muxer.writeSampleData(track, sample3.first, sample3.second);
@ -798,7 +797,7 @@ public class Mp4MuxerEndToEndTest {
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L);
try { try {
TrackToken track = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT); int track = mp4Muxer.addTrack(FAKE_VIDEO_FORMAT);
mp4Muxer.writeSampleData(track, sample1.first, sample1.second); mp4Muxer.writeSampleData(track, sample1.first, sample1.second);
mp4Muxer.writeSampleData(track, sample2.first, sample2.second); mp4Muxer.writeSampleData(track, sample2.first, sample2.second);
mp4Muxer.writeSampleData(track, sample3.first, sample3.second); mp4Muxer.writeSampleData(track, sample3.first, sample3.second);
@ -814,12 +813,12 @@ public class Mp4MuxerEndToEndTest {
assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(400L); assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(400L);
} }
private static void writeFakeSamples(Mp4Muxer muxer, TrackToken trackToken, int sampleCount) private static void writeFakeSamples(Mp4Muxer muxer, int trackId, int sampleCount)
throws MuxerException { throws MuxerException {
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo = Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i); getFakeSampleAndSampleInfo(/* presentationTimeUs= */ i);
muxer.writeSampleData(trackToken, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} }
} }
} }

View File

@ -33,7 +33,6 @@ import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.XmpData; import androidx.media3.container.XmpData;
import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.muxer.Muxer.TrackToken;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.DumpableMp4Box; import androidx.media3.test.utils.DumpableMp4Box;
import androidx.media3.test.utils.FakeExtractorOutput; import androidx.media3.test.utils.FakeExtractorOutput;
@ -66,8 +65,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -92,8 +91,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 90)); muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 90));
} finally { } finally {
@ -120,8 +119,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 180)); muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 180));
} finally { } finally {
@ -148,8 +147,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 270)); muxer.addMetadataEntry(new Mp4OrientationData(/* orientation= */ 270));
} finally { } finally {
@ -176,8 +175,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
muxer.addMetadataEntry(new Mp4LocationData(/* latitude= */ 33.0f, /* longitude= */ -120f)); muxer.addMetadataEntry(new Mp4LocationData(/* latitude= */ 33.0f, /* longitude= */ -120f));
} finally { } finally {
muxer.close(); muxer.close();
@ -203,8 +202,8 @@ public class Mp4MuxerMetadataTest {
new Mp4TimestampData( new Mp4TimestampData(
/* creationTimestampSeconds= */ 1_000_000L, /* creationTimestampSeconds= */ 1_000_000L,
/* modificationTimestampSeconds= */ 5_000_000L)); /* modificationTimestampSeconds= */ 5_000_000L));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -233,8 +232,8 @@ public class Mp4MuxerMetadataTest {
muxer.addMetadataEntry( muxer.addMetadataEntry(
new MdtaMetadataEntry( new MdtaMetadataEntry(
KEY_ANDROID_CAPTURE_FPS, Util.toByteArray(captureFps), TYPE_INDICATOR_FLOAT32)); KEY_ANDROID_CAPTURE_FPS, Util.toByteArray(captureFps), TYPE_INDICATOR_FLOAT32));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -262,8 +261,8 @@ public class Mp4MuxerMetadataTest {
muxer.addMetadataEntry( muxer.addMetadataEntry(
new MdtaMetadataEntry( new MdtaMetadataEntry(
"SomeStringKey", Util.getUtf8Bytes("Some Random String"), TYPE_INDICATOR_STRING)); "SomeStringKey", Util.getUtf8Bytes("Some Random String"), TYPE_INDICATOR_STRING));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -289,10 +288,10 @@ public class Mp4MuxerMetadataTest {
muxer.addMetadataEntry( muxer.addMetadataEntry(
new MdtaMetadataEntry(metadataKey, metadataValue, TYPE_INDICATOR_STRING)); new MdtaMetadataEntry(metadataKey, metadataValue, TYPE_INDICATOR_STRING));
} }
TrackToken token = muxer.addTrack(FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
try { try {
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
assertThat(sampleAndSampleInfo.first.remaining()).isEqualTo(0); assertThat(sampleAndSampleInfo.first.remaining()).isEqualTo(0);
} finally { } finally {
muxer.close(); muxer.close();
@ -313,8 +312,8 @@ public class Mp4MuxerMetadataTest {
muxer.addMetadataEntry( muxer.addMetadataEntry(
new MdtaMetadataEntry( new MdtaMetadataEntry(
"SomeStringKey", Util.toByteArray(floatValue), TYPE_INDICATOR_FLOAT32)); "SomeStringKey", Util.toByteArray(floatValue), TYPE_INDICATOR_FLOAT32));
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }
@ -342,8 +341,8 @@ public class Mp4MuxerMetadataTest {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA); byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
muxer.addMetadataEntry(new XmpData(xmpBytes)); muxer.addMetadataEntry(new XmpData(xmpBytes));
TrackToken token = muxer.addTrack(0, FAKE_VIDEO_FORMAT); int trackId = muxer.addTrack(0, FAKE_VIDEO_FORMAT);
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second); muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
} finally { } finally {
muxer.close(); muxer.close();
} }

View File

@ -24,7 +24,6 @@ import static org.junit.Assume.assumeFalse;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.Format; import androidx.media3.common.Format;
@ -448,7 +447,7 @@ public class TransformerPauseResumeTest {
private final FrameBlockingMuxer.Listener listener; private final FrameBlockingMuxer.Listener listener;
private boolean notifiedListener; private boolean notifiedListener;
@Nullable private TrackToken videoTrackToken; private int videoTrackId;
private FrameBlockingMuxer(Muxer wrappedMuxer, FrameBlockingMuxer.Listener listener) { private FrameBlockingMuxer(Muxer wrappedMuxer, FrameBlockingMuxer.Listener listener) {
this.wrappedMuxer = wrappedMuxer; this.wrappedMuxer = wrappedMuxer;
@ -456,18 +455,18 @@ public class TransformerPauseResumeTest {
} }
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
TrackToken trackToken = wrappedMuxer.addTrack(format); int trackId = wrappedMuxer.addTrack(format);
if (MimeTypes.isVideo(format.sampleMimeType)) { if (MimeTypes.isVideo(format.sampleMimeType)) {
videoTrackToken = trackToken; videoTrackId = trackId;
} }
return trackToken; return trackId;
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer data, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
if (trackToken == videoTrackToken if (trackId == videoTrackId
&& bufferInfo.presentationTimeUs >= DEFAULT_PRESENTATION_TIME_US_TO_BLOCK_FRAME) { && bufferInfo.presentationTimeUs >= DEFAULT_PRESENTATION_TIME_US_TO_BLOCK_FRAME) {
if (!notifiedListener) { if (!notifiedListener) {
listener.onFrameBlocked(); listener.onFrameBlocked();
@ -475,7 +474,7 @@ public class TransformerPauseResumeTest {
} }
return; return;
} }
wrappedMuxer.writeSampleData(trackToken, data, bufferInfo); wrappedMuxer.writeSampleData(trackId, data, bufferInfo);
} }
@Override @Override

View File

@ -84,14 +84,14 @@ public final class DefaultMuxer implements Muxer {
} }
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
return muxer.addTrack(format); return muxer.addTrack(format);
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
muxer.writeSampleData(trackToken, byteBuffer, bufferInfo); muxer.writeSampleData(trackId, byteBuffer, bufferInfo);
} }
@Override @Override

View File

@ -24,7 +24,7 @@ import android.annotation.SuppressLint;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.MediaMuxer; import android.media.MediaMuxer;
import androidx.annotation.Nullable; import android.util.SparseArray;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
@ -40,9 +40,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
/** {@link Muxer} implementation that uses a {@link MediaMuxer}. */ /** {@link Muxer} implementation that uses a {@link MediaMuxer}. */
/* package */ final class FrameworkMuxer implements Muxer { /* package */ final class FrameworkMuxer implements Muxer {
@ -105,13 +103,14 @@ import java.util.Map;
private static final ImmutableList<String> SUPPORTED_AUDIO_SAMPLE_MIME_TYPES = private static final ImmutableList<String> SUPPORTED_AUDIO_SAMPLE_MIME_TYPES =
ImmutableList.of(MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB); ImmutableList.of(MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
private static final String TAG = "FrameworkMuxer"; private static final String TAG = "FrameworkMuxer";
private static final int TRACK_ID_UNSET = -1;
private final MediaMuxer mediaMuxer; private final MediaMuxer mediaMuxer;
private final long videoDurationUs; private final long videoDurationUs;
private final Map<TrackToken, Long> trackTokenToLastPresentationTimeUs; private final SparseArray<Long> trackIdToLastPresentationTimeUs;
private final Map<TrackToken, Long> trackTokenToPresentationTimeOffsetUs; private final SparseArray<Long> trackIdToPresentationTimeOffsetUs;
@Nullable private TrackToken videoTrackToken; private int videoTrackId;
private boolean isStarted; private boolean isStarted;
private boolean isReleased; private boolean isReleased;
@ -119,12 +118,13 @@ import java.util.Map;
private FrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationUs) { private FrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationUs) {
this.mediaMuxer = mediaMuxer; this.mediaMuxer = mediaMuxer;
this.videoDurationUs = videoDurationUs; this.videoDurationUs = videoDurationUs;
trackTokenToLastPresentationTimeUs = new HashMap<>(); trackIdToLastPresentationTimeUs = new SparseArray<>();
trackTokenToPresentationTimeOffsetUs = new HashMap<>(); trackIdToPresentationTimeOffsetUs = new SparseArray<>();
videoTrackId = TRACK_ID_UNSET;
} }
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
String sampleMimeType = checkNotNull(format.sampleMimeType); String sampleMimeType = checkNotNull(format.sampleMimeType);
MediaFormat mediaFormat; MediaFormat mediaFormat;
boolean isVideo = MimeTypes.isVideo(sampleMimeType); boolean isVideo = MimeTypes.isVideo(sampleMimeType);
@ -150,20 +150,19 @@ import java.util.Map;
throw new MuxerException("Failed to add track with format=" + format, e); throw new MuxerException("Failed to add track with format=" + format, e);
} }
TrackToken trackToken = new TrackTokenImpl(trackIndex);
if (isVideo) { if (isVideo) {
videoTrackToken = trackToken; videoTrackId = trackIndex;
} }
return trackToken; return trackIndex;
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer data, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
long presentationTimeUs = bufferInfo.presentationTimeUs; long presentationTimeUs = bufferInfo.presentationTimeUs;
if (videoDurationUs != C.TIME_UNSET if (videoDurationUs != C.TIME_UNSET
&& trackToken == videoTrackToken && trackId == videoTrackId
&& presentationTimeUs > videoDurationUs) { && presentationTimeUs > videoDurationUs) {
Log.w( Log.w(
TAG, TAG,
@ -176,20 +175,18 @@ import java.util.Map;
} }
if (!isStarted) { if (!isStarted) {
if (Util.SDK_INT < 30 && presentationTimeUs < 0) { if (Util.SDK_INT < 30 && presentationTimeUs < 0) {
trackTokenToPresentationTimeOffsetUs.put(trackToken, -presentationTimeUs); trackIdToPresentationTimeOffsetUs.put(trackId, -presentationTimeUs);
} }
startMuxer(); startMuxer();
} }
long presentationTimeOffsetUs = long presentationTimeOffsetUs =
trackTokenToPresentationTimeOffsetUs.containsKey(trackToken) trackIdToPresentationTimeOffsetUs.get(trackId, /* valueIfKeyNotFound= */ 0L);
? trackTokenToPresentationTimeOffsetUs.get(trackToken)
: 0;
presentationTimeUs += presentationTimeOffsetUs; presentationTimeUs += presentationTimeOffsetUs;
long lastSamplePresentationTimeUs = long lastSamplePresentationTimeUs =
trackTokenToLastPresentationTimeUs.containsKey(trackToken) Util.contains(trackIdToLastPresentationTimeUs, trackId)
? trackTokenToLastPresentationTimeUs.get(trackToken) ? trackIdToLastPresentationTimeUs.get(trackId)
: 0; : 0;
// writeSampleData blocks on old API versions, so check here to avoid calling the method. // writeSampleData blocks on old API versions, so check here to avoid calling the method.
checkState( checkState(
@ -199,7 +196,7 @@ import java.util.Map;
+ " < " + " < "
+ lastSamplePresentationTimeUs + lastSamplePresentationTimeUs
+ ") unsupported on this API version"); + ") unsupported on this API version");
trackTokenToLastPresentationTimeUs.put(trackToken, presentationTimeUs); trackIdToLastPresentationTimeUs.put(trackId, presentationTimeUs);
checkState( checkState(
presentationTimeOffsetUs == 0 || presentationTimeUs >= 0, presentationTimeOffsetUs == 0 || presentationTimeUs >= 0,
@ -212,8 +209,8 @@ import java.util.Map;
bufferInfo.set(bufferInfo.offset, bufferInfo.size, presentationTimeUs, bufferInfo.flags); bufferInfo.set(bufferInfo.offset, bufferInfo.size, presentationTimeUs, bufferInfo.flags);
try { try {
checkState(trackToken instanceof TrackTokenImpl);
mediaMuxer.writeSampleData(((TrackTokenImpl) trackToken).trackIndex, data, bufferInfo); mediaMuxer.writeSampleData(trackId, data, bufferInfo);
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new MuxerException( throw new MuxerException(
"Failed to write sample for presentationTimeUs=" "Failed to write sample for presentationTimeUs="
@ -244,14 +241,14 @@ import java.util.Map;
startMuxer(); startMuxer();
} }
if (videoDurationUs != C.TIME_UNSET && videoTrackToken != null) { if (videoDurationUs != C.TIME_UNSET && videoTrackId != TRACK_ID_UNSET) {
BufferInfo bufferInfo = new BufferInfo(); BufferInfo bufferInfo = new BufferInfo();
bufferInfo.set( bufferInfo.set(
/* newOffset= */ 0, /* newOffset= */ 0,
/* newSize= */ 0, /* newSize= */ 0,
videoDurationUs, videoDurationUs,
TransformerUtil.getMediaCodecFlags(C.BUFFER_FLAG_END_OF_STREAM)); TransformerUtil.getMediaCodecFlags(C.BUFFER_FLAG_END_OF_STREAM));
writeSampleData(checkNotNull(videoTrackToken), ByteBuffer.allocateDirect(0), bufferInfo); writeSampleData(videoTrackId, ByteBuffer.allocateDirect(0), bufferInfo);
} }
isStarted = false; isStarted = false;
@ -314,12 +311,4 @@ import java.util.Map;
} }
return supportedMimeTypes.build(); return supportedMimeTypes.build();
} }
private static class TrackTokenImpl implements TrackToken {
public final int trackIndex;
public TrackTokenImpl(int trackIndex) {
this.trackIndex = trackIndex;
}
}
} }

View File

@ -15,7 +15,6 @@
*/ */
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.muxer.Mp4Muxer.LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS; import static androidx.media3.muxer.Mp4Muxer.LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -207,13 +206,14 @@ public final class InAppMuxer implements Muxer {
} }
private static final String TAG = "InAppMuxer"; private static final String TAG = "InAppMuxer";
private static final int TRACK_ID_UNSET = -1;
private final Muxer muxer; private final Muxer muxer;
@Nullable private final MetadataProvider metadataProvider; @Nullable private final MetadataProvider metadataProvider;
private final long videoDurationUs; private final long videoDurationUs;
private final Set<Metadata.Entry> metadataEntries; private final Set<Metadata.Entry> metadataEntries;
@Nullable private TrackToken videoTrackToken; private int videoTrackId;
private InAppMuxer( private InAppMuxer(
Muxer muxer, @Nullable MetadataProvider metadataProvider, long videoDurationUs) { Muxer muxer, @Nullable MetadataProvider metadataProvider, long videoDurationUs) {
@ -221,23 +221,24 @@ public final class InAppMuxer implements Muxer {
this.metadataProvider = metadataProvider; this.metadataProvider = metadataProvider;
this.videoDurationUs = videoDurationUs; this.videoDurationUs = videoDurationUs;
metadataEntries = new LinkedHashSet<>(); metadataEntries = new LinkedHashSet<>();
videoTrackId = TRACK_ID_UNSET;
} }
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
TrackToken trackToken = muxer.addTrack(format); int trackId = muxer.addTrack(format);
if (MimeTypes.isVideo(format.sampleMimeType)) { if (MimeTypes.isVideo(format.sampleMimeType)) {
muxer.addMetadataEntry(new Mp4OrientationData(format.rotationDegrees)); muxer.addMetadataEntry(new Mp4OrientationData(format.rotationDegrees));
videoTrackToken = trackToken; videoTrackId = trackId;
} }
return trackToken; return trackId;
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
if (videoDurationUs != C.TIME_UNSET if (videoDurationUs != C.TIME_UNSET
&& trackToken == videoTrackToken && trackId == videoTrackId
&& bufferInfo.presentationTimeUs > videoDurationUs) { && bufferInfo.presentationTimeUs > videoDurationUs) {
Log.w( Log.w(
TAG, TAG,
@ -248,7 +249,7 @@ public final class InAppMuxer implements Muxer {
videoDurationUs)); videoDurationUs));
return; return;
} }
muxer.writeSampleData(trackToken, byteBuffer, bufferInfo); muxer.writeSampleData(trackId, byteBuffer, bufferInfo);
} }
@Override @Override
@ -260,14 +261,14 @@ public final class InAppMuxer implements Muxer {
@Override @Override
public void close() throws MuxerException { public void close() throws MuxerException {
if (videoDurationUs != C.TIME_UNSET && videoTrackToken != null) { if (videoDurationUs != C.TIME_UNSET && videoTrackId != TRACK_ID_UNSET) {
BufferInfo bufferInfo = new BufferInfo(); BufferInfo bufferInfo = new BufferInfo();
bufferInfo.set( bufferInfo.set(
/* newOffset= */ 0, /* newOffset= */ 0,
/* newSize= */ 0, /* newSize= */ 0,
videoDurationUs, videoDurationUs,
MediaCodec.BUFFER_FLAG_END_OF_STREAM); MediaCodec.BUFFER_FLAG_END_OF_STREAM);
writeSampleData(checkNotNull(videoTrackToken), ByteBuffer.allocateDirect(0), bufferInfo); writeSampleData(videoTrackId, ByteBuffer.allocateDirect(0), bufferInfo);
} }
writeMetadata(); writeMetadata();
muxer.close(); muxer.close();

View File

@ -48,7 +48,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.container.NalUnitUtil; import androidx.media3.container.NalUnitUtil;
import androidx.media3.effect.DebugTraceUtil; import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.muxer.Muxer; import androidx.media3.muxer.Muxer;
import androidx.media3.muxer.Muxer.TrackToken;
import androidx.media3.muxer.MuxerException; import androidx.media3.muxer.MuxerException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
@ -580,7 +579,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
data.remaining(), data.remaining(),
presentationTimeUs, presentationTimeUs,
TransformerUtil.getMediaCodecFlags(isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0)); TransformerUtil.getMediaCodecFlags(isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0));
muxer.writeSampleData(trackInfo.trackToken, data, bufferInfo); muxer.writeSampleData(trackInfo.trackId, data, bufferInfo);
DebugTraceUtil.logEvent( DebugTraceUtil.logEvent(
COMPONENT_MUXER, COMPONENT_MUXER,
@ -749,16 +748,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final class TrackInfo { private static final class TrackInfo {
public final Format format; public final Format format;
public final TrackToken trackToken; public final int trackId;
public long startTimeUs; public long startTimeUs;
public long bytesWritten; public long bytesWritten;
public int sampleCount; public int sampleCount;
public long timeUs; public long timeUs;
public TrackInfo(Format format, TrackToken trackToken) { public TrackInfo(Format format, int trackId) {
this.format = format; this.format = format;
this.trackToken = trackToken; this.trackId = trackId;
} }
/** /**

View File

@ -63,13 +63,12 @@ import java.nio.ByteBuffer;
} }
@Override @Override
public TrackToken addTrack(Format format) { public int addTrack(Format format) {
return new TrackToken() {}; return 0;
} }
@Override @Override
public void writeSampleData( public void writeSampleData(int trackId, ByteBuffer data, MediaCodec.BufferInfo bufferInfo) {}
TrackToken trackToken, ByteBuffer data, MediaCodec.BufferInfo bufferInfo) {}
@Override @Override
public void addMetadataEntry(Metadata.Entry metadataEntry) {} public void addMetadataEntry(Metadata.Entry metadataEntry) {}

View File

@ -38,8 +38,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
/** /**
* A {@link Dumpable} {@link Muxer} implementation that supports dumping information about all * A {@link Dumpable} {@link Muxer} implementation that supports dumping information about all
@ -88,7 +86,7 @@ public final class CapturingMuxer implements Muxer, Dumpable {
private final boolean handleAudioAsPcm; private final boolean handleAudioAsPcm;
private final SparseArray<DumpableFormat> dumpableFormatByTrackType; private final SparseArray<DumpableFormat> dumpableFormatByTrackType;
private final SparseArray<DumpableStream> dumpableStreamByTrackType; private final SparseArray<DumpableStream> dumpableStreamByTrackType;
private final Map<TrackToken, Integer> trackTokenToType; private final SparseArray<Integer> trackIdToType;
private final ArrayList<Metadata.Entry> metadataList; private final ArrayList<Metadata.Entry> metadataList;
private boolean released; private boolean released;
@ -98,18 +96,18 @@ public final class CapturingMuxer implements Muxer, Dumpable {
this.handleAudioAsPcm = handleAudioAsPcm; this.handleAudioAsPcm = handleAudioAsPcm;
dumpableFormatByTrackType = new SparseArray<>(); dumpableFormatByTrackType = new SparseArray<>();
dumpableStreamByTrackType = new SparseArray<>(); dumpableStreamByTrackType = new SparseArray<>();
trackTokenToType = new HashMap<>(); trackIdToType = new SparseArray<>();
metadataList = new ArrayList<>(); metadataList = new ArrayList<>();
} }
// Muxer implementation. // Muxer implementation.
@Override @Override
public TrackToken addTrack(Format format) throws MuxerException { public int addTrack(Format format) throws MuxerException {
TrackToken trackToken = wrappedMuxer.addTrack(format); int trackId = wrappedMuxer.addTrack(format);
@C.TrackType int trackType = getProcessedTrackType(format.sampleMimeType); @C.TrackType int trackType = getProcessedTrackType(format.sampleMimeType);
trackTokenToType.put(trackToken, trackType); trackIdToType.put(trackId, trackType);
dumpableFormatByTrackType.append( dumpableFormatByTrackType.append(
trackType, new DumpableFormat(format, /* tag= */ Util.getTrackTypeString(trackType))); trackType, new DumpableFormat(format, /* tag= */ Util.getTrackTypeString(trackType)));
@ -120,20 +118,20 @@ public final class CapturingMuxer implements Muxer, Dumpable {
? new DumpablePcmAudioStream(trackType) ? new DumpablePcmAudioStream(trackType)
: new DumpableStream(trackType)); : new DumpableStream(trackType));
return trackToken; return trackId;
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) public void writeSampleData(int trackId, ByteBuffer data, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
@C.TrackType int trackType = checkNotNull(trackTokenToType.get(trackToken)); @C.TrackType int trackType = checkNotNull(trackIdToType.get(trackId));
dumpableStreamByTrackType dumpableStreamByTrackType
.get(trackType) .get(trackType)
.addSample( .addSample(
data, data,
(bufferInfo.flags & C.BUFFER_FLAG_KEY_FRAME) == C.BUFFER_FLAG_KEY_FRAME, (bufferInfo.flags & C.BUFFER_FLAG_KEY_FRAME) == C.BUFFER_FLAG_KEY_FRAME,
bufferInfo.presentationTimeUs); bufferInfo.presentationTimeUs);
wrappedMuxer.writeSampleData(trackToken, data, bufferInfo); wrappedMuxer.writeSampleData(trackId, data, bufferInfo);
} }
@Override @Override