mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
45d2bc39ae
commit
5acb483222
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
110
demos/composition/src/main/res/layout/export_settings.xml
Normal file
110
demos/composition/src/main/res/layout/export_settings.xml
Normal 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>
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user