mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Split InAppMuxer into InApp Mp4Muxer and FragmentedMp4Muxer
This is pre work required to remove `Muxer.java` interface from the muxer module. `Mp4Muxer` and `FragmentedMp4Muxer` will no longer implement the `Muxer` interface. PiperOrigin-RevId: 716669531
This commit is contained in:
parent
a4d9a3e096
commit
4ac4f7e2e0
@ -29,6 +29,10 @@
|
|||||||
by the user of the device. Apps can opt-out of contributing to platform
|
by the user of the device. Apps can opt-out of contributing to platform
|
||||||
diagnostics for Transformer with
|
diagnostics for Transformer with
|
||||||
`Transformer.Builder.setUsePlatformDiagnostics(false)`.
|
`Transformer.Builder.setUsePlatformDiagnostics(false)`.
|
||||||
|
* Split `InAppMuxer` into `InAppMp4Muxer` and `InAppFragmentedMp4Muxer`.
|
||||||
|
`InAppMp4Muxer` is to be used for producing a non-fragmented MP4 file,
|
||||||
|
while `InAppFragmentedMp4Muxer` is to be used for producing a fragmented
|
||||||
|
MP4 file.
|
||||||
* Track Selection:
|
* Track Selection:
|
||||||
* Extractors:
|
* Extractors:
|
||||||
* Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather
|
* Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather
|
||||||
|
@ -57,7 +57,8 @@ import androidx.media3.transformer.EditedMediaItemSequence;
|
|||||||
import androidx.media3.transformer.Effects;
|
import androidx.media3.transformer.Effects;
|
||||||
import androidx.media3.transformer.ExportException;
|
import androidx.media3.transformer.ExportException;
|
||||||
import androidx.media3.transformer.ExportResult;
|
import androidx.media3.transformer.ExportResult;
|
||||||
import androidx.media3.transformer.InAppMuxer;
|
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
|
||||||
|
import androidx.media3.transformer.InAppMp4Muxer;
|
||||||
import androidx.media3.transformer.JsonUtil;
|
import androidx.media3.transformer.JsonUtil;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
import androidx.media3.ui.PlayerView;
|
import androidx.media3.ui.PlayerView;
|
||||||
@ -362,21 +363,20 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
|
|||||||
enableDebugTracingCheckBox.setOnCheckedChangeListener(
|
enableDebugTracingCheckBox.setOnCheckedChangeListener(
|
||||||
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
|
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
|
||||||
|
|
||||||
// Connect producing fragmented MP4 to using Media3 Muxer
|
CheckBox useMedia3Mp4MuxerCheckBox =
|
||||||
CheckBox useMedia3MuxerCheckBox =
|
exportSettingsDialogView.findViewById(R.id.use_media3_mp4_muxer_checkbox);
|
||||||
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
|
CheckBox useMedia3FragmentedMp4MuxerCheckBox =
|
||||||
CheckBox produceFragmentedMp4CheckBox =
|
exportSettingsDialogView.findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
|
||||||
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
|
useMedia3Mp4MuxerCheckBox.setOnCheckedChangeListener(
|
||||||
useMedia3MuxerCheckBox.setOnCheckedChangeListener(
|
|
||||||
(buttonView, isChecked) -> {
|
|
||||||
if (!isChecked) {
|
|
||||||
produceFragmentedMp4CheckBox.setChecked(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
|
|
||||||
(buttonView, isChecked) -> {
|
(buttonView, isChecked) -> {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
useMedia3MuxerCheckBox.setChecked(true);
|
useMedia3FragmentedMp4MuxerCheckBox.setChecked(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useMedia3FragmentedMp4MuxerCheckBox.setOnCheckedChangeListener(
|
||||||
|
(buttonView, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
useMedia3Mp4MuxerCheckBox.setChecked(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -419,15 +419,15 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
|
|||||||
transformerBuilder.setVideoMimeType(selectedVideoMimeType);
|
transformerBuilder.setVideoMimeType(selectedVideoMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox useMedia3MuxerCheckBox =
|
CheckBox useMedia3Mp4MuxerCheckBox =
|
||||||
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
|
exportSettingsDialogView.findViewById(R.id.use_media3_mp4_muxer_checkbox);
|
||||||
CheckBox produceFragmentedMp4CheckBox =
|
CheckBox useMedia3FragmentedMp4MuxerCheckBox =
|
||||||
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
|
exportSettingsDialogView.findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
|
||||||
if (useMedia3MuxerCheckBox.isChecked()) {
|
if (useMedia3Mp4MuxerCheckBox.isChecked()) {
|
||||||
transformerBuilder.setMuxerFactory(
|
transformerBuilder.setMuxerFactory(new InAppMp4Muxer.Factory());
|
||||||
new InAppMuxer.Factory.Builder()
|
}
|
||||||
.setOutputFragmentedMp4(produceFragmentedMp4CheckBox.isChecked())
|
if (useMedia3FragmentedMp4MuxerCheckBox.isChecked()) {
|
||||||
.build());
|
transformerBuilder.setMuxerFactory(new InAppFragmentedMp4Muxer.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
transformer =
|
transformer =
|
||||||
|
@ -79,12 +79,12 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@string/use_media3_muxer"
|
android:text="@string/use_media3_mp4_muxer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/use_media3_muxer_checkbox"
|
android:id="@+id/use_media3_mp4_muxer_checkbox"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:checked="false"
|
android:checked="false"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -96,12 +96,12 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@string/produce_fragmented_mp4"
|
android:text="@string/use_media3_fragmented_mp4_muxer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/produce_fragmented_mp4_checkbox"
|
android:id="@+id/use_media3_fragmented_mp4_muxer_checkbox"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:checked="false"
|
android:checked="false"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -34,6 +34,6 @@
|
|||||||
<string name="output_audio_mime_type" translatable="false">Output audio MIME type</string>
|
<string name="output_audio_mime_type" translatable="false">Output audio MIME type</string>
|
||||||
<string name="output_video_mime_type" translatable="false">Output video MIME type</string>
|
<string name="output_video_mime_type" translatable="false">Output video MIME type</string>
|
||||||
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
|
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
|
||||||
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
|
<string name="use_media3_mp4_muxer" translatable="false">Use Media3 Mp4Muxer</string>
|
||||||
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
|
<string name="use_media3_fragmented_mp4_muxer" translatable="false">Use Media3 FragmentedMp4Muxer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -77,8 +77,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
public static final String ENABLE_ANALYZER_MODE = "enable_analyzer_mode";
|
public static final String ENABLE_ANALYZER_MODE = "enable_analyzer_mode";
|
||||||
public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview";
|
public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview";
|
||||||
public static final String ABORT_SLOW_EXPORT = "abort_slow_export";
|
public static final String ABORT_SLOW_EXPORT = "abort_slow_export";
|
||||||
public static final String USE_MEDIA3_MUXER = "use_media3_muxer";
|
public static final String USE_MEDIA3_MP4_MUXER = "use_media3_mp4_muxer";
|
||||||
public static final String PRODUCE_FRAGMENTED_MP4 = "produce_fragmented_mp4";
|
public static final String USE_MEDIA3_FRAGMENTED_MP4_MUXER = "use_media3_fragmented_mp4_muxer";
|
||||||
public static final String HDR_MODE = "hdr_mode";
|
public static final String HDR_MODE = "hdr_mode";
|
||||||
public static final String AUDIO_EFFECTS_SELECTIONS = "audio_effects_selections";
|
public static final String AUDIO_EFFECTS_SELECTIONS = "audio_effects_selections";
|
||||||
public static final String VIDEO_EFFECTS_SELECTIONS = "video_effects_selections";
|
public static final String VIDEO_EFFECTS_SELECTIONS = "video_effects_selections";
|
||||||
@ -177,8 +177,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
private CheckBox enableDebugPreviewCheckBox;
|
private CheckBox enableDebugPreviewCheckBox;
|
||||||
private CheckBox enableDebugTracingCheckBox;
|
private CheckBox enableDebugTracingCheckBox;
|
||||||
private CheckBox abortSlowExportCheckBox;
|
private CheckBox abortSlowExportCheckBox;
|
||||||
private CheckBox useMedia3Muxer;
|
private CheckBox useMedia3Mp4Muxer;
|
||||||
private CheckBox produceFragmentedMp4CheckBox;
|
private CheckBox useMedia3FragmentedMp4Muxer;
|
||||||
private Spinner hdrModeSpinner;
|
private Spinner hdrModeSpinner;
|
||||||
private Button selectAudioEffectsButton;
|
private Button selectAudioEffectsButton;
|
||||||
private Button selectVideoEffectsButton;
|
private Button selectVideoEffectsButton;
|
||||||
@ -303,18 +303,18 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
|
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
|
||||||
|
|
||||||
abortSlowExportCheckBox = findViewById(R.id.abort_slow_export_checkbox);
|
abortSlowExportCheckBox = findViewById(R.id.abort_slow_export_checkbox);
|
||||||
useMedia3Muxer = findViewById(R.id.use_media3_muxer_checkbox);
|
useMedia3Mp4Muxer = findViewById(R.id.use_media3_mp4_muxer_checkbox);
|
||||||
produceFragmentedMp4CheckBox = findViewById(R.id.produce_fragmented_mp4_checkbox);
|
useMedia3FragmentedMp4Muxer = findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
|
||||||
useMedia3Muxer.setOnCheckedChangeListener(
|
useMedia3Mp4Muxer.setOnCheckedChangeListener(
|
||||||
(buttonView, isChecked) -> {
|
|
||||||
if (!isChecked) {
|
|
||||||
produceFragmentedMp4CheckBox.setChecked(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
|
|
||||||
(buttonView, isChecked) -> {
|
(buttonView, isChecked) -> {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
useMedia3Muxer.setChecked(true);
|
useMedia3FragmentedMp4Muxer.setChecked(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useMedia3FragmentedMp4Muxer.setOnCheckedChangeListener(
|
||||||
|
(buttonView, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
useMedia3Mp4Muxer.setChecked(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -407,8 +407,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
bundle.putBoolean(ENABLE_ANALYZER_MODE, enableAnalyzerModeCheckBox.isChecked());
|
bundle.putBoolean(ENABLE_ANALYZER_MODE, enableAnalyzerModeCheckBox.isChecked());
|
||||||
bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked());
|
bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked());
|
||||||
bundle.putBoolean(ABORT_SLOW_EXPORT, abortSlowExportCheckBox.isChecked());
|
bundle.putBoolean(ABORT_SLOW_EXPORT, abortSlowExportCheckBox.isChecked());
|
||||||
bundle.putBoolean(USE_MEDIA3_MUXER, useMedia3Muxer.isChecked());
|
bundle.putBoolean(USE_MEDIA3_MP4_MUXER, useMedia3Mp4Muxer.isChecked());
|
||||||
bundle.putBoolean(PRODUCE_FRAGMENTED_MP4, produceFragmentedMp4CheckBox.isChecked());
|
bundle.putBoolean(USE_MEDIA3_FRAGMENTED_MP4_MUXER, useMedia3FragmentedMp4Muxer.isChecked());
|
||||||
String selectedHdrMode = String.valueOf(hdrModeSpinner.getSelectedItem());
|
String selectedHdrMode = String.valueOf(hdrModeSpinner.getSelectedItem());
|
||||||
bundle.putInt(HDR_MODE, HDR_MODE_DESCRIPTIONS.get(selectedHdrMode));
|
bundle.putInt(HDR_MODE, HDR_MODE_DESCRIPTIONS.get(selectedHdrMode));
|
||||||
bundle.putBooleanArray(AUDIO_EFFECTS_SELECTIONS, audioEffectsSelections);
|
bundle.putBooleanArray(AUDIO_EFFECTS_SELECTIONS, audioEffectsSelections);
|
||||||
|
@ -88,7 +88,8 @@ import androidx.media3.transformer.Effects;
|
|||||||
import androidx.media3.transformer.ExperimentalAnalyzerModeFactory;
|
import androidx.media3.transformer.ExperimentalAnalyzerModeFactory;
|
||||||
import androidx.media3.transformer.ExportException;
|
import androidx.media3.transformer.ExportException;
|
||||||
import androidx.media3.transformer.ExportResult;
|
import androidx.media3.transformer.ExportResult;
|
||||||
import androidx.media3.transformer.InAppMuxer;
|
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
|
||||||
|
import androidx.media3.transformer.InAppMp4Muxer;
|
||||||
import androidx.media3.transformer.JsonUtil;
|
import androidx.media3.transformer.JsonUtil;
|
||||||
import androidx.media3.transformer.ProgressHolder;
|
import androidx.media3.transformer.ProgressHolder;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
@ -302,12 +303,12 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
|
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_MUXER)) {
|
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_MP4_MUXER)) {
|
||||||
transformerBuilder.setMuxerFactory(
|
transformerBuilder.setMuxerFactory(new InAppMp4Muxer.Factory());
|
||||||
new InAppMuxer.Factory.Builder()
|
}
|
||||||
.setOutputFragmentedMp4(
|
|
||||||
bundle.getBoolean(ConfigurationActivity.PRODUCE_FRAGMENTED_MP4))
|
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_FRAGMENTED_MP4_MUXER)) {
|
||||||
.build());
|
transformerBuilder.setMuxerFactory(new InAppFragmentedMp4Muxer.Factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
|
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
|
||||||
|
@ -239,18 +239,18 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:text="@string/use_media3_muxer" />
|
android:text="@string/use_media3_mp4_muxer" />
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/use_media3_muxer_checkbox"
|
android:id="@+id/use_media3_mp4_muxer_checkbox"
|
||||||
android:layout_gravity="end"/>
|
android:layout_gravity="end"/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:text="@string/produce_fragmented_mp4" />
|
android:text="@string/use_media3_fragmented_mp4_muxer" />
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/produce_fragmented_mp4_checkbox"
|
android:id="@+id/use_media3_fragmented_mp4_muxer_checkbox"
|
||||||
android:layout_gravity="end"/>
|
android:layout_gravity="end"/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
|
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
|
||||||
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
|
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
|
||||||
<string name="abort_slow_export" translatable="false">Abort slow export</string>
|
<string name="abort_slow_export" translatable="false">Abort slow export</string>
|
||||||
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
|
<string name="use_media3_mp4_muxer" translatable="false">Use Media3 Mp4Muxer</string>
|
||||||
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
|
<string name="use_media3_fragmented_mp4_muxer" translatable="false">Use Media3 FragmentedMp4Muxer</string>
|
||||||
<string name="trim" translatable="false">Trim</string>
|
<string name="trim" translatable="false">Trim</string>
|
||||||
<string name="hdr_mode" translatable="false">HDR mode</string>
|
<string name="hdr_mode" translatable="false">HDR mode</string>
|
||||||
<string name="select_audio_effects" translatable="false">Add audio effects</string>
|
<string name="select_audio_effects" translatable="false">Add audio effects</string>
|
||||||
|
@ -1385,7 +1385,7 @@ public final class AndroidTestUtil {
|
|||||||
/** Returns a {@link Muxer.Factory} depending upon the API level. */
|
/** Returns a {@link Muxer.Factory} depending upon the API level. */
|
||||||
public static Muxer.Factory getMuxerFactoryBasedOnApi() {
|
public static Muxer.Factory getMuxerFactoryBasedOnApi() {
|
||||||
// MediaMuxer supports B-frame from API > 24.
|
// MediaMuxer supports B-frame from API > 24.
|
||||||
return SDK_INT > 24 ? new DefaultMuxer.Factory() : new InAppMuxer.Factory.Builder().build();
|
return SDK_INT > 24 ? new DefaultMuxer.Factory() : new InAppMp4Muxer.Factory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canDecode(Format format) throws MediaCodecUtil.DecoderQueryException {
|
private static boolean canDecode(Format format) throws MediaCodecUtil.DecoderQueryException {
|
||||||
|
@ -1815,7 +1815,7 @@ public class TransformerEndToEndTest {
|
|||||||
context,
|
context,
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context)
|
||||||
.setVideoMimeType(MimeTypes.VIDEO_H265)
|
.setVideoMimeType(MimeTypes.VIDEO_H265)
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
.setMuxerFactory(new InAppMp4Muxer.Factory())
|
||||||
.build())
|
.build())
|
||||||
.build()
|
.build()
|
||||||
.run(testId, editedMediaItem);
|
.run(testId, editedMediaItem);
|
||||||
@ -2112,9 +2112,7 @@ public class TransformerEndToEndTest {
|
|||||||
public void transmux_audioWithEditListUsingInAppMuxer_preservesDuration() throws Exception {
|
public void transmux_audioWithEditListUsingInAppMuxer_preservesDuration() throws Exception {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context).setMuxerFactory(new InAppMp4Muxer.Factory()).build();
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
|
||||||
.build();
|
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
MediaItem.fromUri(Uri.parse("asset:///media/mp4/long_edit_list_audioonly.mp4"));
|
MediaItem.fromUri(Uri.parse("asset:///media/mp4/long_edit_list_audioonly.mp4"));
|
||||||
|
|
||||||
@ -2393,7 +2391,7 @@ public class TransformerEndToEndTest {
|
|||||||
// The MediaMuxer is not writing the bitrate hence use the InAppMuxer.
|
// The MediaMuxer is not writing the bitrate hence use the InAppMuxer.
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context)
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
.setMuxerFactory(new InAppMp4Muxer.Factory())
|
||||||
.setEncoderFactory(
|
.setEncoderFactory(
|
||||||
new DefaultEncoderFactory.Builder(context)
|
new DefaultEncoderFactory.Builder(context)
|
||||||
.setRequestedAudioEncoderSettings(
|
.setRequestedAudioEncoderSettings(
|
||||||
|
@ -38,9 +38,9 @@ import org.junit.runners.Parameterized;
|
|||||||
import org.junit.runners.Parameterized.Parameter;
|
import org.junit.runners.Parameterized.Parameter;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
/** End-to-end instrumentation test for {@link Transformer} with {@link InAppMuxer}. */
|
/** End-to-end instrumentation test for {@link Transformer} with {@link InAppMp4Muxer}. */
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class TransformerWithInAppMuxerEndToEndAndroidTest {
|
public class TransformerWithInAppMp4MuxerEndToEndAndroidTest {
|
||||||
private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/mp4/";
|
private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/mp4/";
|
||||||
private static final String H264_MP4 = "sample_no_bframes.mp4";
|
private static final String H264_MP4 = "sample_no_bframes.mp4";
|
||||||
private static final String H265_MP4 = "h265_with_metadata_track.mp4";
|
private static final String H265_MP4 = "h265_with_metadata_track.mp4";
|
||||||
@ -66,9 +66,7 @@ public class TransformerWithInAppMuxerEndToEndAndroidTest {
|
|||||||
/* inputFormat= */ MP4_ASSET.videoFormat,
|
/* inputFormat= */ MP4_ASSET.videoFormat,
|
||||||
/* outputFormat= */ MP4_ASSET.videoFormat);
|
/* outputFormat= */ MP4_ASSET.videoFormat);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context).setMuxerFactory(new InAppMp4Muxer.Factory()).build();
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
|
||||||
.build();
|
|
||||||
ImmutableList<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
|
ImmutableList<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFile));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFile));
|
||||||
EditedMediaItem editedMediaItem =
|
EditedMediaItem editedMediaItem =
|
||||||
@ -91,9 +89,7 @@ public class TransformerWithInAppMuxerEndToEndAndroidTest {
|
|||||||
assumeTrue(checkNotNull(inputFile).equals(H264_MP4));
|
assumeTrue(checkNotNull(inputFile).equals(H264_MP4));
|
||||||
String testId = "audioEditing_completesSuccessfully";
|
String testId = "audioEditing_completesSuccessfully";
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context).setMuxerFactory(new InAppMp4Muxer.Factory()).build();
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
|
||||||
.build();
|
|
||||||
ChannelMixingAudioProcessor channelMixingAudioProcessor = new ChannelMixingAudioProcessor();
|
ChannelMixingAudioProcessor channelMixingAudioProcessor = new ChannelMixingAudioProcessor();
|
||||||
channelMixingAudioProcessor.putChannelMixingMatrix(
|
channelMixingAudioProcessor.putChannelMixingMatrix(
|
||||||
ChannelMixingMatrix.create(/* inputChannelCount= */ 1, /* outputChannelCount= */ 2));
|
ChannelMixingMatrix.create(/* inputChannelCount= */ 1, /* outputChannelCount= */ 2));
|
@ -27,7 +27,7 @@ import androidx.media3.effect.RgbFilter;
|
|||||||
import androidx.media3.transformer.EditedMediaItem;
|
import androidx.media3.transformer.EditedMediaItem;
|
||||||
import androidx.media3.transformer.Effects;
|
import androidx.media3.transformer.Effects;
|
||||||
import androidx.media3.transformer.ExportTestResult;
|
import androidx.media3.transformer.ExportTestResult;
|
||||||
import androidx.media3.transformer.InAppMuxer;
|
import androidx.media3.transformer.InAppMp4Muxer;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@ -40,9 +40,9 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.TestName;
|
import org.junit.rules.TestName;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** End-to-end instrumentation test for {@link Transformer} with {@link InAppMuxer}. */
|
/** End-to-end instrumentation test for {@link Transformer} with {@link InAppMp4Muxer}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TransformerWithInAppMuxerEndToEndMhTest {
|
public class TransformerWithInAppMp4MuxerEndToEndMhTest {
|
||||||
@Rule public final TestName testName = new TestName();
|
@Rule public final TestName testName = new TestName();
|
||||||
|
|
||||||
private String testId;
|
private String testId;
|
||||||
@ -61,9 +61,7 @@ public class TransformerWithInAppMuxerEndToEndMhTest {
|
|||||||
/* inputFormat= */ MP4_ASSET_AV1_VIDEO.videoFormat,
|
/* inputFormat= */ MP4_ASSET_AV1_VIDEO.videoFormat,
|
||||||
/* outputFormat= */ null);
|
/* outputFormat= */ null);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new Transformer.Builder(context)
|
new Transformer.Builder(context).setMuxerFactory(new InAppMp4Muxer.Factory()).build();
|
||||||
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
|
|
||||||
.build();
|
|
||||||
ImmutableList<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
|
ImmutableList<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_AV1_VIDEO.uri));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_AV1_VIDEO.uri));
|
||||||
EditedMediaItem editedMediaItem =
|
EditedMediaItem editedMediaItem =
|
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaCodec.BufferInfo;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.container.Mp4OrientationData;
|
||||||
|
import androidx.media3.muxer.FragmentedMp4Muxer;
|
||||||
|
import androidx.media3.muxer.Muxer;
|
||||||
|
import androidx.media3.muxer.MuxerException;
|
||||||
|
import androidx.media3.muxer.MuxerUtil;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/** {@link Muxer} implementation that uses a {@link FragmentedMp4Muxer}. */
|
||||||
|
// TODO: b/372417042 - Add E2E tests for producing fragmented MP4 output.
|
||||||
|
@UnstableApi
|
||||||
|
public final class InAppFragmentedMp4Muxer implements Muxer {
|
||||||
|
/** {@link Muxer.Factory} for {@link InAppFragmentedMp4Muxer}. */
|
||||||
|
public static final class Factory implements Muxer.Factory {
|
||||||
|
// TODO: b/372417042 - Move these lists to FragmentedMp4Muxer.
|
||||||
|
/** A list of supported video sample MIME types. */
|
||||||
|
private static final ImmutableList<String> SUPPORTED_VIDEO_SAMPLE_MIME_TYPES =
|
||||||
|
ImmutableList.of(
|
||||||
|
MimeTypes.VIDEO_AV1,
|
||||||
|
MimeTypes.VIDEO_H263,
|
||||||
|
MimeTypes.VIDEO_H264,
|
||||||
|
MimeTypes.VIDEO_H265,
|
||||||
|
MimeTypes.VIDEO_MP4V);
|
||||||
|
|
||||||
|
/** A list of 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,
|
||||||
|
MimeTypes.AUDIO_OPUS,
|
||||||
|
MimeTypes.AUDIO_VORBIS);
|
||||||
|
|
||||||
|
private final long fragmentDurationMs;
|
||||||
|
|
||||||
|
private long videoDurationUs;
|
||||||
|
|
||||||
|
/** Creates an instance with default values. */
|
||||||
|
public Factory() {
|
||||||
|
this(C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param fragmentDurationMs The fragment duration (in milliseconds).
|
||||||
|
*/
|
||||||
|
public Factory(long fragmentDurationMs) {
|
||||||
|
this.fragmentDurationMs = fragmentDurationMs;
|
||||||
|
videoDurationUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of the video track (in microseconds) in the output.
|
||||||
|
*
|
||||||
|
* <p>Only the duration of the last sample is adjusted to achieve the given duration. Duration
|
||||||
|
* of the other samples remains unchanged.
|
||||||
|
*
|
||||||
|
* <p>The default is {@link C#TIME_UNSET} to not set any duration in the output. In this case
|
||||||
|
* the video track duration is determined by the samples written to it and the duration of the
|
||||||
|
* last sample will be same as that of the sample before that.
|
||||||
|
*
|
||||||
|
* @param videoDurationUs The duration of the video track (in microseconds) in the output, or
|
||||||
|
* {@link C#TIME_UNSET} to not set any duration. Only applicable when a video track is
|
||||||
|
* {@linkplain #addTrack(Format) added}.
|
||||||
|
* @return This factory.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Factory setVideoDurationUs(long videoDurationUs) {
|
||||||
|
this.videoDurationUs = videoDurationUs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InAppFragmentedMp4Muxer create(String path) throws MuxerException {
|
||||||
|
FileOutputStream outputStream;
|
||||||
|
try {
|
||||||
|
outputStream = new FileOutputStream(path);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new MuxerException("Error creating file output stream", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentedMp4Muxer.Builder builder = new FragmentedMp4Muxer.Builder(outputStream);
|
||||||
|
if (fragmentDurationMs != C.TIME_UNSET) {
|
||||||
|
builder.setFragmentDurationMs(fragmentDurationMs);
|
||||||
|
}
|
||||||
|
FragmentedMp4Muxer muxer = builder.build();
|
||||||
|
|
||||||
|
return new InAppFragmentedMp4Muxer(muxer, videoDurationUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType) {
|
||||||
|
if (trackType == C.TRACK_TYPE_VIDEO) {
|
||||||
|
return SUPPORTED_VIDEO_SAMPLE_MIME_TYPES;
|
||||||
|
} else if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||||
|
return SUPPORTED_AUDIO_SAMPLE_MIME_TYPES;
|
||||||
|
}
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "InAppFragmentedMp4Muxer";
|
||||||
|
private static final int TRACK_ID_UNSET = -1;
|
||||||
|
|
||||||
|
private final FragmentedMp4Muxer muxer;
|
||||||
|
private final long videoDurationUs;
|
||||||
|
|
||||||
|
private int videoTrackId;
|
||||||
|
|
||||||
|
private InAppFragmentedMp4Muxer(FragmentedMp4Muxer muxer, long videoDurationUs) {
|
||||||
|
this.muxer = muxer;
|
||||||
|
this.videoDurationUs = videoDurationUs;
|
||||||
|
videoTrackId = TRACK_ID_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int addTrack(Format format) {
|
||||||
|
int trackId = muxer.addTrack(format);
|
||||||
|
if (MimeTypes.isVideo(format.sampleMimeType)) {
|
||||||
|
muxer.addMetadataEntry(new Mp4OrientationData(format.rotationDegrees));
|
||||||
|
videoTrackId = trackId;
|
||||||
|
}
|
||||||
|
return trackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo)
|
||||||
|
throws MuxerException {
|
||||||
|
if (videoDurationUs != C.TIME_UNSET
|
||||||
|
&& trackId == videoTrackId
|
||||||
|
&& bufferInfo.presentationTimeUs > videoDurationUs) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
String.format(
|
||||||
|
Locale.US,
|
||||||
|
"Skipped sample with presentation time (%d) > video duration (%d)",
|
||||||
|
bufferInfo.presentationTimeUs,
|
||||||
|
videoDurationUs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
muxer.writeSampleData(trackId, byteBuffer, bufferInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addMetadataEntry(Metadata.Entry metadataEntry) {
|
||||||
|
if (MuxerUtil.isMetadataSupported(metadataEntry)) {
|
||||||
|
muxer.addMetadataEntry(metadataEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws MuxerException {
|
||||||
|
if (videoDurationUs != C.TIME_UNSET && videoTrackId != TRACK_ID_UNSET) {
|
||||||
|
BufferInfo bufferInfo = new BufferInfo();
|
||||||
|
bufferInfo.set(
|
||||||
|
/* newOffset= */ 0,
|
||||||
|
/* newSize= */ 0,
|
||||||
|
videoDurationUs,
|
||||||
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||||
|
writeSampleData(videoTrackId, ByteBuffer.allocateDirect(0), bufferInfo);
|
||||||
|
}
|
||||||
|
muxer.close();
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,6 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.container.Mp4OrientationData;
|
import androidx.media3.container.Mp4OrientationData;
|
||||||
import androidx.media3.muxer.FragmentedMp4Muxer;
|
|
||||||
import androidx.media3.muxer.Mp4Muxer;
|
import androidx.media3.muxer.Mp4Muxer;
|
||||||
import androidx.media3.muxer.Muxer;
|
import androidx.media3.muxer.Muxer;
|
||||||
import androidx.media3.muxer.MuxerException;
|
import androidx.media3.muxer.MuxerException;
|
||||||
@ -41,10 +40,9 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** {@link Muxer} implementation that uses an {@link Mp4Muxer} or {@link FragmentedMp4Muxer}. */
|
/** {@link Muxer} implementation that uses an {@link Mp4Muxer}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class InAppMuxer implements Muxer {
|
public final class InAppMp4Muxer implements Muxer {
|
||||||
|
|
||||||
/** Provides {@linkplain Metadata.Entry metadata} to add in the output MP4 file. */
|
/** Provides {@linkplain Metadata.Entry metadata} to add in the output MP4 file. */
|
||||||
public interface MetadataProvider {
|
public interface MetadataProvider {
|
||||||
|
|
||||||
@ -60,57 +58,9 @@ public final class InAppMuxer implements Muxer {
|
|||||||
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@link Muxer.Factory} for {@link InAppMuxer}. */
|
/** {@link Muxer.Factory} for {@link InAppMp4Muxer}. */
|
||||||
public static final class Factory implements Muxer.Factory {
|
public static final class Factory implements Muxer.Factory {
|
||||||
|
// TODO: b/372417042 - Move these lists to Mp4Muxer.
|
||||||
/** A builder for {@link Factory} instances. */
|
|
||||||
public static final class Builder {
|
|
||||||
@Nullable private MetadataProvider metadataProvider;
|
|
||||||
private boolean outputFragmentedMp4;
|
|
||||||
private long fragmentDurationMs;
|
|
||||||
|
|
||||||
/** Creates a {@link Builder} instance with default values. */
|
|
||||||
public Builder() {
|
|
||||||
fragmentDurationMs = C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an implementation of {@link MetadataProvider}.
|
|
||||||
*
|
|
||||||
* <p>The default value is {@code null}.
|
|
||||||
*
|
|
||||||
* <p>If the value is not set then the {@linkplain Metadata.Entry metadata} from the input
|
|
||||||
* file is set as it is in the output file.
|
|
||||||
*/
|
|
||||||
@CanIgnoreReturnValue
|
|
||||||
public Builder setMetadataProvider(MetadataProvider metadataProvider) {
|
|
||||||
this.metadataProvider = metadataProvider;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets whether to output a fragmented MP4. */
|
|
||||||
@CanIgnoreReturnValue
|
|
||||||
public Builder setOutputFragmentedMp4(boolean outputFragmentedMp4) {
|
|
||||||
this.outputFragmentedMp4 = outputFragmentedMp4;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the fragment duration (in milliseconds) if the output file is {@link
|
|
||||||
* #setOutputFragmentedMp4(boolean) fragmented}.
|
|
||||||
*/
|
|
||||||
@CanIgnoreReturnValue
|
|
||||||
public Builder setFragmentDurationMs(long fragmentDurationMs) {
|
|
||||||
this.fragmentDurationMs = fragmentDurationMs;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Builds a {@link Factory} instance. */
|
|
||||||
public Factory build() {
|
|
||||||
return new Factory(metadataProvider, outputFragmentedMp4, fragmentDurationMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A list of supported video sample MIME types. */
|
/** A list of supported video sample MIME types. */
|
||||||
private static final ImmutableList<String> SUPPORTED_VIDEO_SAMPLE_MIME_TYPES =
|
private static final ImmutableList<String> SUPPORTED_VIDEO_SAMPLE_MIME_TYPES =
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
@ -130,18 +80,21 @@ public final class InAppMuxer implements Muxer {
|
|||||||
MimeTypes.AUDIO_VORBIS);
|
MimeTypes.AUDIO_VORBIS);
|
||||||
|
|
||||||
@Nullable private final MetadataProvider metadataProvider;
|
@Nullable private final MetadataProvider metadataProvider;
|
||||||
private final boolean outputFragmentedMp4;
|
|
||||||
private final long fragmentDurationMs;
|
|
||||||
|
|
||||||
private long videoDurationUs;
|
private long videoDurationUs;
|
||||||
|
|
||||||
private Factory(
|
/** Creates an instance with default values. */
|
||||||
@Nullable MetadataProvider metadataProvider,
|
public Factory() {
|
||||||
boolean outputFragmentedMp4,
|
this(/* metadataProvider= */ null);
|
||||||
long fragmentDurationMs) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param metadataProvider A {@link MetadataProvider}.
|
||||||
|
*/
|
||||||
|
public Factory(@Nullable MetadataProvider metadataProvider) {
|
||||||
this.metadataProvider = metadataProvider;
|
this.metadataProvider = metadataProvider;
|
||||||
this.outputFragmentedMp4 = outputFragmentedMp4;
|
|
||||||
this.fragmentDurationMs = fragmentDurationMs;
|
|
||||||
videoDurationUs = C.TIME_UNSET;
|
videoDurationUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +120,7 @@ public final class InAppMuxer implements Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InAppMuxer create(String path) throws MuxerException {
|
public InAppMp4Muxer create(String path) throws MuxerException {
|
||||||
FileOutputStream outputStream;
|
FileOutputStream outputStream;
|
||||||
try {
|
try {
|
||||||
outputStream = new FileOutputStream(path);
|
outputStream = new FileOutputStream(path);
|
||||||
@ -175,23 +128,14 @@ public final class InAppMuxer implements Muxer {
|
|||||||
throw new MuxerException("Error creating file output stream", e);
|
throw new MuxerException("Error creating file output stream", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Muxer muxer = null;
|
|
||||||
if (outputFragmentedMp4) {
|
|
||||||
FragmentedMp4Muxer.Builder builder = new FragmentedMp4Muxer.Builder(outputStream);
|
|
||||||
if (fragmentDurationMs != C.TIME_UNSET) {
|
|
||||||
builder.setFragmentDurationMs(fragmentDurationMs);
|
|
||||||
}
|
|
||||||
muxer = builder.build();
|
|
||||||
} else {
|
|
||||||
Mp4Muxer.Builder builder = new Mp4Muxer.Builder(outputStream);
|
Mp4Muxer.Builder builder = new Mp4Muxer.Builder(outputStream);
|
||||||
if (videoDurationUs != C.TIME_UNSET) {
|
if (videoDurationUs != C.TIME_UNSET) {
|
||||||
builder.setLastSampleDurationBehavior(
|
builder.setLastSampleDurationBehavior(
|
||||||
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS);
|
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS);
|
||||||
}
|
}
|
||||||
muxer = builder.build();
|
Mp4Muxer muxer = builder.build();
|
||||||
}
|
|
||||||
|
|
||||||
return new InAppMuxer(muxer, metadataProvider, videoDurationUs);
|
return new InAppMp4Muxer(muxer, metadataProvider, videoDurationUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -205,18 +149,18 @@ public final class InAppMuxer implements Muxer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = "InAppMuxer";
|
private static final String TAG = "InAppMp4Muxer";
|
||||||
private static final int TRACK_ID_UNSET = -1;
|
private static final int TRACK_ID_UNSET = -1;
|
||||||
|
|
||||||
private final Muxer muxer;
|
private final Mp4Muxer 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;
|
||||||
|
|
||||||
private int videoTrackId;
|
private int videoTrackId;
|
||||||
|
|
||||||
private InAppMuxer(
|
private InAppMp4Muxer(
|
||||||
Muxer muxer, @Nullable MetadataProvider metadataProvider, long videoDurationUs) {
|
Mp4Muxer muxer, @Nullable MetadataProvider metadataProvider, long videoDurationUs) {
|
||||||
this.muxer = muxer;
|
this.muxer = muxer;
|
||||||
this.metadataProvider = metadataProvider;
|
this.metadataProvider = metadataProvider;
|
||||||
this.videoDurationUs = videoDurationUs;
|
this.videoDurationUs = videoDurationUs;
|
@ -66,7 +66,7 @@ public final class EncodedSampleExporterTest {
|
|||||||
new TransformationRequest.Builder().build(),
|
new TransformationRequest.Builder().build(),
|
||||||
new MuxerWrapper(
|
new MuxerWrapper(
|
||||||
/* outputPath= */ "unused",
|
/* outputPath= */ "unused",
|
||||||
new InAppMuxer.Factory.Builder().build(),
|
new InAppMp4Muxer.Factory(),
|
||||||
mock(MuxerWrapper.Listener.class),
|
mock(MuxerWrapper.Listener.class),
|
||||||
MuxerWrapper.MUXER_MODE_DEFAULT,
|
MuxerWrapper.MUXER_MODE_DEFAULT,
|
||||||
/* dropSamplesBeforeFirstVideoSample= */ false,
|
/* dropSamplesBeforeFirstVideoSample= */ false,
|
||||||
|
@ -39,9 +39,9 @@ import org.robolectric.ParameterizedRobolectricTestRunner;
|
|||||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
|
||||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||||
|
|
||||||
/** End to end parameterized tests for {@link Transformer} with {@link InAppMuxer}. */
|
/** End to end parameterized tests for {@link Transformer} with {@link InAppMp4Muxer}. */
|
||||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
public class TransformerWithInAppMuxerEndToEndParameterizedTest {
|
public class TransformerWithInAppMp4MuxerEndToEndParameterizedTest {
|
||||||
|
|
||||||
private static final String H263_3GP = "mp4/bbb_176x144_128kbps_15fps_h263.3gp";
|
private static final String H263_3GP = "mp4/bbb_176x144_128kbps_15fps_h263.3gp";
|
||||||
private static final String H264_MP4 = "mp4/sample_no_bframes.mp4";
|
private static final String H264_MP4 = "mp4/sample_no_bframes.mp4";
|
||||||
@ -84,15 +84,13 @@ public class TransformerWithInAppMuxerEndToEndParameterizedTest {
|
|||||||
@Test
|
@Test
|
||||||
public void transmux_mp4File_outputMatchesExpected() throws Exception {
|
public void transmux_mp4File_outputMatchesExpected() throws Exception {
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(
|
||||||
.setMetadataProvider(
|
|
||||||
metadataEntries ->
|
metadataEntries ->
|
||||||
// Add timestamp to make output file deterministic.
|
// Add timestamp to make output file deterministic.
|
||||||
metadataEntries.add(
|
metadataEntries.add(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 3_000_000_000L,
|
/* creationTimestampSeconds= */ 3_000_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 4_000_000_000L)))
|
/* modificationTimestampSeconds= */ 4_000_000_000L)));
|
||||||
.build();
|
|
||||||
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
@ -47,9 +47,9 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/** End-to-end test for {@link Transformer} with {@link InAppMuxer}. */
|
/** End-to-end test for {@link Transformer} with {@link InAppMp4Muxer}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
public class TransformerWithInAppMp4MuxerEndToEndTest {
|
||||||
private static final String MP4_FILE_PATH = "asset:///media/mp4/sample_no_bframes.mp4";
|
private static final String MP4_FILE_PATH = "asset:///media/mp4/sample_no_bframes.mp4";
|
||||||
|
|
||||||
@Rule public final TemporaryFolder outputDir = new TemporaryFolder();
|
@Rule public final TemporaryFolder outputDir = new TemporaryFolder();
|
||||||
@ -67,15 +67,13 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
String tsFilePath = "asset:///media/ts/sample_h264.ts";
|
String tsFilePath = "asset:///media/ts/sample_h264.ts";
|
||||||
String tsFileName = "ts/sample_h264.ts";
|
String tsFileName = "ts/sample_h264.ts";
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(
|
||||||
.setMetadataProvider(
|
|
||||||
metadataEntries ->
|
metadataEntries ->
|
||||||
// Add timestamp to make output file deterministic.
|
// Add timestamp to make output file deterministic.
|
||||||
metadataEntries.add(
|
metadataEntries.add(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 3_000_000_000L,
|
/* creationTimestampSeconds= */ 3_000_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 4_000_000_000L)))
|
/* modificationTimestampSeconds= */ 4_000_000_000L)));
|
||||||
.build();
|
|
||||||
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
@ -104,14 +102,11 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
Mp4LocationData expectedLocationData =
|
Mp4LocationData expectedLocationData =
|
||||||
new Mp4LocationData(/* latitude= */ 45f, /* longitude= */ -90f);
|
new Mp4LocationData(/* latitude= */ 45f, /* longitude= */ -90f);
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(
|
||||||
.setMetadataProvider(
|
|
||||||
metadataEntries -> {
|
metadataEntries -> {
|
||||||
metadataEntries.removeIf(
|
metadataEntries.removeIf((Metadata.Entry entry) -> entry instanceof Mp4LocationData);
|
||||||
(Metadata.Entry entry) -> entry instanceof Mp4LocationData);
|
|
||||||
metadataEntries.add(expectedLocationData);
|
metadataEntries.add(expectedLocationData);
|
||||||
})
|
});
|
||||||
.build();
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
||||||
@ -130,9 +125,7 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
String xmpSampleData = "media/xmp/sample_datetime_xmp.xmp";
|
String xmpSampleData = "media/xmp/sample_datetime_xmp.xmp";
|
||||||
byte[] xmpData = androidx.media3.test.utils.TestUtil.getByteArray(context, xmpSampleData);
|
byte[] xmpData = androidx.media3.test.utils.TestUtil.getByteArray(context, xmpSampleData);
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(metadataEntries -> metadataEntries.add(new XmpData(xmpData)));
|
||||||
.setMetadataProvider(metadataEntries -> metadataEntries.add(new XmpData(xmpData)))
|
|
||||||
.build();
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
||||||
@ -153,9 +146,7 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
/* value= */ Util.toByteArray(captureFps),
|
/* value= */ Util.toByteArray(captureFps),
|
||||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(metadataEntries -> metadataEntries.add(expectedCaptureFps));
|
||||||
.setMetadataProvider(metadataEntries -> metadataEntries.add(expectedCaptureFps))
|
|
||||||
.build();
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
||||||
@ -181,9 +172,7 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
/* creationTimestampSeconds= */ 3_000_000_000L,
|
/* creationTimestampSeconds= */ 3_000_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 4_000_000_000L);
|
/* modificationTimestampSeconds= */ 4_000_000_000L);
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(metadataEntries -> metadataEntries.add(expectedTimestampData));
|
||||||
.setMetadataProvider(metadataEntries -> metadataEntries.add(expectedTimestampData))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
@ -212,13 +201,11 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
/* value= */ Util.toByteArray(600.0f),
|
/* value= */ Util.toByteArray(600.0f),
|
||||||
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
||||||
Muxer.Factory inAppMuxerFactory =
|
Muxer.Factory inAppMuxerFactory =
|
||||||
new InAppMuxer.Factory.Builder()
|
new InAppMp4Muxer.Factory(
|
||||||
.setMetadataProvider(
|
|
||||||
metadataEntries -> {
|
metadataEntries -> {
|
||||||
metadataEntries.add(expectedStringMetadata);
|
metadataEntries.add(expectedStringMetadata);
|
||||||
metadataEntries.add(expectedFloatMetadata);
|
metadataEntries.add(expectedFloatMetadata);
|
||||||
})
|
});
|
||||||
.build();
|
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
new TestTransformerBuilder(context).setMuxerFactory(inAppMuxerFactory).build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_PATH));
|
||||||
@ -248,7 +235,7 @@ public class TransformerWithInAppMuxerEndToEndNonParameterizedTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transmux_withSettingVideoDuration_writesCorrectVideoDuration() throws Exception {
|
public void transmux_withSettingVideoDuration_writesCorrectVideoDuration() throws Exception {
|
||||||
InAppMuxer.Factory inAppMuxerFactory = new InAppMuxer.Factory.Builder().build();
|
InAppMp4Muxer.Factory inAppMuxerFactory = new InAppMp4Muxer.Factory();
|
||||||
long expectedDurationUs = 2_000_000L;
|
long expectedDurationUs = 2_000_000L;
|
||||||
inAppMuxerFactory.setVideoDurationUs(expectedDurationUs);
|
inAppMuxerFactory.setVideoDurationUs(expectedDurationUs);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
Loading…
x
Reference in New Issue
Block a user