Add profile and level for H263 codec.
To support for 3gpp h263 codec in Mp4Muxer currently profile and level is hardcoded and provided to h263 box. Parse profile and level from MediaFormat and use those value to write h263 box. PiperOrigin-RevId: 675004590
This commit is contained in:
parent
6c92402fbb
commit
011659b326
@ -48,6 +48,8 @@ public final class CodecSpecificDataUtil {
|
||||
private static final int RECTANGULAR = 0x00;
|
||||
|
||||
// Codecs to constant mappings.
|
||||
// H263
|
||||
private static final String CODEC_ID_H263 = "s263";
|
||||
// AVC.
|
||||
private static final String CODEC_ID_AVC1 = "avc1";
|
||||
private static final String CODEC_ID_AVC2 = "avc2";
|
||||
@ -259,6 +261,11 @@ public final class CodecSpecificDataUtil {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/** Builds an RFC 6381 H263 codec string using profile and level. */
|
||||
public static String buildH263CodecString(int profile, int level) {
|
||||
return Util.formatInvariant("s263.%d.%d", profile, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns profile and level (as defined by {@link MediaCodecInfo.CodecProfileLevel})
|
||||
* corresponding to the codec description string (as defined by RFC 6381) of the given format.
|
||||
@ -278,6 +285,8 @@ public final class CodecSpecificDataUtil {
|
||||
return getDolbyVisionProfileAndLevel(format.codecs, parts);
|
||||
}
|
||||
switch (parts[0]) {
|
||||
case CODEC_ID_H263:
|
||||
return getH263ProfileAndLevel(format.codecs, parts);
|
||||
case CODEC_ID_AVC1:
|
||||
case CODEC_ID_AVC2:
|
||||
return getAvcProfileAndLevel(format.codecs, parts);
|
||||
@ -463,6 +472,27 @@ public final class CodecSpecificDataUtil {
|
||||
return new Pair<>(profile, level);
|
||||
}
|
||||
|
||||
/** Returns H263 profile and level from codec string. */
|
||||
private static Pair<Integer, Integer> getH263ProfileAndLevel(String codec, String[] parts) {
|
||||
Pair<Integer, Integer> defaultProfileAndLevel =
|
||||
new Pair<>(
|
||||
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
|
||||
MediaCodecInfo.CodecProfileLevel.H263Level10);
|
||||
if (parts.length < 3) {
|
||||
Log.w(TAG, "Ignoring malformed H263 codec string: " + codec);
|
||||
return defaultProfileAndLevel;
|
||||
}
|
||||
|
||||
try {
|
||||
int profile = Integer.parseInt(parts[1]);
|
||||
int level = Integer.parseInt(parts[2]);
|
||||
return new Pair<>(profile, level);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Ignoring malformed H263 codec string: " + codec);
|
||||
return defaultProfileAndLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<Integer, Integer> getAvcProfileAndLevel(String codec, String[] parts) {
|
||||
if (parts.length < 2) {
|
||||
|
@ -28,6 +28,7 @@ import androidx.media3.common.MimeTypes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Helper class containing utility methods for managing {@link MediaFormat} instances. */
|
||||
@UnstableApi
|
||||
@ -79,7 +80,7 @@ public final class MediaFormatUtil {
|
||||
.setAverageBitrate(
|
||||
getInteger(
|
||||
mediaFormat, MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
|
||||
.setCodecs(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING))
|
||||
.setCodecs(getCodecString(mediaFormat))
|
||||
.setFrameRate(getFrameRate(mediaFormat, /* defaultValue= */ Format.NO_VALUE))
|
||||
.setWidth(
|
||||
getInteger(mediaFormat, MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE))
|
||||
@ -332,6 +333,32 @@ public final class MediaFormatUtil {
|
||||
return mediaFormat.containsKey(name) ? mediaFormat.getFloat(name) : defaultValue;
|
||||
}
|
||||
|
||||
/** Supports {@link MediaFormat#getString(String, String)} for {@code API < 29}. */
|
||||
@Nullable
|
||||
public static String getString(
|
||||
MediaFormat mediaFormat, String name, @Nullable String defaultValue) {
|
||||
return mediaFormat.containsKey(name) ? mediaFormat.getString(name) : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Codecs string} of {@link MediaFormat}. In case of an H263 codec string, builds
|
||||
* and returns an RFC 6381 H263 codec string using profile and level.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressLint("InlinedApi") // Inlined MediaFormat keys.
|
||||
private static String getCodecString(MediaFormat mediaFormat) {
|
||||
// Add H263 profile and level to codec string as per RFC 6381.
|
||||
if (Objects.equals(mediaFormat.getString(MediaFormat.KEY_MIME), MimeTypes.VIDEO_H263)
|
||||
&& mediaFormat.containsKey(MediaFormat.KEY_PROFILE)
|
||||
&& mediaFormat.containsKey(MediaFormat.KEY_LEVEL)) {
|
||||
return CodecSpecificDataUtil.buildH263CodecString(
|
||||
mediaFormat.getInteger(MediaFormat.KEY_PROFILE),
|
||||
mediaFormat.getInteger(MediaFormat.KEY_LEVEL));
|
||||
} else {
|
||||
return getString(mediaFormat, MediaFormat.KEY_CODECS_STRING, /* defaultValue= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frame rate from a {@link MediaFormat}.
|
||||
*
|
||||
|
@ -53,6 +53,15 @@ public class CodecSpecificDataUtilTest {
|
||||
assertThat(sampleRateAndChannelCount.second).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCodecProfileAndLevel_handlesH263CodecString() {
|
||||
assertCodecProfileAndLevelForCodecsString(
|
||||
MimeTypes.VIDEO_H263,
|
||||
"s263.1.1",
|
||||
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
|
||||
MediaCodecInfo.CodecProfileLevel.H263Level10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() {
|
||||
assertCodecProfileAndLevelForCodecsString(
|
||||
|
@ -26,11 +26,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.CodecSpecificDataUtil;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.container.MdtaMetadataEntry;
|
||||
import androidx.media3.container.Mp4LocationData;
|
||||
@ -678,7 +681,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
case MimeTypes.AUDIO_OPUS:
|
||||
return dOpsBox(format);
|
||||
case MimeTypes.VIDEO_H263:
|
||||
return d263Box();
|
||||
return d263Box(format);
|
||||
case MimeTypes.VIDEO_H264:
|
||||
return avcCBox(format);
|
||||
case MimeTypes.VIDEO_H265:
|
||||
@ -1258,13 +1261,19 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
}
|
||||
|
||||
/** Returns the d263Box box as per 3GPP ETSI TS 126 244: 6.8. */
|
||||
private static ByteBuffer d263Box() {
|
||||
private static ByteBuffer d263Box(Format format) {
|
||||
ByteBuffer d263Box = ByteBuffer.allocate(7);
|
||||
d263Box.put(" ".getBytes(UTF_8)); // 4 spaces (vendor)
|
||||
d263Box.put((byte) 0x0); // decoder version
|
||||
// TODO: b/352000778 - Get profile and level from format.
|
||||
d263Box.put((byte) 0x10); // level
|
||||
d263Box.put((byte) 0x0); // profile
|
||||
d263Box.put((byte) 0x00); // decoder version
|
||||
Pair<Integer, Integer> profileAndLevel = CodecSpecificDataUtil.getCodecProfileAndLevel(format);
|
||||
if (profileAndLevel == null) {
|
||||
profileAndLevel =
|
||||
new Pair<>(
|
||||
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
|
||||
MediaCodecInfo.CodecProfileLevel.H263Level10);
|
||||
}
|
||||
d263Box.put(profileAndLevel.second.byteValue()); // level
|
||||
d263Box.put(profileAndLevel.first.byteValue()); // profile
|
||||
|
||||
d263Box.flip();
|
||||
return BoxUtils.wrapIntoBox("d263", d263Box);
|
||||
|
@ -368,7 +368,12 @@ public class BoxesTest {
|
||||
|
||||
@Test
|
||||
public void createVideoSampleEntryBox_forH263_matchesExpected() throws Exception {
|
||||
Format format = FAKE_VIDEO_FORMAT.buildUpon().setSampleMimeType(MimeTypes.VIDEO_H263).build();
|
||||
Format format =
|
||||
FAKE_VIDEO_FORMAT
|
||||
.buildUpon()
|
||||
.setSampleMimeType(MimeTypes.VIDEO_H263)
|
||||
.setCodecs("s263.1.10")
|
||||
.build();
|
||||
|
||||
ByteBuffer videoSampleEntryBox = Boxes.videoSampleEntry(format);
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
s263 (117 bytes):
|
||||
Data = length 109, hash EABCBA75
|
||||
Data = length 109, hash 9FFB4BBC
|
||||
|
Loading…
x
Reference in New Issue
Block a user