mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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;
|
private static final int RECTANGULAR = 0x00;
|
||||||
|
|
||||||
// Codecs to constant mappings.
|
// Codecs to constant mappings.
|
||||||
|
// H263
|
||||||
|
private static final String CODEC_ID_H263 = "s263";
|
||||||
// AVC.
|
// AVC.
|
||||||
private static final String CODEC_ID_AVC1 = "avc1";
|
private static final String CODEC_ID_AVC1 = "avc1";
|
||||||
private static final String CODEC_ID_AVC2 = "avc2";
|
private static final String CODEC_ID_AVC2 = "avc2";
|
||||||
@ -259,6 +261,11 @@ public final class CodecSpecificDataUtil {
|
|||||||
return builder.toString();
|
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})
|
* 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.
|
* 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);
|
return getDolbyVisionProfileAndLevel(format.codecs, parts);
|
||||||
}
|
}
|
||||||
switch (parts[0]) {
|
switch (parts[0]) {
|
||||||
|
case CODEC_ID_H263:
|
||||||
|
return getH263ProfileAndLevel(format.codecs, parts);
|
||||||
case CODEC_ID_AVC1:
|
case CODEC_ID_AVC1:
|
||||||
case CODEC_ID_AVC2:
|
case CODEC_ID_AVC2:
|
||||||
return getAvcProfileAndLevel(format.codecs, parts);
|
return getAvcProfileAndLevel(format.codecs, parts);
|
||||||
@ -463,6 +472,27 @@ public final class CodecSpecificDataUtil {
|
|||||||
return new Pair<>(profile, level);
|
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
|
@Nullable
|
||||||
private static Pair<Integer, Integer> getAvcProfileAndLevel(String codec, String[] parts) {
|
private static Pair<Integer, Integer> getAvcProfileAndLevel(String codec, String[] parts) {
|
||||||
if (parts.length < 2) {
|
if (parts.length < 2) {
|
||||||
|
@ -28,6 +28,7 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/** Helper class containing utility methods for managing {@link MediaFormat} instances. */
|
/** Helper class containing utility methods for managing {@link MediaFormat} instances. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -79,7 +80,7 @@ public final class MediaFormatUtil {
|
|||||||
.setAverageBitrate(
|
.setAverageBitrate(
|
||||||
getInteger(
|
getInteger(
|
||||||
mediaFormat, MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
|
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))
|
.setFrameRate(getFrameRate(mediaFormat, /* defaultValue= */ Format.NO_VALUE))
|
||||||
.setWidth(
|
.setWidth(
|
||||||
getInteger(mediaFormat, MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE))
|
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;
|
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}.
|
* Returns the frame rate from a {@link MediaFormat}.
|
||||||
*
|
*
|
||||||
|
@ -53,6 +53,15 @@ public class CodecSpecificDataUtilTest {
|
|||||||
assertThat(sampleRateAndChannelCount.second).isEqualTo(2);
|
assertThat(sampleRateAndChannelCount.second).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCodecProfileAndLevel_handlesH263CodecString() {
|
||||||
|
assertCodecProfileAndLevelForCodecsString(
|
||||||
|
MimeTypes.VIDEO_H263,
|
||||||
|
"s263.1.1",
|
||||||
|
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
|
||||||
|
MediaCodecInfo.CodecProfileLevel.H263Level10);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() {
|
public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() {
|
||||||
assertCodecProfileAndLevelForCodecsString(
|
assertCodecProfileAndLevelForCodecsString(
|
||||||
|
@ -26,11 +26,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.util.CodecSpecificDataUtil;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.container.MdtaMetadataEntry;
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
@ -678,7 +681,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
case MimeTypes.AUDIO_OPUS:
|
case MimeTypes.AUDIO_OPUS:
|
||||||
return dOpsBox(format);
|
return dOpsBox(format);
|
||||||
case MimeTypes.VIDEO_H263:
|
case MimeTypes.VIDEO_H263:
|
||||||
return d263Box();
|
return d263Box(format);
|
||||||
case MimeTypes.VIDEO_H264:
|
case MimeTypes.VIDEO_H264:
|
||||||
return avcCBox(format);
|
return avcCBox(format);
|
||||||
case MimeTypes.VIDEO_H265:
|
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. */
|
/** 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);
|
ByteBuffer d263Box = ByteBuffer.allocate(7);
|
||||||
d263Box.put(" ".getBytes(UTF_8)); // 4 spaces (vendor)
|
d263Box.put(" ".getBytes(UTF_8)); // 4 spaces (vendor)
|
||||||
d263Box.put((byte) 0x0); // decoder version
|
d263Box.put((byte) 0x00); // decoder version
|
||||||
// TODO: b/352000778 - Get profile and level from format.
|
Pair<Integer, Integer> profileAndLevel = CodecSpecificDataUtil.getCodecProfileAndLevel(format);
|
||||||
d263Box.put((byte) 0x10); // level
|
if (profileAndLevel == null) {
|
||||||
d263Box.put((byte) 0x0); // profile
|
profileAndLevel =
|
||||||
|
new Pair<>(
|
||||||
|
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
|
||||||
|
MediaCodecInfo.CodecProfileLevel.H263Level10);
|
||||||
|
}
|
||||||
|
d263Box.put(profileAndLevel.second.byteValue()); // level
|
||||||
|
d263Box.put(profileAndLevel.first.byteValue()); // profile
|
||||||
|
|
||||||
d263Box.flip();
|
d263Box.flip();
|
||||||
return BoxUtils.wrapIntoBox("d263", d263Box);
|
return BoxUtils.wrapIntoBox("d263", d263Box);
|
||||||
|
@ -368,7 +368,12 @@ public class BoxesTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createVideoSampleEntryBox_forH263_matchesExpected() throws Exception {
|
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);
|
ByteBuffer videoSampleEntryBox = Boxes.videoSampleEntry(format);
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
s263 (117 bytes):
|
s263 (117 bytes):
|
||||||
Data = length 109, hash EABCBA75
|
Data = length 109, hash 9FFB4BBC
|
||||||
|
Loading…
x
Reference in New Issue
Block a user