Skip to content

Commit 64c87a5

Browse files
SheenaChhabramicrokatz
authored andcommitted
Move muxer initialization off application thread
Problem: We are initialising muxer as soon as we start the transformation. Now the startTransformation() method can be called from main thread, but muxer creation is an I/O operation and should be not be done on main thread. Solution: Added lazy initialisation of muxer object. The actual transformation happens on background thread so the muxer will be initialised lazily from background thread only. Another way was to provide an initialize() method on MuxerWrapper which will explicitly initialise muxer object but with this approach the caller need to call the initialise method before calling anything else. With current implementation the renderers are calling MuxerWrapper methods on various callbacks (Not sequentially) and also we are sharing same muxer with multiple renderers so It might become confusing for the caller on when to call the initialise() method. Also there are few methods on MuxerWrapper which dont really need muxer object. So in short it might make MuxerWrapper APIs more confusing. Validation: Verified the transformation from demo app. PiperOrigin-RevId: 486735787 (cherry picked from commit eb35765)
1 parent a42050f commit 64c87a5

File tree

7 files changed

+72
-31
lines changed

7 files changed

+72
-31
lines changed

libraries/transformer/src/main/java/androidx/media3/transformer/DefaultMuxer.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import androidx.media3.common.Format;
2121
import androidx.media3.common.util.UnstableApi;
2222
import com.google.common.collect.ImmutableList;
23-
import java.io.IOException;
2423
import java.nio.ByteBuffer;
2524

2625
/** A default {@link Muxer} implementation. */
@@ -53,12 +52,12 @@ public Factory(long maxDelayBetweenSamplesMs) {
5352
}
5453

5554
@Override
56-
public Muxer create(String path) throws IOException {
55+
public Muxer create(String path) throws MuxerException {
5756
return new DefaultMuxer(muxerFactory.create(path));
5857
}
5958

6059
@Override
61-
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
60+
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws MuxerException {
6261
return new DefaultMuxer(muxerFactory.create(parcelFileDescriptor));
6362
}
6463

libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,28 @@ public Factory(long maxDelayBetweenSamplesMs) {
6363
}
6464

6565
@Override
66-
public FrameworkMuxer create(String path) throws IOException {
67-
MediaMuxer mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
66+
public FrameworkMuxer create(String path) throws MuxerException {
67+
MediaMuxer mediaMuxer;
68+
try {
69+
mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
70+
} catch (IOException e) {
71+
throw new MuxerException("Error creating muxer", e);
72+
}
6873
return new FrameworkMuxer(mediaMuxer, maxDelayBetweenSamplesMs);
6974
}
7075

7176
@RequiresApi(26)
7277
@Override
73-
public FrameworkMuxer create(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
74-
MediaMuxer mediaMuxer =
75-
new MediaMuxer(
76-
parcelFileDescriptor.getFileDescriptor(),
77-
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
78+
public FrameworkMuxer create(ParcelFileDescriptor parcelFileDescriptor) throws MuxerException {
79+
MediaMuxer mediaMuxer;
80+
try {
81+
mediaMuxer =
82+
new MediaMuxer(
83+
parcelFileDescriptor.getFileDescriptor(),
84+
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
85+
} catch (IOException e) {
86+
throw new MuxerException("Error creating muxer", e);
87+
}
7888
return new FrameworkMuxer(mediaMuxer, maxDelayBetweenSamplesMs);
7989
}
8090

libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import androidx.media3.common.MimeTypes;
2222
import androidx.media3.common.util.UnstableApi;
2323
import com.google.common.collect.ImmutableList;
24-
import java.io.IOException;
2524
import java.nio.ByteBuffer;
2625

2726
/**
@@ -57,9 +56,9 @@ interface Factory {
5756
*
5857
* @param path The path to the output file.
5958
* @throws IllegalArgumentException If the path is invalid.
60-
* @throws IOException If an error occurs opening the output file for writing.
59+
* @throws MuxerException If an error occurs opening the output file for writing.
6160
*/
62-
Muxer create(String path) throws IOException;
61+
Muxer create(String path) throws MuxerException;
6362

6463
/**
6564
* Returns a new muxer writing to a file descriptor.
@@ -69,9 +68,9 @@ interface Factory {
6968
* muxer is released. It is the responsibility of the caller to close the
7069
* ParcelFileDescriptor. This can be done after this method returns.
7170
* @throws IllegalArgumentException If the file descriptor is invalid.
72-
* @throws IOException If an error occurs opening the output file descriptor for writing.
71+
* @throws MuxerException If an error occurs opening the output file descriptor for writing.
7372
*/
74-
Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws IOException;
73+
Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws MuxerException;
7574

7675
/**
7776
* Returns the supported sample {@linkplain MimeTypes MIME types} for the given {@link

libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package androidx.media3.transformer;
1818

19+
import static androidx.media3.common.util.Assertions.checkNotNull;
1920
import static androidx.media3.common.util.Assertions.checkState;
2021
import static androidx.media3.common.util.Util.maxValue;
2122
import static androidx.media3.common.util.Util.minValue;
2223
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2324

25+
import android.os.ParcelFileDescriptor;
2426
import android.util.SparseIntArray;
2527
import android.util.SparseLongArray;
2628
import androidx.annotation.Nullable;
@@ -33,7 +35,9 @@
3335
import java.util.concurrent.Executors;
3436
import java.util.concurrent.ScheduledExecutorService;
3537
import java.util.concurrent.ScheduledFuture;
38+
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
3639
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
40+
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
3741

3842
/**
3943
* A wrapper around a media muxer.
@@ -50,7 +54,8 @@
5054
*/
5155
private static final long MAX_TRACK_WRITE_AHEAD_US = Util.msToUs(500);
5256

53-
private final Muxer muxer;
57+
@Nullable private final String outputPath;
58+
@Nullable private final ParcelFileDescriptor outputParcelFileDescriptor;
5459
private final Muxer.Factory muxerFactory;
5560
private final Transformer.AsyncErrorListener asyncErrorListener;
5661
private final SparseIntArray trackTypeToIndex;
@@ -66,10 +71,19 @@
6671
private long minTrackTimeUs;
6772
private @MonotonicNonNull ScheduledFuture abortScheduledFuture;
6873
private boolean isAborted;
74+
private @MonotonicNonNull Muxer muxer;
6975

7076
public MuxerWrapper(
71-
Muxer muxer, Muxer.Factory muxerFactory, Transformer.AsyncErrorListener asyncErrorListener) {
72-
this.muxer = muxer;
77+
@Nullable String outputPath,
78+
@Nullable ParcelFileDescriptor outputParcelFileDescriptor,
79+
Muxer.Factory muxerFactory,
80+
Transformer.AsyncErrorListener asyncErrorListener) {
81+
if (outputPath == null && outputParcelFileDescriptor == null) {
82+
throw new NullPointerException("Both output path and ParcelFileDescriptor are null");
83+
}
84+
85+
this.outputPath = outputPath;
86+
this.outputParcelFileDescriptor = outputParcelFileDescriptor;
7387
this.muxerFactory = muxerFactory;
7488
this.asyncErrorListener = asyncErrorListener;
7589

@@ -135,6 +149,8 @@ public void addTrackFormat(Format format) throws Muxer.MuxerException {
135149
trackTypeToIndex.get(trackType, /* valueIfKeyNotFound= */ C.INDEX_UNSET) == C.INDEX_UNSET,
136150
"There is already a track of type " + trackType);
137151

152+
ensureMuxerInitialized();
153+
138154
int trackIndex = muxer.addTrack(format);
139155
trackTypeToIndex.put(trackType, trackIndex);
140156
trackTypeToSampleCount.put(trackType, 0);
@@ -181,6 +197,7 @@ public boolean writeSample(
181197
trackTypeToTimeUs.put(trackType, presentationTimeUs);
182198
}
183199

200+
checkNotNull(muxer);
184201
resetAbortTimer();
185202
muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs);
186203
previousTrackType = trackType;
@@ -213,7 +230,9 @@ public void endTrack(@C.TrackType int trackType) {
213230
public void release(boolean forCancellation) throws Muxer.MuxerException {
214231
isReady = false;
215232
abortScheduledExecutorService.shutdownNow();
216-
muxer.release(forCancellation);
233+
if (muxer != null) {
234+
muxer.release(forCancellation);
235+
}
217236
}
218237

219238
/** Returns the number of {@link #registerTrack() registered} tracks. */
@@ -276,6 +295,7 @@ private boolean canWriteSampleOfType(int trackType) {
276295
return trackTimeUs - minTrackTimeUs <= MAX_TRACK_WRITE_AHEAD_US;
277296
}
278297

298+
@RequiresNonNull("muxer")
279299
private void resetAbortTimer() {
280300
long maxDelayBetweenSamplesMs = muxer.getMaxDelayBetweenSamplesMs();
281301
if (maxDelayBetweenSamplesMs == C.TIME_UNSET) {
@@ -302,4 +322,16 @@ private void resetAbortTimer() {
302322
maxDelayBetweenSamplesMs,
303323
MILLISECONDS);
304324
}
325+
326+
@EnsuresNonNull("muxer")
327+
private void ensureMuxerInitialized() throws Muxer.MuxerException {
328+
if (muxer == null) {
329+
if (outputPath != null) {
330+
muxer = muxerFactory.create(outputPath);
331+
} else {
332+
checkNotNull(outputParcelFileDescriptor);
333+
muxer = muxerFactory.create(outputParcelFileDescriptor);
334+
}
335+
}
336+
}
305337
}

libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ public void removeAllListeners() {
677677
* @throws IllegalArgumentException If the path is invalid.
678678
* @throws IllegalStateException If this method is called from the wrong thread.
679679
* @throws IllegalStateException If a transformation is already in progress.
680-
* @throws IOException If an error occurs opening the output file for writing.
680+
* @throws IOException If {@link MediaItem} is not supported.
681681
*/
682682
public void startTransformation(MediaItem mediaItem, String path) throws IOException {
683683
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
@@ -688,7 +688,7 @@ public void startTransformation(MediaItem mediaItem, String path) throws IOExcep
688688
}
689689
this.outputPath = path;
690690
this.outputParcelFileDescriptor = null;
691-
startTransformation(mediaItem, muxerFactory.create(path));
691+
startTransformationInternal(mediaItem);
692692
}
693693

694694
/**
@@ -711,24 +711,26 @@ public void startTransformation(MediaItem mediaItem, String path) throws IOExcep
711711
* @throws IllegalArgumentException If the file descriptor is invalid.
712712
* @throws IllegalStateException If this method is called from the wrong thread.
713713
* @throws IllegalStateException If a transformation is already in progress.
714-
* @throws IOException If an error occurs opening the output file for writing.
715714
*/
716715
@RequiresApi(26)
717-
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor)
718-
throws IOException {
716+
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) {
719717
this.outputParcelFileDescriptor = parcelFileDescriptor;
720718
this.outputPath = null;
721-
startTransformation(mediaItem, muxerFactory.create(parcelFileDescriptor));
719+
startTransformationInternal(mediaItem);
722720
}
723721

724-
private void startTransformation(MediaItem mediaItem, Muxer muxer) {
722+
private void startTransformationInternal(MediaItem mediaItem) {
725723
verifyApplicationThread();
726724
if (player != null) {
727725
throw new IllegalStateException("There is already a transformation in progress.");
728726
}
729727
TransformerPlayerListener playerListener = new TransformerPlayerListener(mediaItem, looper);
730728
MuxerWrapper muxerWrapper =
731-
new MuxerWrapper(muxer, muxerFactory, /* asyncErrorListener= */ playerListener);
729+
new MuxerWrapper(
730+
outputPath,
731+
outputParcelFileDescriptor,
732+
muxerFactory,
733+
/* asyncErrorListener= */ playerListener);
732734
this.muxerWrapper = muxerWrapper;
733735
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
734736
trackSelector.setParameters(

libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import androidx.media3.common.Format;
1919
import androidx.media3.test.utils.DumpableFormat;
2020
import androidx.media3.test.utils.Dumper;
21-
import java.io.IOException;
2221
import java.nio.ByteBuffer;
2322
import java.util.ArrayList;
2423
import java.util.Arrays;
@@ -35,7 +34,7 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable {
3534
private final List<Dumper.Dumpable> dumpables;
3635

3736
/** Creates a new test muxer. */
38-
public TestMuxer(String path, Muxer.Factory muxerFactory) throws IOException {
37+
public TestMuxer(String path, Muxer.Factory muxerFactory) throws MuxerException {
3938
muxer = muxerFactory.create(path);
4039
dumpables = new ArrayList<>();
4140
}

libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,13 +912,13 @@ public TestMuxerFactory(long maxDelayBetweenSamplesMs) {
912912
}
913913

914914
@Override
915-
public Muxer create(String path) throws IOException {
915+
public Muxer create(String path) throws Muxer.MuxerException {
916916
testMuxer = new TestMuxer(path, defaultMuxerFactory);
917917
return testMuxer;
918918
}
919919

920920
@Override
921-
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
921+
public Muxer create(ParcelFileDescriptor parcelFileDescriptor) throws Muxer.MuxerException {
922922
testMuxer = new TestMuxer("FD:" + parcelFileDescriptor.getFd(), defaultMuxerFactory);
923923
return testMuxer;
924924
}

0 commit comments

Comments
 (0)