mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Support setting H.264 level
Try to follow recommendations in https://developer.android.com/media/optimize/sharing more closely For maximum compatibility the H.264 level should be less than or equal to 4.1 PiperOrigin-RevId: 658755139
This commit is contained in:
parent
b09cea9e3a
commit
a79b80fcee
@ -15,7 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer.mh;
|
package androidx.media3.transformer.mh;
|
||||||
|
|
||||||
|
import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel41;
|
||||||
|
import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
import static androidx.media3.common.util.MediaFormatUtil.createFormatFromMediaFormat;
|
||||||
import static androidx.media3.common.util.Util.SDK_INT;
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
|
import static androidx.media3.exoplayer.mediacodec.MediaCodecUtil.getCodecProfileAndLevel;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.FORCE_TRANSCODE_VIDEO_EFFECTS;
|
import static androidx.media3.transformer.AndroidTestUtil.FORCE_TRANSCODE_VIDEO_EFFECTS;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_4K60_PORTRAIT;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_4K60_PORTRAIT;
|
||||||
@ -24,6 +29,7 @@ import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_BT2020_SDR;
|
|||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_SEF;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_SEF;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_SEF_H265;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_SEF_H265;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS;
|
||||||
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_TRIM_OPTIMIZATION_PIXEL;
|
import static androidx.media3.transformer.AndroidTestUtil.MP4_TRIM_OPTIMIZATION_PIXEL;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported;
|
import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported;
|
||||||
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
|
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
|
||||||
@ -34,7 +40,9 @@ import static org.junit.Assume.assumeFalse;
|
|||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -42,6 +50,7 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.effect.Presentation;
|
import androidx.media3.effect.Presentation;
|
||||||
import androidx.media3.effect.ScaleAndRotateTransformation;
|
import androidx.media3.effect.ScaleAndRotateTransformation;
|
||||||
|
import androidx.media3.exoplayer.MediaExtractorCompat;
|
||||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||||
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
|
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
|
||||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||||
@ -438,4 +447,43 @@ public class ExportTest {
|
|||||||
int inputVideoLevel = 41;
|
int inputVideoLevel = 41;
|
||||||
assertThat((int) sps[spsLevelIndex]).isAtLeast(inputVideoLevel);
|
assertThat((int) sps[spsLevelIndex]).isAtLeast(inputVideoLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void export_setEncodingProfileLevel_changesProfileAndLevel() throws Exception {
|
||||||
|
assumeTrue(
|
||||||
|
"Android encoding guidelines recommend H.264 baseline profile prior to API 25",
|
||||||
|
Util.SDK_INT >= 25);
|
||||||
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context)
|
||||||
|
.setEncoderFactory(
|
||||||
|
new ForceEncodeEncoderFactory(
|
||||||
|
new DefaultEncoderFactory.Builder(context)
|
||||||
|
.setRequestedVideoEncoderSettings(
|
||||||
|
new VideoEncoderSettings.Builder()
|
||||||
|
.setEncodingProfileLevel(AVCProfileHigh, AVCLevel41)
|
||||||
|
.build())
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S.uri)
|
||||||
|
.build();
|
||||||
|
EditedMediaItem editedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
|
||||||
|
|
||||||
|
ExportTestResult result =
|
||||||
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
|
.build()
|
||||||
|
.run(testId, editedMediaItem);
|
||||||
|
|
||||||
|
MediaExtractorCompat mediaExtractor = new MediaExtractorCompat(context);
|
||||||
|
mediaExtractor.setDataSource(Uri.parse(result.filePath), 0);
|
||||||
|
checkState(mediaExtractor.getTrackCount() == 1);
|
||||||
|
MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
|
||||||
|
Format format = createFormatFromMediaFormat(mediaFormat);
|
||||||
|
Pair<Integer, Integer> profileAndLevel = getCodecProfileAndLevel(format);
|
||||||
|
assertThat(profileAndLevel.first).isAtMost(AVCProfileHigh);
|
||||||
|
assertThat(profileAndLevel.second).isAtMost(AVCLevel41);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,7 +308,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
|||||||
|
|
||||||
if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE
|
if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE
|
||||||
&& supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE
|
&& supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE
|
||||||
&& Util.SDK_INT >= 23) {
|
&& Util.SDK_INT >= 24) {
|
||||||
|
// For API levels below 24, setting profile and level can lead to failures in MediaCodec
|
||||||
|
// configuration. The encoder selects the profile/level when we don't set them.
|
||||||
// Set profile and level at the same time to maximize compatibility, or the encoder will pick
|
// Set profile and level at the same time to maximize compatibility, or the encoder will pick
|
||||||
// the values.
|
// the values.
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile);
|
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile);
|
||||||
@ -586,9 +588,11 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applying suggested profile/level settings from
|
* Applying suggested profile settings from
|
||||||
* https://developer.android.com/media/optimize/sharing#b-frames_and_encoding_profiles
|
* https://developer.android.com/media/optimize/sharing#b-frames_and_encoding_profiles
|
||||||
*
|
*
|
||||||
|
* <p>Sets H.264 level only if it wasn't set previously.
|
||||||
|
*
|
||||||
* <p>The adjustment is applied in-place to {@code mediaFormat}.
|
* <p>The adjustment is applied in-place to {@code mediaFormat}.
|
||||||
*/
|
*/
|
||||||
private static void adjustMediaFormatForH264EncoderSettings(
|
private static void adjustMediaFormatForH264EncoderSettings(
|
||||||
@ -614,7 +618,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
|||||||
// Use the highest supported profile. Don't configure B-frames, because it doesn't work on
|
// Use the highest supported profile. Don't configure B-frames, because it doesn't work on
|
||||||
// some devices.
|
// some devices.
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
|
if (!mediaFormat.containsKey(MediaFormat.KEY_LEVEL)) {
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (Util.SDK_INT >= 26 && !deviceNeedsNoH264HighProfileWorkaround()) {
|
} else if (Util.SDK_INT >= 26 && !deviceNeedsNoH264HighProfileWorkaround()) {
|
||||||
int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
|
int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
|
||||||
@ -626,7 +632,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
|||||||
// MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these
|
// MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these
|
||||||
// system versions.
|
// system versions.
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
|
if (!mediaFormat.containsKey(MediaFormat.KEY_LEVEL)) {
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel);
|
||||||
|
}
|
||||||
// TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after in-app muxing
|
// TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after in-app muxing
|
||||||
// is the default and it supports B-frames.
|
// is the default and it supports B-frames.
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1);
|
mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1);
|
||||||
@ -640,7 +648,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
|||||||
// Use the baseline profile for safest results, as encoding in baseline is required per
|
// Use the baseline profile for safest results, as encoding in baseline is required per
|
||||||
// https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding
|
// https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel);
|
if (!mediaFormat.containsKey(MediaFormat.KEY_LEVEL)) {
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// For API levels below 24, setting profile and level can lead to failures in MediaCodec
|
// For API levels below 24, setting profile and level can lead to failures in MediaCodec
|
||||||
// configuration. The encoder selects the profile/level when we don't set them.
|
// configuration. The encoder selects the profile/level when we don't set them.
|
||||||
|
@ -137,8 +137,8 @@ public final class VideoEncoderSettings {
|
|||||||
* <p>The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel},
|
* <p>The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel},
|
||||||
* or {@link #NO_VALUE}.
|
* or {@link #NO_VALUE}.
|
||||||
*
|
*
|
||||||
* <p>Profile and level settings will be ignored when using {@link DefaultEncoderFactory} and
|
* <p>Profile settings will be ignored when using {@link DefaultEncoderFactory} and encoding to
|
||||||
* encoding to H264.
|
* H264.
|
||||||
*
|
*
|
||||||
* @param encodingProfile The {@link VideoEncoderSettings#profile}.
|
* @param encodingProfile The {@link VideoEncoderSettings#profile}.
|
||||||
* @param encodingLevel The {@link VideoEncoderSettings#level}.
|
* @param encodingLevel The {@link VideoEncoderSettings#level}.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user