diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index fb98ceca1c..21862c8ff4 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -138,8 +138,10 @@
* Media2 extension:
* Deprecate `setControlDispatcher` in `SessionPlayerConnector`.
* RTSP:
- * Use standard RTSP header names.
+ * Use standard RTSP header names
([#9182](https://github.com/google/ExoPlayer/issues/9182)).
+ * Handle an extra semicolon in SDP fmtp attribute
+ ([#9247](https://github.com/google/ExoPlayer/pull/9247)).
* MediaSession extension:
* Deprecate `setControlDispatcher` in `MediaSessionConnector`. The
`ControlDispatcher` parameter has also been deprecated in all
diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java
index f5547e119c..0a7d87436c 100644
--- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java
+++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/MediaDescription.java
@@ -300,7 +300,7 @@ import java.util.HashMap;
* {@link MediaDescription} does not contain any FMTP attribute.
*
*
FMTP format reference: RFC2327 Page 27. The spaces around the FMTP attribute delimiters are
- * removed. For example,
+ * removed.
*/
public ImmutableMap getFmtpParametersAsMap() {
@Nullable String fmtpAttributeValue = attributes.get(ATTR_FMTP);
@@ -314,7 +314,8 @@ import java.util.HashMap;
// Format of the parameter: RFC3640 Section 4.4.1:
// =[; =].
- String[] parameters = Util.split(fmtpComponents[1], ";\\s?");
+ // Split with an explicit limit of 0 to handle an optional trailing semicolon.
+ String[] parameters = fmtpComponents[1].split(";\\s?", /* limit= */ 0);
ImmutableMap.Builder formatParametersBuilder = new ImmutableMap.Builder<>();
for (String parameter : parameters) {
// The parameter values can bear equal signs, so splitAtFirst must be used.
diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java
index 363533536e..7a24183084 100644
--- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java
+++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java
@@ -80,6 +80,48 @@ public class RtspMediaTrackTest {
assertThat(format).isEqualTo(expectedFormat);
}
+ @Test
+ public void generatePayloadFormat_withFmtpTrailingSemicolon_succeeds() {
+ MediaDescription mediaDescription =
+ new MediaDescription.Builder(
+ MEDIA_TYPE_VIDEO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 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==,aOvjyyLA;")
+ .addAttribute(ATTR_CONTROL, "track1")
+ .build();
+
+ RtpPayloadFormat format = RtspMediaTrack.generatePayloadFormat(mediaDescription);
+ RtpPayloadFormat expectedFormat =
+ new RtpPayloadFormat(
+ new Format.Builder()
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setAverageBitrate(500_000)
+ .setPixelWidthHeightRatio(1.0f)
+ .setHeight(544)
+ .setWidth(960)
+ .setCodecs("avc1.64001F")
+ .setInitializationData(
+ ImmutableList.of(
+ new byte[] {
+ 0, 0, 0, 1, 103, 100, 0, 31, -84, -39, 64, -16, 17, 105, -78, 0, 0, 3, 0,
+ 8, 0, 0, 3, 1, -100, 30, 48, 99, 44
+ },
+ new byte[] {0, 0, 0, 1, 104, -21, -29, -53, 34, -64}))
+ .build(),
+ /* rtpPayloadType= */ 96,
+ /* clockRate= */ 90_000,
+ /* fmtpParameters= */ ImmutableMap.of(
+ "packetization-mode", "1",
+ "profile-level-id", "64001F",
+ "sprop-parameter-sets", "Z2QAH6zZQPARabIAAAMACAAAAwGcHjBjLA==,aOvjyyLA"));
+
+ assertThat(format).isEqualTo(expectedFormat);
+ }
+
@Test
public void generatePayloadFormat_withAacMediaDescription_succeeds() {
MediaDescription mediaDescription =