Skip to content

Commit

Permalink
Catch IllegalArgumentExceptions in RTSP Response parsing
Browse files Browse the repository at this point in the history
In parsing Describe RTSP response messages, IllegalArgumentExceptions are thrown for invalid parameters and values. These exceptions were not caught and crashed the Playback thread. Now these exceptions will be caught and their errors forwarded to the proper error handling listeners.

#minor-release

Issue: google/ExoPlayer#10971
PiperOrigin-RevId: 509207881
  • Loading branch information
microkatz authored and christosts committed Feb 13, 2023
1 parent fdf636b commit a8c8745
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 11 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
([#241](https://github.com/androidx/media/issues/241)).
* Fix a bug where notification play/pause button doesn't update with
player state ([#192](https://github.com/androidx/media/issues/192)).
* RTSP:
* Catch the IllegalArgumentException thrown in parsing of invalid RTSP
Describe response messages
([#10971](https://github.com/google/ExoPlayer/issues/10971)).
* Metadata:
* Parse multiple null-separated values from ID3 frames, as permitted by
ID3 v2.4.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ private void handleRtspResponse(List<String> message) {
default:
throw new IllegalStateException();
}
} catch (ParserException e) {
} catch (ParserException | IllegalArgumentException e) {
dispatchRtspError(new RtspPlaybackException(e));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@
* @param sessionUri The {@link Uri} of the RTSP playback session.
*/
public RtspMediaTrack(MediaDescription mediaDescription, Uri sessionUri) {
checkArgument(mediaDescription.attributes.containsKey(ATTR_CONTROL));
checkArgument(
mediaDescription.attributes.containsKey(ATTR_CONTROL), "missing attribute control");
payloadFormat = generatePayloadFormat(mediaDescription);
uri = extractTrackUri(sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL)));
}
Expand Down Expand Up @@ -210,7 +211,7 @@ public int hashCode() {
switch (mimeType) {
case MimeTypes.AUDIO_AAC:
checkArgument(channelCount != C.INDEX_UNSET);
checkArgument(!fmtpParameters.isEmpty());
checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp");
if (mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_LATM_AUDIO)) {
// cpresent is defined in RFC3016 Section 5.3. cpresent=0 means the config fmtp parameter
// must exist.
Expand Down Expand Up @@ -259,11 +260,11 @@ public int hashCode() {
formatBuilder.setWidth(DEFAULT_H263_WIDTH).setHeight(DEFAULT_H263_HEIGHT);
break;
case MimeTypes.VIDEO_H264:
checkArgument(!fmtpParameters.isEmpty());
checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp");
processH264FmtpAttribute(formatBuilder, fmtpParameters);
break;
case MimeTypes.VIDEO_H265:
checkArgument(!fmtpParameters.isEmpty());
checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp");
processH265FmtpAttribute(formatBuilder, fmtpParameters);
break;
case MimeTypes.VIDEO_VP8:
Expand Down Expand Up @@ -312,7 +313,8 @@ private static void processAacFmtpAttribute(
ImmutableMap<String, String> fmtpAttributes,
int channelCount,
int sampleRate) {
checkArgument(fmtpAttributes.containsKey(PARAMETER_PROFILE_LEVEL_ID));
checkArgument(
fmtpAttributes.containsKey(PARAMETER_PROFILE_LEVEL_ID), "missing profile-level-id param");
String profileLevel = checkNotNull(fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID));
formatBuilder.setCodecs(AAC_CODECS_PREFIX + profileLevel);
formatBuilder.setInitializationData(
Expand Down Expand Up @@ -380,10 +382,10 @@ private static byte[] getInitializationDataFromParameterSet(String parameterSet)

private static void processH264FmtpAttribute(
Format.Builder formatBuilder, ImmutableMap<String, String> fmtpAttributes) {
checkArgument(fmtpAttributes.containsKey(PARAMETER_SPROP_PARAMS));
checkArgument(fmtpAttributes.containsKey(PARAMETER_SPROP_PARAMS), "missing sprop parameter");
String spropParameterSets = checkNotNull(fmtpAttributes.get(PARAMETER_SPROP_PARAMS));
String[] parameterSets = Util.split(spropParameterSets, ",");
checkArgument(parameterSets.length == 2);
checkArgument(parameterSets.length == 2, "empty sprop value");
ImmutableList<byte[]> initializationData =
ImmutableList.of(
getInitializationDataFromParameterSet(parameterSets[0]),
Expand Down Expand Up @@ -418,11 +420,14 @@ private static void processH265FmtpAttribute(
maxDonDiff == 0, "non-zero sprop-max-don-diff " + maxDonDiff + " is not supported");
}

checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_VPS));
checkArgument(
fmtpAttributes.containsKey(PARAMETER_H265_SPROP_VPS), "missing sprop-vps parameter");
String spropVPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_VPS));
checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_SPS));
checkArgument(
fmtpAttributes.containsKey(PARAMETER_H265_SPROP_SPS), "missing sprop-sps parameter");
String spropSPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_SPS));
checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_PPS));
checkArgument(
fmtpAttributes.containsKey(PARAMETER_H265_SPROP_PPS), "missing sprop-pps parameter");
String spropPPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_PPS));
ImmutableList<byte[]> initializationData =
ImmutableList.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,68 @@ public void onSessionTimelineRequestFailed(
RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get);
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
}

@Test
public void connectServerAndClient_sdpInDescribeResponseHasInvalidFmtpAttr_doesNotUpdateTimeline()
throws Exception {
class ResponseProvider implements RtspServer.ResponseProvider {
@Override
public RtspResponse getOptionsResponse() {
return new RtspResponse(
/* status= */ 200,
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build());
}

@Override
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
String testMediaSdpInfo =
"v=0\r\n"
+ "o=- 1600785369059721 1 IN IP4 192.168.2.176\r\n"
+ "s=video, streamed by ExoPlayer\r\n"
+ "i=test.mkv\r\n"
+ "t=0 0\r\n"
+ "a=tool:ExoPlayer\r\n"
+ "a=type:broadcast\r\n"
+ "a=control:*\r\n"
+ "a=range:npt=0-30.102\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=\r\n"
+ "a=control:track1\r\n";
return RtspTestUtils.newDescribeResponseWithSdpMessage(
/* sessionDescription= */ testMediaSdpInfo,
// This session description has no tracks.
/* rtpPacketStreamDumps= */ ImmutableList.of(),
requestedUri);
}
}
rtspServer = new RtspServer(new ResponseProvider());

AtomicBoolean timelineRequestFailed = new AtomicBoolean();
rtspClient =
new RtspClient(
new SessionInfoListener() {
@Override
public void onSessionTimelineUpdated(
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> tracks) {}

@Override
public void onSessionTimelineRequestFailed(
String message, @Nullable Throwable cause) {
timelineRequestFailed.set(true);
}
},
EMPTY_PLAYBACK_LISTENER,
/* userAgent= */ "ExoPlayer:RtspClientTest",
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
rtspClient.start();

RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get);
assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED);
}
}

0 comments on commit a8c8745

Please sign in to comment.