Skip to content

Commit

Permalink
Skip invalid media description in SessionDescriptionParser
Browse files Browse the repository at this point in the history
Some RTSP servers may provide media descriptions for custom streams that are not supported. ExoPlayer should skip the invalid media description and continues parsing the following media descriptions.

To start, ExoPlayer will still error on malformed SDP lines for media descriptions, but will now skip media descriptions with "non-parsable" formats as described by [RFC 8866 Section 5.14](https://datatracker.ietf.org/doc/html/rfc8866#section-5.14).

Issue: #1472
PiperOrigin-RevId: 660826116
(cherry picked from commit 8b33ad5)
  • Loading branch information
microkatz authored and tianyif committed Aug 21, 2024
1 parent bf93449 commit c773789
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 7 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
* HLS Extension:
* Smooth Streaming Extension:
* RTSP Extension:
* Skip invalid Media Descriptions in SDP parsing
([#1087](https://github.com/androidx/media/issues/1472)).
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
* MIDI extension:
* Leanback extension:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Parses a String based SDP message into {@link SessionDescription}. */
/* package */ final class SessionDescriptionParser {
private static final String TAG = "SDPParser";

// SDP line always starts with an one letter tag, followed by an equal sign. The information
// under the given tag follows an optional space.
private static final Pattern SDP_LINE_PATTERN = Pattern.compile("([a-z])=\\s?(.+)");
Expand Down Expand Up @@ -74,6 +77,10 @@ public static SessionDescription parse(String sdpString) throws ParserException
SessionDescription.Builder sessionDescriptionBuilder = new SessionDescription.Builder();
@Nullable MediaDescription.Builder mediaDescriptionBuilder = null;

// Tracks if currently parsing an invalid media description and should skip any parsed
// and related SDP lines until the next valid media description.
boolean isSkippingMediaDescription = false;

// Lines are separated by an CRLF.
for (String line : RtspMessageUtil.splitRtspMessageBody(sdpString)) {
if ("".equals(line)) {
Expand Down Expand Up @@ -111,6 +118,9 @@ public static SessionDescription parse(String sdpString) throws ParserException
break;

case INFORMATION_TYPE:
if (isSkippingMediaDescription) {
continue;
}
if (mediaDescriptionBuilder == null) {
sessionDescriptionBuilder.setSessionInfo(sdpValue);
} else {
Expand All @@ -131,6 +141,9 @@ public static SessionDescription parse(String sdpString) throws ParserException
break;

case CONNECTION_TYPE:
if (isSkippingMediaDescription) {
continue;
}
if (mediaDescriptionBuilder == null) {
sessionDescriptionBuilder.setConnection(sdpValue);
} else {
Expand All @@ -139,6 +152,9 @@ public static SessionDescription parse(String sdpString) throws ParserException
break;

case BANDWIDTH_TYPE:
if (isSkippingMediaDescription) {
continue;
}
String[] bandwidthComponents = Util.split(sdpValue, ":\\s?");
checkArgument(bandwidthComponents.length == 2);
int bitrateKbps = Integer.parseInt(bandwidthComponents[1]);
Expand All @@ -156,6 +172,9 @@ public static SessionDescription parse(String sdpString) throws ParserException
break;

case KEY_TYPE:
if (isSkippingMediaDescription) {
continue;
}
if (mediaDescriptionBuilder == null) {
sessionDescriptionBuilder.setKey(sdpValue);
} else {
Expand All @@ -164,6 +183,10 @@ public static SessionDescription parse(String sdpString) throws ParserException
break;

case ATTRIBUTE_TYPE:
// Parsing attribute
if (isSkippingMediaDescription) {
continue;
}
matcher = ATTRIBUTE_PATTERN.matcher(sdpValue);
if (!matcher.matches()) {
throw ParserException.createForMalformedManifest(
Expand All @@ -186,6 +209,7 @@ public static SessionDescription parse(String sdpString) throws ParserException
addMediaDescriptionToSession(sessionDescriptionBuilder, mediaDescriptionBuilder);
}
mediaDescriptionBuilder = parseMediaDescriptionLine(sdpValue);
isSkippingMediaDescription = mediaDescriptionBuilder == null;
break;
case REPEAT_TYPE:
case ZONE_TYPE:
Expand Down Expand Up @@ -216,6 +240,18 @@ private static void addMediaDescriptionToSession(
}
}

/**
* Parses a Media Description SDP line.
*
* <p>Returns a {@link MediaDescription.Builder} from parsing a valid SDP {@code line} is parsed
* or {@code null} if {@code line} contains invalid port or format values.
*
* @param line representing a Media Description.
* @return valid {@link MediaDescription.Builder} or {@code null} if {@code line} contains invalid
* port or format values.
* @throws ParserException if malformed SDP media description line.
*/
@Nullable
private static MediaDescription.Builder parseMediaDescriptionLine(String line)
throws ParserException {
Matcher matcher = MEDIA_DESCRIPTION_PATTERN.matcher(line);
Expand All @@ -228,16 +264,18 @@ private static MediaDescription.Builder parseMediaDescriptionLine(String line)
String transportProtocol = checkNotNull(matcher.group(3));
String payloadTypeString = checkNotNull(matcher.group(4));

@Nullable MediaDescription.Builder mediaDescriptionBuilder = null;
try {
return new MediaDescription.Builder(
mediaType,
Integer.parseInt(portString),
transportProtocol,
Integer.parseInt(payloadTypeString));
mediaDescriptionBuilder =
new MediaDescription.Builder(
mediaType,
Integer.parseInt(portString),
transportProtocol,
Integer.parseInt(payloadTypeString));
} catch (NumberFormatException e) {
throw ParserException.createForMalformedManifest(
"Malformed SDP media description line: " + line, e);
Log.w(TAG, "Malformed SDP media description line: " + line, e);
}
return mediaDescriptionBuilder;
}

/** Prevents initialization. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,58 @@ public void parse_sdpString2_succeeds() throws Exception {
assertThat(sessionDescription).isEqualTo(expectedSession);
}

@Test
public void parse_sdpStringWithInvalidMediaDescriptionFormatType_succeeds() throws Exception {
// SDP with invalid media description should skip and parse following media descriptions
String testMediaSdpInfo =
"v=0\r\n"
+ "o=- 1600785369059721 1 IN IP4 192.168.2.176\r\n"
+ "s=SDP Seminar\r\n"
+ "a=control:*\r\n"
+ "m=video 0 RTP/AVP 96\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "b=AS:500\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96"
+ " packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z2QAH6zZQPARabIAAAMACAAAAwGcHjBjLA==,aOvjyyLAAA==\r\n"
+ "a=control:track1\r\n"
+ "m=application/T-Link 0 RTP/AVP smart/1/90000\r\n"
+ "i=CustomStream\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "b=AS:500\r\n"
+ "a=rtpmap:95 T-Link/90000 \r\n"
+ "a=control:track2 \r\n"
+ "m=audio 3456 RTP/AVP 97\r\n"
+ "a=rtpmap:97 AC3/44100\r\n"
+ "a=control:track3\r\n";

SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo);

SessionDescription expectedSession =
new SessionDescription.Builder()
.setOrigin("- 1600785369059721 1 IN IP4 192.168.2.176")
.setSessionName("SDP Seminar")
.addAttribute(ATTR_CONTROL, "*")
.addMediaDescription(
new MediaDescription.Builder(MEDIA_TYPE_VIDEO, 0, RTP_AVP_PROFILE, 96)
.setConnection("IN IP4 0.0.0.0")
.setBitrate(500_000)
.addAttribute(ATTR_RTPMAP, "96 H264/90000")
.addAttribute(
ATTR_FMTP,
"96 packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z2QAH6zZQPARabIAAAMACAAAAwGcHjBjLA==,aOvjyyLAAA==")
.addAttribute(ATTR_CONTROL, "track1")
.build())
.addMediaDescription(
new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 3456, RTP_AVP_PROFILE, 97)
.addAttribute(ATTR_RTPMAP, "97 AC3/44100")
.addAttribute(ATTR_CONTROL, "track3")
.build())
.build();

assertThat(sessionDescription).isEqualTo(expectedSession);
}

@Test
public void parse_sdpStringWithDuplicatedMediaAttribute_recordsTheMostRecentValue()
throws Exception {
Expand Down

0 comments on commit c773789

Please sign in to comment.