From 89acf3cf08630320728d4668a61947e54c2bd92d Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Mon, 13 Feb 2023 14:18:53 +0000 Subject: [PATCH] Catch IllegalArgumentExceptions in RTSP Response parsing 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 (cherry picked from commit 711fa44d56678ebdb5c1c3a48f940fa71810c510) --- .../exoplayer2/source/rtsp/RtspClient.java | 2 +- .../source/rtsp/RtspMediaTrack.java | 25 +++++--- .../source/rtsp/RtspClientTest.java | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java index 5ff812bd82..3d079fd774 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java @@ -652,7 +652,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; default: throw new IllegalStateException(); } - } catch (ParserException e) { + } catch (ParserException | IllegalArgumentException e) { dispatchRtspError(new RtspPlaybackException(e)); } } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java index d28d3e20a9..427551abce 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java @@ -157,7 +157,8 @@ import com.google.common.collect.ImmutableMap; * @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))); } @@ -208,7 +209,7 @@ import com.google.common.collect.ImmutableMap; 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. @@ -257,11 +258,11 @@ import com.google.common.collect.ImmutableMap; 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: @@ -310,7 +311,8 @@ import com.google.common.collect.ImmutableMap; ImmutableMap 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( @@ -378,10 +380,10 @@ import com.google.common.collect.ImmutableMap; private static void processH264FmtpAttribute( Format.Builder formatBuilder, ImmutableMap 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 initializationData = ImmutableList.of( getInitializationDataFromParameterSet(parameterSets[0]), @@ -416,11 +418,14 @@ import com.google.common.collect.ImmutableMap; 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 initializationData = ImmutableList.of( diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java index 6e4c19bb58..f53d1ee30d 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java @@ -389,4 +389,68 @@ public final class RtspClientTest { 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 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); + } }