Fix a concurrency issue that shader capacity is set off GL thread

Also add test to cover transitions between BT709 and 601.

PiperOrigin-RevId: 641224971
This commit is contained in:
claincly 2024-06-07 05:56:46 -07:00 committed by Copybara-Service
parent 38a7229d96
commit 39e572ad6d
5 changed files with 187 additions and 32 deletions

View File

@ -191,8 +191,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) { public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
checkState(samplingGlShaderProgram instanceof ExternalShaderProgram); checkState(samplingGlShaderProgram instanceof ExternalShaderProgram);
externalShaderProgramInputCapacity.set(0); videoFrameProcessingTaskExecutor.submit(
this.externalShaderProgram = (ExternalShaderProgram) samplingGlShaderProgram; () -> {
externalShaderProgramInputCapacity.set(0);
this.externalShaderProgram = (ExternalShaderProgram) samplingGlShaderProgram;
});
} }
@Override @Override

View File

@ -103,8 +103,8 @@ public final class AndroidTestUtil {
// ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4 // ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4
public static final int MP4_ASSET_FRAME_COUNT = 30; public static final int MP4_ASSET_FRAME_COUNT = 30;
public static final String BT601_ASSET_URI_STRING = "asset:///media/mp4/bt601.mov"; public static final String BT601_MOV_ASSET_URI_STRING = "asset:///media/mp4/bt601.mov";
public static final Format BT601_ASSET_FORMAT = public static final Format BT601_MOV_ASSET_FORMAT =
new Format.Builder() new Format.Builder()
.setSampleMimeType(VIDEO_H264) .setSampleMimeType(VIDEO_H264)
.setWidth(640) .setWidth(640)
@ -119,6 +119,25 @@ public final class AndroidTestUtil {
.setCodecs("avc1.4D001E") .setCodecs("avc1.4D001E")
.build(); .build();
public static final String BT601_MP4_ASSET_URI_STRING = "asset:///media/mp4/bt601.mp4";
public static final Format BT601_MP4_ASSET_FORMAT =
new Format.Builder()
.setSampleMimeType(VIDEO_H264)
.setWidth(360)
.setHeight(240)
.setFrameRate(29.97f)
.setColorInfo(
new ColorInfo.Builder()
.setColorSpace(C.COLOR_SPACE_BT601)
.setColorRange(C.COLOR_RANGE_LIMITED)
.setColorTransfer(C.COLOR_TRANSFER_SDR)
.build())
.setCodecs("avc1.42C00D")
.build();
// Result of the following command for BT601_MP4_ASSET_URI_STRING
// ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames bt601.mp4
public static final int BT601_MP4_ASSET_FRAME_COUNT = 30;
public static final String MP4_PORTRAIT_ASSET_URI_STRING = public static final String MP4_PORTRAIT_ASSET_URI_STRING =
"asset:///media/mp4/sample_portrait.mp4"; "asset:///media/mp4/sample_portrait.mp4";
public static final Format MP4_PORTRAIT_ASSET_FORMAT = public static final Format MP4_PORTRAIT_ASSET_FORMAT =

View File

@ -17,6 +17,9 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.transformer.AndroidTestUtil.BT601_MP4_ASSET_FORMAT;
import static androidx.media3.transformer.AndroidTestUtil.BT601_MP4_ASSET_FRAME_COUNT;
import static androidx.media3.transformer.AndroidTestUtil.BT601_MP4_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FORMAT;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FRAME_COUNT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FRAME_COUNT;
@ -28,7 +31,10 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.effect.Presentation; import androidx.media3.effect.Presentation;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -51,6 +57,16 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TransformerMixedInputEndToEndTest { public class TransformerMixedInputEndToEndTest {
// Image by default are encoded in H265 and BT709 SDR.
private static final Format IMAGE_VIDEO_ENCODING_FORMAT =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_H265)
.setFrameRate(30.f)
.setWidth(480)
.setHeight(360)
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.build();
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Rule public final TestName testName = new TestName(); @Rule public final TestName testName = new TestName();
@ -65,7 +81,10 @@ public class TransformerMixedInputEndToEndTest {
public void videoEditing_withImageThenVideoInputs_completesWithCorrectFrameCount() public void videoEditing_withImageThenVideoInputs_completesWithCorrectFrameCount()
throws Exception { throws Exception {
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT); context,
testId,
/* inputFormat= */ MP4_ASSET_FORMAT,
/* outputFormat= */ IMAGE_VIDEO_ENCODING_FORMAT);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory( .setEncoderFactory(
@ -116,10 +135,18 @@ public class TransformerMixedInputEndToEndTest {
@Test @Test
public void public void
videoEditing_withComplexVideoAndImageInputsEndWithVideo_completesWithCorrectFrameCount() videoEditing_withComplexMixedColorSpaceSdrVideoAndImageInputsEndWithVideo_completesWithCorrectFrameCount()
throws Exception { throws Exception {
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT); context,
testId,
/* inputFormat= */ MP4_ASSET_FORMAT,
/* outputFormat= */ BT601_MP4_ASSET_FORMAT);
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ BT601_MP4_ASSET_FORMAT,
/* outputFormat= */ BT601_MP4_ASSET_FORMAT);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory( .setEncoderFactory(
@ -131,7 +158,9 @@ public class TransformerMixedInputEndToEndTest {
createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount); createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
EditedMediaItem imageEditedMediaItem2 = EditedMediaItem imageEditedMediaItem2 =
createImageEditedMediaItem(JPG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount); createImageEditedMediaItem(JPG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
EditedMediaItem videoEditedMediaItem = EditedMediaItem bt601VideoEditedMediaItem =
createVideoEditedMediaItem(BT601_MP4_ASSET_URI_STRING, /* height= */ 360);
EditedMediaItem bt709VideoEditedMediaItem =
createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 360); createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 360);
ExportTestResult result = ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
@ -139,25 +168,35 @@ public class TransformerMixedInputEndToEndTest {
.run( .run(
testId, testId,
buildComposition( buildComposition(
videoEditedMediaItem, bt601VideoEditedMediaItem,
videoEditedMediaItem, bt709VideoEditedMediaItem,
imageEditedMediaItem1, imageEditedMediaItem1,
imageEditedMediaItem2, imageEditedMediaItem2,
videoEditedMediaItem, bt709VideoEditedMediaItem,
imageEditedMediaItem1, imageEditedMediaItem1,
videoEditedMediaItem)); bt601VideoEditedMediaItem));
assertThat(result.exportResult.videoFrameCount) assertThat(result.exportResult.videoFrameCount)
.isEqualTo(3 * imageFrameCount + 4 * MP4_ASSET_FRAME_COUNT); .isEqualTo(
3 * imageFrameCount + 2 * MP4_ASSET_FRAME_COUNT + 2 * BT601_MP4_ASSET_FRAME_COUNT);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }
@Test @Test
public void public void
videoEditing_withComplexVideoAndImageInputsEndWithImage_completesWithCorrectFrameCount() videoEditing_withComplexMixedColorSpaceSdrVideoAndImageInputsEndWithImage_completesWithCorrectFrameCount()
throws Exception { throws Exception {
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT); context,
testId,
/* inputFormat= */ MP4_ASSET_FORMAT,
/* outputFormat= */ IMAGE_VIDEO_ENCODING_FORMAT);
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ BT601_MP4_ASSET_FORMAT,
/* outputFormat= */ IMAGE_VIDEO_ENCODING_FORMAT);
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory( .setEncoderFactory(
@ -167,9 +206,9 @@ public class TransformerMixedInputEndToEndTest {
int imageFrameCount = 34; int imageFrameCount = 34;
EditedMediaItem imageEditedMediaItem = EditedMediaItem imageEditedMediaItem =
createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount); createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
// Result of the following command: EditedMediaItem bt601VideoEditedMediaItem =
// ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4 createVideoEditedMediaItem(BT601_MP4_ASSET_URI_STRING, /* height= */ 480);
EditedMediaItem videoEditedMediaItem = EditedMediaItem bt709VideoEditedMediaItem =
createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 480); createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 480);
ExportTestResult result = ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
@ -178,15 +217,107 @@ public class TransformerMixedInputEndToEndTest {
testId, testId,
buildComposition( buildComposition(
imageEditedMediaItem, imageEditedMediaItem,
videoEditedMediaItem, bt709VideoEditedMediaItem,
videoEditedMediaItem, bt601VideoEditedMediaItem,
imageEditedMediaItem, imageEditedMediaItem,
imageEditedMediaItem, imageEditedMediaItem,
videoEditedMediaItem, bt601VideoEditedMediaItem,
imageEditedMediaItem)); imageEditedMediaItem));
assertThat(result.exportResult.videoFrameCount) assertThat(result.exportResult.videoFrameCount)
.isEqualTo(4 * imageFrameCount + 3 * MP4_ASSET_FRAME_COUNT); .isEqualTo(4 * imageFrameCount + MP4_ASSET_FRAME_COUNT + 2 * BT601_MP4_ASSET_FRAME_COUNT);
assertThat(new File(result.filePath).length()).isGreaterThan(0);
}
@Test
public void
videoEditing_withComplexVideoAndImageInputsEndWithVideo_completesWithCorrectFrameCount()
throws Exception {
assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT);
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ BT601_MP4_ASSET_FORMAT,
/* outputFormat= */ MP4_ASSET_FORMAT);
Transformer transformer =
new Transformer.Builder(context)
.setEncoderFactory(
new DefaultEncoderFactory.Builder(context).setEnableFallback(false).build())
.build();
int imageFrameCount = 33;
EditedMediaItem imageEditedMediaItem1 =
createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
EditedMediaItem imageEditedMediaItem2 =
createImageEditedMediaItem(JPG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
EditedMediaItem videoEditedMediaItem1 =
createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 360);
EditedMediaItem videoEditedMediaItem2 =
createVideoEditedMediaItem(BT601_MP4_ASSET_URI_STRING, /* height= */ 360);
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(
testId,
buildComposition(
videoEditedMediaItem1,
videoEditedMediaItem2,
imageEditedMediaItem1,
imageEditedMediaItem2,
videoEditedMediaItem1,
imageEditedMediaItem1,
videoEditedMediaItem2));
assertThat(result.exportResult.videoFrameCount)
.isEqualTo(
3 * imageFrameCount + 2 * MP4_ASSET_FRAME_COUNT + 2 * BT601_MP4_ASSET_FRAME_COUNT);
assertThat(new File(result.filePath).length()).isGreaterThan(0);
}
@Test
public void
videoEditing_withComplexVideoAndImageInputsEndWithImage_completesWithCorrectFrameCount()
throws Exception {
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ MP4_ASSET_FORMAT,
/* outputFormat= */ IMAGE_VIDEO_ENCODING_FORMAT);
assumeFormatsSupported(
context,
testId,
/* inputFormat= */ BT601_MP4_ASSET_FORMAT,
/* outputFormat= */ IMAGE_VIDEO_ENCODING_FORMAT);
Transformer transformer =
new Transformer.Builder(context)
.setEncoderFactory(
new DefaultEncoderFactory.Builder(context).setEnableFallback(false).build())
.build();
int imageFrameCount = 34;
EditedMediaItem imageEditedMediaItem =
createImageEditedMediaItem(PNG_ASSET_URI_STRING, /* frameCount= */ imageFrameCount);
EditedMediaItem videoEditedMediaItem1 =
createVideoEditedMediaItem(MP4_ASSET_URI_STRING, /* height= */ 480);
EditedMediaItem videoEditedMediaItem2 =
createVideoEditedMediaItem(BT601_MP4_ASSET_URI_STRING, /* height= */ 480);
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(
testId,
buildComposition(
imageEditedMediaItem,
videoEditedMediaItem1,
videoEditedMediaItem2,
imageEditedMediaItem,
imageEditedMediaItem,
videoEditedMediaItem2,
imageEditedMediaItem));
assertThat(result.exportResult.videoFrameCount)
.isEqualTo(4 * imageFrameCount + MP4_ASSET_FRAME_COUNT + 2 * BT601_MP4_ASSET_FRAME_COUNT);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }

View File

@ -21,8 +21,8 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.effect.DebugTraceUtil.EVENT_SURFACE_TEXTURE_TRANSFORM_FIX; import static androidx.media3.effect.DebugTraceUtil.EVENT_SURFACE_TEXTURE_TRANSFORM_FIX;
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
import static androidx.media3.transformer.AndroidTestUtil.BT601_ASSET_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.BT601_MOV_ASSET_FORMAT;
import static androidx.media3.transformer.AndroidTestUtil.BT601_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.BT601_MOV_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.JPG_PORTRAIT_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.JPG_PORTRAIT_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_AV1_VIDEO_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_AV1_VIDEO_FORMAT;
@ -228,18 +228,20 @@ public final class TransformerSequenceEffectTest {
assumeFormatsSupported( assumeFormatsSupported(
context, context,
testId, testId,
/* inputFormat= */ BT601_ASSET_FORMAT, /* inputFormat= */ BT601_MOV_ASSET_FORMAT,
/* outputFormat= */ BT601_ASSET_FORMAT); /* outputFormat= */ BT601_MOV_ASSET_FORMAT);
List<MediaCodecInfo> mediaCodecInfoList = List<MediaCodecInfo> mediaCodecInfoList =
MediaCodecSelector.DEFAULT.getDecoderInfos( MediaCodecSelector.DEFAULT.getDecoderInfos(
checkNotNull(BT601_ASSET_FORMAT.sampleMimeType), checkNotNull(BT601_MOV_ASSET_FORMAT.sampleMimeType),
/* requiresSecureDecoder= */ false, /* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false); /* requiresTunnelingDecoder= */ false);
Composition composition = Composition composition =
createComposition( createComposition(
/* presentation= */ null, /* presentation= */ null,
clippedVideo( clippedVideo(
BT601_ASSET_URI_STRING, NO_EFFECT, /* endPositionMs= */ C.MILLIS_PER_SECOND / 4)); BT601_MOV_ASSET_URI_STRING,
NO_EFFECT,
/* endPositionMs= */ C.MILLIS_PER_SECOND / 4));
boolean atLeastOneDecoderSucceeds = false; boolean atLeastOneDecoderSucceeds = false;
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
@ -556,12 +558,12 @@ public final class TransformerSequenceEffectTest {
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT); context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT);
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ BT601_ASSET_FORMAT, /* outputFormat= */ null); context, testId, /* inputFormat= */ BT601_MOV_ASSET_FORMAT, /* outputFormat= */ null);
Composition composition = Composition composition =
createComposition( createComposition(
Presentation.createForHeight(EXPORT_HEIGHT), Presentation.createForHeight(EXPORT_HEIGHT),
clippedVideo( clippedVideo(
BT601_ASSET_URI_STRING, BT601_MOV_ASSET_URI_STRING,
ImmutableList.of(RgbFilter.createInvertedFilter()), ImmutableList.of(RgbFilter.createInvertedFilter()),
SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS), SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS),
clippedVideo(MP4_ASSET_URI_STRING, NO_EFFECT, SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS)); clippedVideo(MP4_ASSET_URI_STRING, NO_EFFECT, SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS));
@ -581,12 +583,12 @@ public final class TransformerSequenceEffectTest {
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT); context, testId, /* inputFormat= */ MP4_ASSET_FORMAT, /* outputFormat= */ MP4_ASSET_FORMAT);
assumeFormatsSupported( assumeFormatsSupported(
context, testId, /* inputFormat= */ BT601_ASSET_FORMAT, /* outputFormat= */ null); context, testId, /* inputFormat= */ BT601_MOV_ASSET_FORMAT, /* outputFormat= */ null);
Composition composition = Composition composition =
createComposition( createComposition(
Presentation.createForHeight(EXPORT_HEIGHT), Presentation.createForHeight(EXPORT_HEIGHT),
clippedVideo( clippedVideo(
BT601_ASSET_URI_STRING, BT601_MOV_ASSET_URI_STRING,
ImmutableList.of(RgbFilter.createInvertedFilter()), ImmutableList.of(RgbFilter.createInvertedFilter()),
SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS), SINGLE_30_FPS_VIDEO_FRAME_THRESHOLD_MS),
oneFrameFromImage(JPG_ASSET_URI_STRING, NO_EFFECT)); oneFrameFromImage(JPG_ASSET_URI_STRING, NO_EFFECT));