Skip invalid media description in SessionDescriptionParser
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: androidx/media#1472 PiperOrigin-RevId: 660826116
This commit is contained in:
parent
c1078e3cfa
commit
8b33ad5811
@ -108,6 +108,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:
|
||||
|
@ -24,6 +24,7 @@ import static com.google.common.base.Strings.nullToEmpty;
|
||||
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;
|
||||
@ -31,6 +32,8 @@ 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?(.+)");
|
||||
@ -74,6 +77,10 @@ import java.util.regex.Pattern;
|
||||
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)) {
|
||||
@ -111,6 +118,9 @@ import java.util.regex.Pattern;
|
||||
break;
|
||||
|
||||
case INFORMATION_TYPE:
|
||||
if (isSkippingMediaDescription) {
|
||||
continue;
|
||||
}
|
||||
if (mediaDescriptionBuilder == null) {
|
||||
sessionDescriptionBuilder.setSessionInfo(sdpValue);
|
||||
} else {
|
||||
@ -131,6 +141,9 @@ import java.util.regex.Pattern;
|
||||
break;
|
||||
|
||||
case CONNECTION_TYPE:
|
||||
if (isSkippingMediaDescription) {
|
||||
continue;
|
||||
}
|
||||
if (mediaDescriptionBuilder == null) {
|
||||
sessionDescriptionBuilder.setConnection(sdpValue);
|
||||
} else {
|
||||
@ -139,6 +152,9 @@ import java.util.regex.Pattern;
|
||||
break;
|
||||
|
||||
case BANDWIDTH_TYPE:
|
||||
if (isSkippingMediaDescription) {
|
||||
continue;
|
||||
}
|
||||
String[] bandwidthComponents = Util.split(sdpValue, ":\\s?");
|
||||
checkArgument(bandwidthComponents.length == 2);
|
||||
int bitrateKbps = Integer.parseInt(bandwidthComponents[1]);
|
||||
@ -156,6 +172,9 @@ import java.util.regex.Pattern;
|
||||
break;
|
||||
|
||||
case KEY_TYPE:
|
||||
if (isSkippingMediaDescription) {
|
||||
continue;
|
||||
}
|
||||
if (mediaDescriptionBuilder == null) {
|
||||
sessionDescriptionBuilder.setKey(sdpValue);
|
||||
} else {
|
||||
@ -164,6 +183,10 @@ import java.util.regex.Pattern;
|
||||
break;
|
||||
|
||||
case ATTRIBUTE_TYPE:
|
||||
// Parsing attribute
|
||||
if (isSkippingMediaDescription) {
|
||||
continue;
|
||||
}
|
||||
matcher = ATTRIBUTE_PATTERN.matcher(sdpValue);
|
||||
if (!matcher.matches()) {
|
||||
throw ParserException.createForMalformedManifest(
|
||||
@ -186,6 +209,7 @@ import java.util.regex.Pattern;
|
||||
addMediaDescriptionToSession(sessionDescriptionBuilder, mediaDescriptionBuilder);
|
||||
}
|
||||
mediaDescriptionBuilder = parseMediaDescriptionLine(sdpValue);
|
||||
isSkippingMediaDescription = mediaDescriptionBuilder == null;
|
||||
break;
|
||||
case REPEAT_TYPE:
|
||||
case ZONE_TYPE:
|
||||
@ -216,6 +240,18 @@ import java.util.regex.Pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@ -228,16 +264,18 @@ import java.util.regex.Pattern;
|
||||
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. */
|
||||
|
@ -192,6 +192,58 @@ public class SessionDescriptionTest {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user