From 2500d9184888772e8787e57396758aeb3349c152 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Thu, 10 Apr 2025 09:39:23 +0800 Subject: [PATCH] Add KEY_PROFILE, KEY_LEVEL support for AC-4 --- .../common/util/CodecSpecificDataUtil.java | 87 +++++++++++++++++++ .../audio/MediaCodecAudioRenderer.java | 17 +++- .../androidx/media3/extractor/Ac4Util.java | 23 +++++ 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java index 44c42ff85e..9c0451529e 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java @@ -62,6 +62,8 @@ public final class CodecSpecificDataUtil { private static final String CODEC_ID_AV01 = "av01"; // MP4A AAC. private static final String CODEC_ID_MP4A = "mp4a"; + // AC-4 + private static final String CODEC_ID_AC4 = "ac-4"; private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); @@ -299,6 +301,8 @@ public final class CodecSpecificDataUtil { return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo); case CODEC_ID_MP4A: return getAacCodecProfileAndLevel(format.codecs, parts); + case CODEC_ID_AC4: + return getAc4CodecProfileAndLevel(format.codecs, parts); default: return null; } @@ -635,6 +639,42 @@ public final class CodecSpecificDataUtil { return null; } + @Nullable + private static Pair getAc4CodecProfileAndLevel(String codec, String[] parts) { + if (parts.length != 4) { + Log.w(TAG, "Ignoring malformed AC-4 codec string: " + codec); + return null; + } + int bitstreamVersionInteger; + int presentationVersionInteger; + int levelInteger; + try { + bitstreamVersionInteger = Integer.parseInt(parts[1]); + presentationVersionInteger = Integer.parseInt(parts[2]); + levelInteger = Integer.parseInt(parts[3]); + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed AC-4 codec string: " + codec); + return null; + } + + int profile = ac4BitstreamAndPresentationVersionsToProfileConst( + bitstreamVersionInteger, + presentationVersionInteger); + if (profile == -1) { + Log.w(TAG, "Unknown AC-4 profile: " + + bitstreamVersionInteger + + "." + + presentationVersionInteger); + return null; + } + int level = ac4LevelNumberToConst(levelInteger); + if (level == -1) { + Log.w(TAG, "Unknown AC-4 level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + private static int avcProfileNumberToConst(int profileNumber) { switch (profileNumber) { case 66: @@ -966,5 +1006,52 @@ public final class CodecSpecificDataUtil { } } + private static int ac4BitstreamAndPresentationVersionsToProfileConst( + int bitstreamVersionInteger, + int presentationVersionInteger) { + int ac4Profile = -1; + switch (bitstreamVersionInteger) { + case 0: + if (presentationVersionInteger == 0) { + ac4Profile = MediaCodecInfo.CodecProfileLevel.AC4Profile00; + } + break; + case 1: + if (presentationVersionInteger == 0) { + ac4Profile = MediaCodecInfo.CodecProfileLevel.AC4Profile10; + } else if (presentationVersionInteger == 1) { + ac4Profile = MediaCodecInfo.CodecProfileLevel.AC4Profile11; + } + break; + case 2: + if (presentationVersionInteger == 1) { + ac4Profile = MediaCodecInfo.CodecProfileLevel.AC4Profile21; + } else if (presentationVersionInteger == 2) { + ac4Profile = MediaCodecInfo.CodecProfileLevel.AC4Profile22; + } + break; + default: + break; + } + return ac4Profile; + } + + private static int ac4LevelNumberToConst(int levelNumber) { + switch (levelNumber) { + case 0: + return MediaCodecInfo.CodecProfileLevel.AC4Level0; + case 1: + return MediaCodecInfo.CodecProfileLevel.AC4Level1; + case 2: + return MediaCodecInfo.CodecProfileLevel.AC4Level2; + case 3: + return MediaCodecInfo.CodecProfileLevel.AC4Level3; + case 4: + return MediaCodecInfo.CodecProfileLevel.AC4Level4; + default: + return -1; + } + } + private CodecSpecificDataUtil() {} } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java index e83cf4a377..f25930928d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java @@ -32,6 +32,7 @@ import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.util.Pair; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -43,6 +44,7 @@ import androidx.media3.common.MimeTypes; import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackParameters; import androidx.media3.common.audio.AudioProcessor; +import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.Log; import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.common.util.UnstableApi; @@ -1008,10 +1010,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } - if (Util.SDK_INT <= 28 && MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { - // On some older builds, the AC-4 decoder expects to receive samples formatted as raw frames - // not sync frames. Set a format key to override this. - mediaFormat.setInteger("ac4-is-sync", 1); + if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { + Pair profileLevel = CodecSpecificDataUtil.getCodecProfileAndLevel(format); + if (profileLevel != null) { + MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_PROFILE, profileLevel.first); + MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_LEVEL, profileLevel.second); + } + if (Util.SDK_INT <= 28) { + // On some older builds, the AC-4 decoder expects to receive samples formatted as raw frames + // not sync frames. Set a format key to override this. + mediaFormat.setInteger("ac4-is-sync", 1); + } } if (Util.SDK_INT >= 24 && audioSink.getFormatSupport( diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index e3eb174c33..23c791dd32 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -28,6 +28,7 @@ import androidx.media3.common.ParserException; import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -240,6 +241,7 @@ public final class Ac4Util { presentationConfig = dataBitArray.readBits(5); // presentation_config isSingleSubstreamGroup = (presentationConfig == 0x1f); } + ac4Presentation.version = presentationVersion; boolean addEmdfSubstreams; if (!(isSingleSubstream || isSingleSubstreamGroup) && presentationConfig == 6) { @@ -437,6 +439,11 @@ public final class Ac4Util { "Can't determine channel count of presentation."); } + String codecString = createCodecsString( + bitstreamVersion, + ac4Presentation.version, + ac4Presentation.level); + return new Format.Builder() .setId(trackId) .setSampleMimeType(MimeTypes.AUDIO_AC4) @@ -444,6 +451,7 @@ public final class Ac4Util { .setSampleRate(sampleRate) .setDrmInitData(drmInitData) .setLanguage(language) + .setCodecs(codecString) .build(); } @@ -631,6 +639,19 @@ public final class Ac4Util { } } + /** + * Create codec string based on bitstream version, presentation version and presentation level + * @param bitstreamVersion The bitstream version. + * @param presentationVersion The presentation version. + * @param mdcompat The mdcompat, i.e. presentation level. + * @return An AC-4 codec string built using the provided parameters. + */ + private static String createCodecsString( + int bitstreamVersion, int presentationVersion, int mdcompat) { + return Util.formatInvariant( + "ac-4.%02d.%02d.%02d", bitstreamVersion, presentationVersion, mdcompat); + } + /** * Returns AC-4 format information given {@code data} containing a syncframe. The reading position * of {@code data} will be modified. @@ -767,6 +788,7 @@ public final class Ac4Util { public int numOfUmxObjects; public boolean hasBackChannels; public int topChannelPairs; + public int version; public int level; private Ac4Presentation() { @@ -775,6 +797,7 @@ public final class Ac4Util { numOfUmxObjects = -1; hasBackChannels = true; topChannelPairs = 2; + version = 1; level = 0; } }