Skip to content

Commit f48babb

Browse files
committed
Merge pull request #35 from ittiam-systems:rtp-mpeg4
PiperOrigin-RevId: 438000682
2 parents 8089092 + ef9393a commit f48babb

6 files changed

Lines changed: 298 additions & 6 deletions

File tree

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
dependency from the UI module to the ExoPlayer module. This is a
3737
breaking change.
3838
* RTSP:
39+
* Add RTP reader for MPEG4
40+
([#35](https://github.com/androidx/media/pull/35))
3941
* Add RTP reader for HEVC
4042
([#36](https://github.com/androidx/media/pull/36)).
4143
* Add RTP reader for AMR. Currently only mono-channel, non-interleaved

libraries/common/src/main/java/androidx/media3/common/util/CodecSpecificDataUtil.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package androidx.media3.common.util;
1717

18+
import static androidx.media3.common.util.Assertions.checkArgument;
19+
1820
import android.util.Pair;
1921
import androidx.annotation.Nullable;
2022
import androidx.media3.common.C;
@@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
3133
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
3234
new String[] {"", "A", "B", "C"};
3335

36+
// MP4V-ES
37+
private static final int VISUAL_OBJECT_LAYER = 1;
38+
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
39+
private static final int EXTENDED_PAR = 0x0F;
40+
private static final int RECTANGULAR = 0x00;
41+
3442
/**
3543
* Parses an ALAC AudioSpecificConfig (i.e. an <a
3644
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
@@ -72,6 +80,87 @@ public static boolean parseCea708InitializationData(List<byte[]> initializationD
7280
&& initializationData.get(0)[0] == 1;
7381
}
7482

83+
/**
84+
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
85+
*
86+
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
87+
* to parse.
88+
* @return A pair of the video's width and height.
89+
*/
90+
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
91+
byte[] videoSpecificConfig) {
92+
int offset = 0;
93+
boolean foundVOL = false;
94+
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
95+
while (offset + 3 < videoSpecificConfig.length) {
96+
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
97+
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
98+
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
99+
offset++;
100+
continue;
101+
}
102+
foundVOL = true;
103+
break;
104+
}
105+
106+
checkArgument(foundVOL, "Invalid input: VOL not found.");
107+
108+
ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
109+
// Skip the start codecs from the bitstream
110+
scratchBits.skipBits((offset + 4) * 8);
111+
scratchBits.skipBits(1); // random_accessible_vol
112+
scratchBits.skipBits(8); // video_object_type_indication
113+
114+
if (scratchBits.readBit()) { // object_layer_identifier
115+
scratchBits.skipBits(4); // video_object_layer_verid
116+
scratchBits.skipBits(3); // video_object_layer_priority
117+
}
118+
119+
int aspectRatioInfo = scratchBits.readBits(4);
120+
if (aspectRatioInfo == EXTENDED_PAR) {
121+
scratchBits.skipBits(8); // par_width
122+
scratchBits.skipBits(8); // par_height
123+
}
124+
125+
if (scratchBits.readBit()) { // vol_control_parameters
126+
scratchBits.skipBits(2); // chroma_format
127+
scratchBits.skipBits(1); // low_delay
128+
if (scratchBits.readBit()) { // vbv_parameters
129+
scratchBits.skipBits(79);
130+
}
131+
}
132+
133+
int videoObjectLayerShape = scratchBits.readBits(2);
134+
checkArgument(
135+
videoObjectLayerShape == RECTANGULAR,
136+
"Only supports rectangular video object layer shape.");
137+
138+
checkArgument(scratchBits.readBit()); // marker_bit
139+
int vopTimeIncrementResolution = scratchBits.readBits(16);
140+
checkArgument(scratchBits.readBit()); // marker_bit
141+
142+
if (scratchBits.readBit()) { // fixed_vop_rate
143+
checkArgument(vopTimeIncrementResolution > 0);
144+
vopTimeIncrementResolution--;
145+
int numBitsToSkip = 0;
146+
while (vopTimeIncrementResolution > 0) {
147+
numBitsToSkip++;
148+
vopTimeIncrementResolution >>= 1;
149+
}
150+
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
151+
}
152+
153+
checkArgument(scratchBits.readBit()); // marker_bit
154+
int videoObjectLayerWidth = scratchBits.readBits(13);
155+
checkArgument(scratchBits.readBit()); // marker_bit
156+
int videoObjectLayerHeight = scratchBits.readBits(13);
157+
checkArgument(scratchBits.readBit()); // marker_bit
158+
159+
scratchBits.skipBits(1); // interlaced
160+
161+
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
162+
}
163+
75164
/**
76165
* Builds an RFC 6381 AVC codec string using the provided parameters.
77166
*

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public final class RtpPayloadFormat {
4343
private static final String RTP_MEDIA_AMR = "AMR";
4444
private static final String RTP_MEDIA_AMR_WB = "AMR-WB";
4545
private static final String RTP_MEDIA_MPEG4_GENERIC = "MPEG4-GENERIC";
46+
private static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
4647
private static final String RTP_MEDIA_H264 = "H264";
4748
private static final String RTP_MEDIA_H265 = "H265";
4849
private static final String RTP_MEDIA_PCM_L8 = "L8";
@@ -59,6 +60,7 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) {
5960
case RTP_MEDIA_AMR_WB:
6061
case RTP_MEDIA_H264:
6162
case RTP_MEDIA_H265:
63+
case RTP_MEDIA_MPEG4_VIDEO:
6264
case RTP_MEDIA_MPEG4_GENERIC:
6365
case RTP_MEDIA_PCM_L8:
6466
case RTP_MEDIA_PCM_L16:
@@ -86,10 +88,6 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
8688
return MimeTypes.AUDIO_AMR_NB;
8789
case RTP_MEDIA_AMR_WB:
8890
return MimeTypes.AUDIO_AMR_WB;
89-
case RTP_MEDIA_H264:
90-
return MimeTypes.VIDEO_H264;
91-
case RTP_MEDIA_H265:
92-
return MimeTypes.VIDEO_H265;
9391
case RTP_MEDIA_MPEG4_GENERIC:
9492
return MimeTypes.AUDIO_AAC;
9593
case RTP_MEDIA_PCM_L8:
@@ -99,6 +97,12 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
9997
return MimeTypes.AUDIO_ALAW;
10098
case RTP_MEDIA_PCMU:
10199
return MimeTypes.AUDIO_MLAW;
100+
case RTP_MEDIA_H264:
101+
return MimeTypes.VIDEO_H264;
102+
case RTP_MEDIA_H265:
103+
return MimeTypes.VIDEO_H265;
104+
case RTP_MEDIA_MPEG4_VIDEO:
105+
return MimeTypes.VIDEO_MP4V;
102106
case RTP_MEDIA_VP8:
103107
return MimeTypes.VIDEO_VP8;
104108
default:

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import android.net.Uri;
2727
import android.util.Base64;
28+
import android.util.Pair;
2829
import androidx.annotation.Nullable;
2930
import androidx.annotation.VisibleForTesting;
3031
import androidx.media3.common.C;
@@ -44,19 +45,42 @@
4445
// Format specific parameter names.
4546
private static final String PARAMETER_PROFILE_LEVEL_ID = "profile-level-id";
4647
private static final String PARAMETER_SPROP_PARAMS = "sprop-parameter-sets";
48+
49+
private static final String PARAMETER_AMR_OCTET_ALIGN = "octet-align";
50+
private static final String PARAMETER_AMR_INTERLEAVING = "interleaving";
4751
private static final String PARAMETER_H265_SPROP_SPS = "sprop-sps";
4852
private static final String PARAMETER_H265_SPROP_PPS = "sprop-pps";
4953
private static final String PARAMETER_H265_SPROP_VPS = "sprop-vps";
5054
private static final String PARAMETER_H265_SPROP_MAX_DON_DIFF = "sprop-max-don-diff";
51-
private static final String PARAMETER_AMR_OCTET_ALIGN = "octet-align";
52-
private static final String PARAMETER_AMR_INTERLEAVING = "interleaving";
55+
private static final String PARAMETER_MP4V_CONFIG = "config";
5356

5457
/** Prefix for the RFC6381 codecs string for AAC formats. */
5558
private static final String AAC_CODECS_PREFIX = "mp4a.40.";
5659
/** Prefix for the RFC6381 codecs string for AVC formats. */
5760
private static final String H264_CODECS_PREFIX = "avc1.";
61+
/** Prefix for the RFC6416 codecs string for MPEG4V-ES formats. */
62+
private static final String MPEG4_CODECS_PREFIX = "mp4v.";
5863

5964
private static final String GENERIC_CONTROL_ATTR = "*";
65+
/**
66+
* Default height for MP4V.
67+
*
68+
* <p>RFC6416 does not mandate codec specific data (like width and height) in the fmtp attribute.
69+
* These values are taken from <a
70+
* href=https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp;l=130
71+
* >Android's software MP4V decoder</a>.
72+
*/
73+
private static final int DEFAULT_MP4V_WIDTH = 352;
74+
75+
/**
76+
* Default height for MP4V.
77+
*
78+
* <p>RFC6416 does not mandate codec specific data (like width and height) in the fmtp attribute.
79+
* These values are taken from <a
80+
* href=https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp;l=130
81+
* >Android's software MP4V decoder</a>.
82+
*/
83+
private static final int DEFAULT_MP4V_HEIGHT = 288;
6084

6185
/**
6286
* Default width for VP8.
@@ -156,6 +180,10 @@ public int hashCode() {
156180
!fmtpParameters.containsKey(PARAMETER_AMR_INTERLEAVING),
157181
"Interleaving mode is not currently supported.");
158182
break;
183+
case MimeTypes.VIDEO_MP4V:
184+
checkArgument(!fmtpParameters.isEmpty());
185+
processMPEG4FmtpAttribute(formatBuilder, fmtpParameters);
186+
break;
159187
case MimeTypes.VIDEO_H264:
160188
checkArgument(!fmtpParameters.isEmpty());
161189
processH264FmtpAttribute(formatBuilder, fmtpParameters);
@@ -214,6 +242,23 @@ private static void processAacFmtpAttribute(
214242
AacUtil.buildAacLcAudioSpecificConfig(sampleRate, channelCount)));
215243
}
216244

245+
private static void processMPEG4FmtpAttribute(
246+
Format.Builder formatBuilder, ImmutableMap<String, String> fmtpAttributes) {
247+
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG);
248+
if (configInput != null) {
249+
byte[] configBuffer = Util.getBytesFromHexString(configInput);
250+
formatBuilder.setInitializationData(ImmutableList.of(configBuffer));
251+
Pair<Integer, Integer> resolution =
252+
CodecSpecificDataUtil.getVideoResolutionFromMpeg4VideoConfig(configBuffer);
253+
formatBuilder.setWidth(resolution.first).setHeight(resolution.second);
254+
} else {
255+
// set the default width and height
256+
formatBuilder.setWidth(DEFAULT_MP4V_WIDTH).setHeight(DEFAULT_MP4V_HEIGHT);
257+
}
258+
@Nullable String profileLevel = fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID);
259+
formatBuilder.setCodecs(MPEG4_CODECS_PREFIX + (profileLevel == null ? "1" : profileLevel));
260+
}
261+
217262
/** Returns H264/H265 initialization data from the RTP parameter set. */
218263
private static byte[] getInitializationDataFromParameterSet(String parameterSet) {
219264
byte[] decodedParameterNalData = Base64.decode(parameterSet, Base64.DEFAULT);

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) {
4747
return new RtpH264Reader(payloadFormat);
4848
case MimeTypes.VIDEO_H265:
4949
return new RtpH265Reader(payloadFormat);
50+
case MimeTypes.VIDEO_MP4V:
51+
return new RtpMpeg4Reader(payloadFormat);
5052
case MimeTypes.VIDEO_VP8:
5153
return new RtpVp8Reader(payloadFormat);
5254
default:

0 commit comments

Comments
 (0)