Skip to content

Add KEY_PROFILE, KEY_LEVEL support for AC-4 #2323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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+)$");

Expand Down Expand Up @@ -299,6 +301,8 @@ public static Pair<Integer, Integer> getCodecProfileAndLevel(Format format) {
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;
}
Expand Down Expand Up @@ -635,6 +639,42 @@ private static Pair<Integer, Integer> getAacCodecProfileAndLevel(String codec, S
return null;
}

@Nullable
private static Pair<Integer, Integer> 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:
Expand Down Expand Up @@ -966,5 +1006,52 @@ private static int mp4aAudioObjectTypeToProfile(int profileNumber) {
}
}

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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand All @@ -43,6 +44,7 @@
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;
Expand Down Expand Up @@ -1008,10 +1010,17 @@ protected MediaFormat getMediaFormat(
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<Integer, Integer> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -240,6 +241,7 @@ public static Format parseAc4AnnexEFormat(
presentationConfig = dataBitArray.readBits(5); // presentation_config
isSingleSubstreamGroup = (presentationConfig == 0x1f);
}
ac4Presentation.version = presentationVersion;

boolean addEmdfSubstreams;
if (!(isSingleSubstream || isSingleSubstreamGroup) && presentationConfig == 6) {
Expand Down Expand Up @@ -437,13 +439,19 @@ public static Format parseAc4AnnexEFormat(
"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)
.setChannelCount(channelCount)
.setSampleRate(sampleRate)
.setDrmInitData(drmInitData)
.setLanguage(language)
.setCodecs(codecString)
.build();
}

Expand Down Expand Up @@ -631,6 +639,19 @@ private static int getChannelCountFromChannelMode(@ChannelMode int channelMode)
}
}

/**
* 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.
Expand Down Expand Up @@ -767,6 +788,7 @@ private static final class Ac4Presentation {
public int numOfUmxObjects;
public boolean hasBackChannels;
public int topChannelPairs;
public int version;
public int level;

private Ac4Presentation() {
Expand All @@ -775,6 +797,7 @@ private Ac4Presentation() {
numOfUmxObjects = -1;
hasBackChannels = true;
topChannelPairs = 2;
version = 1;
level = 0;
}
}
Expand Down