Add export settings to demo-composition

Added UI and logic implementation for the following export settings:
* Output audio MIME type
* Output video MIME type
* Enable debug tracing
* Use Media3 Muxer
* Produce fragmented MP4

The settings are shown in a dialog when `Export` button is clicked.

PiperOrigin-RevId: 685648147
This commit is contained in:
shahddaghash 2024-10-14 03:43:30 -07:00 committed by Copybara-Service
parent 45d2bc39ae
commit 5acb483222
4 changed files with 215 additions and 4 deletions

View File

@ -54,6 +54,7 @@ dependencies {
implementation project(modulePrefix + 'lib-effect') implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-muxer')
implementation project(modulePrefix + 'lib-transformer') implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion

View File

@ -23,7 +23,10 @@ import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_S
import android.app.Activity; import android.app.Activity;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -34,12 +37,14 @@ import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.AppCompatTextView;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.LanczosResample; import androidx.media3.effect.LanczosResample;
import androidx.media3.effect.Presentation; import androidx.media3.effect.Presentation;
import androidx.media3.effect.RgbFilter; import androidx.media3.effect.RgbFilter;
@ -50,6 +55,7 @@ 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.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;
@ -118,7 +124,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
exportInformationTextView = findViewById(R.id.export_information_text); exportInformationTextView = findViewById(R.id.export_information_text);
exportButton = findViewById(R.id.composition_export_button); exportButton = findViewById(R.id.composition_export_button);
exportButton.setOnClickListener(view -> exportComposition()); exportButton.setOnClickListener(view -> showExportSettings());
AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox); AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox);
backgroundAudioCheckBox.setOnCheckedChangeListener( backgroundAudioCheckBox.setOnCheckedChangeListener(
@ -231,7 +237,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner); Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
int selectedHdrMode = int selectedHdrMode =
HDR_MODE_DESCRIPTIONS.get(String.valueOf(hdrModeSpinner.getSelectedItem())); HDR_MODE_DESCRIPTIONS.get(String.valueOf(hdrModeSpinner.getSelectedItem()));
return new Composition.Builder(/* sequences= */ compositionSequences) return new Composition.Builder(compositionSequences)
.setEffects( .setEffects(
new Effects( new Effects(
/* audioProcessors= */ ImmutableList.of(sampleRateChanger), /* audioProcessors= */ ImmutableList.of(sampleRateChanger),
@ -302,7 +308,67 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
} }
} }
private void exportComposition() { private void showExportSettings() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
View exportSettingsDialogView = inflater.inflate(R.layout.export_settings, null);
alertDialogBuilder
.setView(exportSettingsDialogView)
.setTitle(R.string.export_settings)
.setPositiveButton(
R.string.export, (dialog, id) -> exportComposition(exportSettingsDialogView))
.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
ArrayAdapter<String> audioMimeAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner audioMimeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
audioMimeSpinner.setAdapter(audioMimeAdapter);
audioMimeAdapter.addAll(
SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
ArrayAdapter<String> videoMimeAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner videoMimeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
videoMimeSpinner.setAdapter(videoMimeAdapter);
videoMimeAdapter.addAll(
SAME_AS_INPUT_OPTION,
MimeTypes.VIDEO_H263,
MimeTypes.VIDEO_H264,
MimeTypes.VIDEO_H265,
MimeTypes.VIDEO_MP4V,
MimeTypes.VIDEO_AV1);
CheckBox enableDebugTracingCheckBox =
exportSettingsDialogView.findViewById(R.id.enable_debug_tracing_checkbox);
enableDebugTracingCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
// Connect producing fragmented MP4 to using Media3 Muxer
CheckBox useMedia3MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
CheckBox produceFragmentedMp4CheckBox =
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
useMedia3MuxerCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (!isChecked) {
produceFragmentedMp4CheckBox.setChecked(false);
}
});
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3MuxerCheckBox.setChecked(true);
}
});
AlertDialog dialog = alertDialogBuilder.create();
dialog.show();
}
private void exportComposition(View exportSettingsDialogView) {
// Cancel and clean up files from any ongoing export. // Cancel and clean up files from any ongoing export.
cancelExport(); cancelExport();
@ -323,8 +389,33 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
} }
String filePath = outputFile.getAbsolutePath(); String filePath = outputFile.getAbsolutePath();
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
Spinner audioMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
String selectedAudioMimeType = String.valueOf(audioMimeTypeSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
transformerBuilder.setAudioMimeType(selectedAudioMimeType);
}
Spinner videoMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
String selectedVideoMimeType = String.valueOf(videoMimeTypeSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
transformerBuilder.setVideoMimeType(selectedVideoMimeType);
}
CheckBox useMedia3MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
CheckBox produceFragmentedMp4CheckBox =
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
if (useMedia3MuxerCheckBox.isChecked()) {
transformerBuilder.setMuxerFactory(
new InAppMuxer.Factory.Builder()
.setOutputFragmentedMp4(produceFragmentedMp4CheckBox.isChecked())
.build());
}
transformer = transformer =
new Transformer.Builder(/* context= */ this) transformerBuilder
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
@ -333,6 +424,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
long elapsedTimeMs = exportStopwatch.elapsed(TimeUnit.MILLISECONDS); long elapsedTimeMs = exportStopwatch.elapsed(TimeUnit.MILLISECONDS);
String details = String details =
getString(R.string.export_completed, elapsedTimeMs / 1000.f, filePath); getString(R.string.export_completed, elapsedTimeMs / 1000.f, filePath);
Log.d(TAG, DebugTraceUtil.generateTraceSummary());
Log.i(TAG, details); Log.i(TAG, details);
exportInformationTextView.setText(details); exportInformationTextView.setText(details);
@ -361,6 +453,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
Toast.LENGTH_LONG) Toast.LENGTH_LONG)
.show(); .show();
Log.e(TAG, "Export error", exportException); Log.e(TAG, "Export error", exportException);
Log.d(TAG, DebugTraceUtil.generateTraceSummary());
exportInformationTextView.setText(R.string.export_error); exportInformationTextView.setText(R.string.export_error);
} }
}) })

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/export_settings_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp"
android:layout_marginTop="12dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/output_audio_mime_type"/>
<Spinner
android:id="@+id/audio_mime_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/output_video_mime_type"/>
<Spinner
android:id="@+id/video_mime_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/enable_debug_tracing"/>
<CheckBox
android:id="@+id/enable_debug_tracing_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/use_media3_muxer"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/use_media3_muxer_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/produce_fragmented_mp4"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/produce_fragmented_mp4_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -29,4 +29,11 @@
<string name="output_video_resolution" translatable="false">Output video resolution</string> <string name="output_video_resolution" translatable="false">Output video resolution</string>
<string name="hdr_mode" translatable="false">HDR mode</string> <string name="hdr_mode" translatable="false">HDR mode</string>
<string name="ok" translatable="false">OK</string> <string name="ok" translatable="false">OK</string>
<string name="cancel" translatable="false">Cancel</string>
<string name="export_settings" translatable="false">Export Settings</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="enable_debug_tracing" translatable="false">Enable debug tracing</string>
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
</resources> </resources>