Skip to content

Commit 23b070d

Browse files
rohitjoinsmicrokatz
authored andcommitted
Merge pull request #162 from ittiam-systems:rtp-mp4a-latm
PiperOrigin-RevId: 482490230 (cherry picked from commit fd2ba37)
1 parent d2c4f74 commit 23b070d

17 files changed

+477
-58
lines changed

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpPayloadFormat.java

+32-19
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,23 @@
3737
*/
3838
public final class RtpPayloadFormat {
3939

40-
private static final String RTP_MEDIA_AC3 = "AC3";
41-
private static final String RTP_MEDIA_AMR = "AMR";
42-
private static final String RTP_MEDIA_AMR_WB = "AMR-WB";
43-
private static final String RTP_MEDIA_MPEG4_GENERIC = "MPEG4-GENERIC";
44-
private static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
45-
private static final String RTP_MEDIA_H263_1998 = "H263-1998";
46-
private static final String RTP_MEDIA_H263_2000 = "H263-2000";
47-
private static final String RTP_MEDIA_H264 = "H264";
48-
private static final String RTP_MEDIA_H265 = "H265";
49-
private static final String RTP_MEDIA_OPUS = "OPUS";
50-
private static final String RTP_MEDIA_PCM_L8 = "L8";
51-
private static final String RTP_MEDIA_PCM_L16 = "L16";
52-
private static final String RTP_MEDIA_PCMA = "PCMA";
53-
private static final String RTP_MEDIA_PCMU = "PCMU";
54-
private static final String RTP_MEDIA_VP8 = "VP8";
55-
private static final String RTP_MEDIA_VP9 = "VP9";
40+
public static final String RTP_MEDIA_AC3 = "AC3";
41+
public static final String RTP_MEDIA_AMR = "AMR";
42+
public static final String RTP_MEDIA_AMR_WB = "AMR-WB";
43+
public static final String RTP_MEDIA_MPEG4_GENERIC = "MPEG4-GENERIC";
44+
public static final String RTP_MEDIA_MPEG4_LATM_AUDIO = "MP4A-LATM";
45+
public static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
46+
public static final String RTP_MEDIA_H263_1998 = "H263-1998";
47+
public static final String RTP_MEDIA_H263_2000 = "H263-2000";
48+
public static final String RTP_MEDIA_H264 = "H264";
49+
public static final String RTP_MEDIA_H265 = "H265";
50+
public static final String RTP_MEDIA_OPUS = "OPUS";
51+
public static final String RTP_MEDIA_PCM_L8 = "L8";
52+
public static final String RTP_MEDIA_PCM_L16 = "L16";
53+
public static final String RTP_MEDIA_PCMA = "PCMA";
54+
public static final String RTP_MEDIA_PCMU = "PCMU";
55+
public static final String RTP_MEDIA_VP8 = "VP8";
56+
public static final String RTP_MEDIA_VP9 = "VP9";
5657

5758
/** Returns whether the format of a {@link MediaDescription} is supported. */
5859
public static boolean isFormatSupported(MediaDescription mediaDescription) {
@@ -64,8 +65,9 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) {
6465
case RTP_MEDIA_H263_2000:
6566
case RTP_MEDIA_H264:
6667
case RTP_MEDIA_H265:
67-
case RTP_MEDIA_MPEG4_VIDEO:
6868
case RTP_MEDIA_MPEG4_GENERIC:
69+
case RTP_MEDIA_MPEG4_LATM_AUDIO:
70+
case RTP_MEDIA_MPEG4_VIDEO:
6971
case RTP_MEDIA_OPUS:
7072
case RTP_MEDIA_PCM_L8:
7173
case RTP_MEDIA_PCM_L16:
@@ -95,6 +97,7 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
9597
case RTP_MEDIA_AMR_WB:
9698
return MimeTypes.AUDIO_AMR_WB;
9799
case RTP_MEDIA_MPEG4_GENERIC:
100+
case RTP_MEDIA_MPEG4_LATM_AUDIO:
98101
return MimeTypes.AUDIO_AAC;
99102
case RTP_MEDIA_OPUS:
100103
return MimeTypes.AUDIO_OPUS;
@@ -140,6 +143,8 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
140143
public final Format format;
141144
/** The format parameters, mapped from the SDP FMTP attribute (RFC2327 Page 22). */
142145
public final ImmutableMap<String, String> fmtpParameters;
146+
/** The RTP media encoding. */
147+
public final String mediaEncoding;
143148

144149
/**
145150
* Creates a new instance.
@@ -151,13 +156,19 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
151156
* @param fmtpParameters The format parameters, from the SDP FMTP attribute (RFC2327 Page 22),
152157
* empty if unset. The keys and values are specified in the RFCs for specific formats. For
153158
* instance, RFC3640 Section 4.1 defines keys like profile-level-id and config.
159+
* @param mediaEncoding The RTP media encoding.
154160
*/
155161
public RtpPayloadFormat(
156-
Format format, int rtpPayloadType, int clockRate, Map<String, String> fmtpParameters) {
162+
Format format,
163+
int rtpPayloadType,
164+
int clockRate,
165+
Map<String, String> fmtpParameters,
166+
String mediaEncoding) {
157167
this.rtpPayloadType = rtpPayloadType;
158168
this.clockRate = clockRate;
159169
this.format = format;
160170
this.fmtpParameters = ImmutableMap.copyOf(fmtpParameters);
171+
this.mediaEncoding = mediaEncoding;
161172
}
162173

163174
@Override
@@ -172,7 +183,8 @@ public boolean equals(@Nullable Object o) {
172183
return rtpPayloadType == that.rtpPayloadType
173184
&& clockRate == that.clockRate
174185
&& format.equals(that.format)
175-
&& fmtpParameters.equals(that.fmtpParameters);
186+
&& fmtpParameters.equals(that.fmtpParameters)
187+
&& mediaEncoding.equals(that.mediaEncoding);
176188
}
177189

178190
@Override
@@ -182,6 +194,7 @@ public int hashCode() {
182194
result = 31 * result + clockRate;
183195
result = 31 * result + format.hashCode();
184196
result = 31 * result + fmtpParameters.hashCode();
197+
result = 31 * result + mediaEncoding.hashCode();
185198
return result;
186199
}
187200
}

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java

+44-3
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@
3030
import androidx.annotation.VisibleForTesting;
3131
import com.google.android.exoplayer2.C;
3232
import com.google.android.exoplayer2.Format;
33+
import com.google.android.exoplayer2.ParserException;
3334
import com.google.android.exoplayer2.audio.AacUtil;
3435
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
3536
import com.google.android.exoplayer2.util.MimeTypes;
3637
import com.google.android.exoplayer2.util.NalUnitUtil;
38+
import com.google.android.exoplayer2.util.ParsableBitArray;
3739
import com.google.android.exoplayer2.util.Util;
3840
import com.google.common.collect.ImmutableList;
3941
import com.google.common.collect.ImmutableMap;
@@ -50,7 +52,8 @@
5052
private static final String PARAMETER_H265_SPROP_PPS = "sprop-pps";
5153
private static final String PARAMETER_H265_SPROP_VPS = "sprop-vps";
5254
private static final String PARAMETER_H265_SPROP_MAX_DON_DIFF = "sprop-max-don-diff";
53-
private static final String PARAMETER_MP4V_CONFIG = "config";
55+
private static final String PARAMETER_MP4A_CONFIG = "config";
56+
private static final String PARAMETER_MP4A_C_PRESENT = "cpresent";
5457

5558
/** Prefix for the RFC6381 codecs string for AAC formats. */
5659
private static final String AAC_CODECS_PREFIX = "mp4a.40.";
@@ -206,6 +209,23 @@ public int hashCode() {
206209
case MimeTypes.AUDIO_AAC:
207210
checkArgument(channelCount != C.INDEX_UNSET);
208211
checkArgument(!fmtpParameters.isEmpty());
212+
if (mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_LATM_AUDIO)) {
213+
// cpresent is defined in RFC3016 Section 5.3. cpresent=0 means the config fmtp parameter
214+
// must exist.
215+
checkArgument(
216+
fmtpParameters.containsKey(PARAMETER_MP4A_C_PRESENT)
217+
&& fmtpParameters.get(PARAMETER_MP4A_C_PRESENT).equals("0"),
218+
"Only supports cpresent=0 in AAC audio.");
219+
@Nullable String config = fmtpParameters.get(PARAMETER_MP4A_CONFIG);
220+
checkNotNull(config, "AAC audio stream must include config fmtp parameter");
221+
// config is a hex string.
222+
checkArgument(config.length() % 2 == 0, "Malformat MPEG4 config: " + config);
223+
AacUtil.Config aacConfig = parseAacStreamMuxConfig(config);
224+
formatBuilder
225+
.setSampleRate(aacConfig.sampleRateHz)
226+
.setChannelCount(aacConfig.channelCount)
227+
.setCodecs(aacConfig.codecs);
228+
}
209229
processAacFmtpAttribute(formatBuilder, fmtpParameters, channelCount, clockRate);
210230
break;
211231
case MimeTypes.AUDIO_AMR_NB:
@@ -265,7 +285,8 @@ public int hashCode() {
265285
}
266286

267287
checkArgument(clockRate > 0);
268-
return new RtpPayloadFormat(formatBuilder.build(), rtpPayloadType, clockRate, fmtpParameters);
288+
return new RtpPayloadFormat(
289+
formatBuilder.build(), rtpPayloadType, clockRate, fmtpParameters, mediaEncoding);
269290
}
270291

271292
private static int inferChannelCount(int encodingParameter, String mimeType) {
@@ -298,9 +319,29 @@ private static void processAacFmtpAttribute(
298319
AacUtil.buildAacLcAudioSpecificConfig(sampleRate, channelCount)));
299320
}
300321

322+
/**
323+
* Returns the {@link AacUtil.Config} by parsing the MPEG4 Audio Stream Mux configuration.
324+
*
325+
* <p>fmtp attribute {@code config} includes the MPEG4 Audio Stream Mux configuration
326+
* (ISO/IEC14496-3, Chapter 1.7.3).
327+
*/
328+
private static AacUtil.Config parseAacStreamMuxConfig(String streamMuxConfig) {
329+
ParsableBitArray config = new ParsableBitArray(Util.getBytesFromHexString(streamMuxConfig));
330+
checkArgument(config.readBits(1) == 0, "Only supports audio mux version 0.");
331+
checkArgument(config.readBits(1) == 1, "Only supports allStreamsSameTimeFraming.");
332+
config.skipBits(6);
333+
checkArgument(config.readBits(4) == 0, "Only supports one program.");
334+
checkArgument(config.readBits(3) == 0, "Only supports one numLayer.");
335+
try {
336+
return AacUtil.parseAudioSpecificConfig(config, false);
337+
} catch (ParserException e) {
338+
throw new IllegalArgumentException(e);
339+
}
340+
}
341+
301342
private static void processMPEG4FmtpAttribute(
302343
Format.Builder formatBuilder, ImmutableMap<String, String> fmtpAttributes) {
303-
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG);
344+
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG);
304345
if (configInput != null) {
305346
byte[] configBuffer = Util.getBytesFromHexString(configInput);
306347
formatBuilder.setInitializationData(ImmutableList.of(configBuffer));

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/reader/DefaultRtpPayloadReaderFactory.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) {
3333
case MimeTypes.AUDIO_AC3:
3434
return new RtpAc3Reader(payloadFormat);
3535
case MimeTypes.AUDIO_AAC:
36-
return new RtpAacReader(payloadFormat);
36+
if (payloadFormat.mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_LATM_AUDIO)) {
37+
return new RtpMp4aReader(payloadFormat);
38+
} else {
39+
return new RtpAacReader(payloadFormat);
40+
}
3741
case MimeTypes.AUDIO_AMR_NB:
3842
case MimeTypes.AUDIO_AMR_WB:
3943
return new RtpAmrReader(payloadFormat);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.exoplayer2.source.rtsp.reader;
17+
18+
import static com.google.android.exoplayer2.source.rtsp.reader.RtpReaderUtils.toSampleTimeUs;
19+
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
20+
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
21+
import static com.google.android.exoplayer2.util.Assertions.checkState;
22+
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
23+
import static com.google.android.exoplayer2.util.Util.castNonNull;
24+
25+
import androidx.annotation.Nullable;
26+
import com.google.android.exoplayer2.C;
27+
import com.google.android.exoplayer2.ParserException;
28+
import com.google.android.exoplayer2.extractor.ExtractorOutput;
29+
import com.google.android.exoplayer2.extractor.TrackOutput;
30+
import com.google.android.exoplayer2.source.rtsp.RtpPacket;
31+
import com.google.android.exoplayer2.source.rtsp.RtpPayloadFormat;
32+
import com.google.android.exoplayer2.util.ParsableBitArray;
33+
import com.google.android.exoplayer2.util.ParsableByteArray;
34+
import com.google.android.exoplayer2.util.Util;
35+
import com.google.common.collect.ImmutableMap;
36+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
37+
38+
/**
39+
* Parses an MP4A-LATM byte stream carried on RTP packets, and extracts MP4A-LATM Access Units.
40+
*
41+
* <p>Refer to RFC3016 for more details. The LATM byte stream format is defined in ISO/IEC14496-3.
42+
*/
43+
/* package */ final class RtpMp4aReader implements RtpPayloadReader {
44+
private static final String TAG = "RtpMp4aReader";
45+
46+
private static final String PARAMETER_MP4A_CONFIG = "config";
47+
48+
private final RtpPayloadFormat payloadFormat;
49+
private final int numberOfSubframes;
50+
private @MonotonicNonNull TrackOutput trackOutput;
51+
private long firstReceivedTimestamp;
52+
private int previousSequenceNumber;
53+
/** The combined size of a sample that is fragmented into multiple subFrames. */
54+
private int fragmentedSampleSizeBytes;
55+
56+
private long startTimeOffsetUs;
57+
private long fragmentedSampleTimeUs;
58+
59+
/**
60+
* Creates an instance.
61+
*
62+
* @throws IllegalArgumentException If {@link RtpPayloadFormat payloadFormat} is malformed.
63+
*/
64+
public RtpMp4aReader(RtpPayloadFormat payloadFormat) {
65+
this.payloadFormat = payloadFormat;
66+
try {
67+
numberOfSubframes = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters);
68+
} catch (ParserException e) {
69+
throw new IllegalArgumentException(e);
70+
}
71+
firstReceivedTimestamp = C.TIME_UNSET;
72+
previousSequenceNumber = C.INDEX_UNSET;
73+
fragmentedSampleSizeBytes = 0;
74+
// The start time offset must be 0 until the first seek.
75+
startTimeOffsetUs = 0;
76+
fragmentedSampleTimeUs = C.TIME_UNSET;
77+
}
78+
79+
@Override
80+
public void createTracks(ExtractorOutput extractorOutput, int trackId) {
81+
trackOutput = extractorOutput.track(trackId, C.TRACK_TYPE_VIDEO);
82+
castNonNull(trackOutput).format(payloadFormat.format);
83+
}
84+
85+
@Override
86+
public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {
87+
checkState(firstReceivedTimestamp == C.TIME_UNSET);
88+
firstReceivedTimestamp = timestamp;
89+
}
90+
91+
@Override
92+
public void consume(
93+
ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) {
94+
checkStateNotNull(trackOutput);
95+
96+
int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber);
97+
if (fragmentedSampleSizeBytes > 0 && expectedSequenceNumber < sequenceNumber) {
98+
outputSampleMetadataForFragmentedPackets();
99+
}
100+
101+
for (int subFrameIndex = 0; subFrameIndex < numberOfSubframes; subFrameIndex++) {
102+
int sampleLength = 0;
103+
// Implements PayloadLengthInfo() in ISO/IEC14496-3 Chapter 1.7.3.1, it only supports one
104+
// program and one layer. Each subframe starts with a variable length encoding.
105+
while (data.getPosition() < data.limit()) {
106+
int payloadMuxLength = data.readUnsignedByte();
107+
sampleLength += payloadMuxLength;
108+
if (payloadMuxLength != 0xff) {
109+
break;
110+
}
111+
}
112+
113+
trackOutput.sampleData(data, sampleLength);
114+
fragmentedSampleSizeBytes += sampleLength;
115+
}
116+
fragmentedSampleTimeUs =
117+
toSampleTimeUs(
118+
startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate);
119+
if (rtpMarker) {
120+
outputSampleMetadataForFragmentedPackets();
121+
}
122+
previousSequenceNumber = sequenceNumber;
123+
}
124+
125+
@Override
126+
public void seek(long nextRtpTimestamp, long timeUs) {
127+
firstReceivedTimestamp = nextRtpTimestamp;
128+
fragmentedSampleSizeBytes = 0;
129+
startTimeOffsetUs = timeUs;
130+
}
131+
132+
// Internal methods.
133+
134+
/**
135+
* Parses an MPEG-4 Audio Stream Mux configuration, as defined in ISO/IEC14496-3.
136+
*
137+
* <p>FMTP attribute {@code config} contains the MPEG-4 Audio Stream Mux configuration.
138+
*
139+
* @param fmtpAttributes The format parameters, mapped from the SDP FMTP attribute.
140+
* @return The number of subframes that is carried in each RTP packet.
141+
*/
142+
private static int getNumOfSubframesFromMpeg4AudioConfig(
143+
ImmutableMap<String, String> fmtpAttributes) throws ParserException {
144+
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG);
145+
int numberOfSubframes = 0;
146+
if (configInput != null && configInput.length() % 2 == 0) {
147+
byte[] configBuffer = Util.getBytesFromHexString(configInput);
148+
ParsableBitArray scratchBits = new ParsableBitArray(configBuffer);
149+
int audioMuxVersion = scratchBits.readBits(1);
150+
if (audioMuxVersion == 0) {
151+
checkArgument(scratchBits.readBits(1) == 1, "Only supports allStreamsSameTimeFraming.");
152+
numberOfSubframes = scratchBits.readBits(6);
153+
checkArgument(scratchBits.readBits(4) == 0, "Only suppors one program.");
154+
checkArgument(scratchBits.readBits(3) == 0, "Only suppors one layer.");
155+
} else {
156+
throw ParserException.createForMalformedDataOfUnknownType(
157+
"unsupported audio mux version: " + audioMuxVersion, null);
158+
}
159+
}
160+
// ISO/IEC14496-3 Chapter 1.7.3.2.3: The minimum value is 0 indicating 1 subframe.
161+
return numberOfSubframes + 1;
162+
}
163+
164+
/**
165+
* Outputs sample metadata.
166+
*
167+
* <p>Call this method only after receiving the end of an MPEG4 partition.
168+
*/
169+
private void outputSampleMetadataForFragmentedPackets() {
170+
checkNotNull(trackOutput)
171+
.sampleMetadata(
172+
fragmentedSampleTimeUs,
173+
C.BUFFER_FLAG_KEY_FRAME,
174+
fragmentedSampleSizeBytes,
175+
/* offset= */ 0,
176+
/* cryptoData= */ null);
177+
fragmentedSampleSizeBytes = 0;
178+
fragmentedSampleTimeUs = C.TIME_UNSET;
179+
}
180+
}

library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public void setUp() throws Exception {
7070
ImmutableList.of(
7171
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/h264-dump.json"),
7272
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json"),
73-
// MP4A-LATM is not supported at the moment.
74-
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/mp4a-latm-dump.json"));
73+
// MPEG2TS is not supported at the moment.
74+
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/mpeg2ts-dump.json"));
7575
}
7676

7777
@After

0 commit comments

Comments
 (0)