From d4c62f386f519618ed1f9118e5fda7bea7e21c7e Mon Sep 17 00:00:00 2001 From: claincly Date: Wed, 14 Jul 2021 09:58:54 +0100 Subject: [PATCH] Handle absolute URI in RtspMediaTrack. Issue: #9183 RFC2326 Section C.1.1 specifies that the URI to identify a track can be either absolute (like rtsp://example.com/path) or relative (like "path"). Currently we don't handle absolute URI, and this CL is to add the support. Note though, we don't currently use the Content-Base or Content-Location headers for the session URI. PiperOrigin-RevId: 384649818 --- RELEASENOTES.md | 2 + .../source/rtsp/RtspMediaTrack.java | 28 +++++-- .../source/rtsp/RtspMediaTrackTest.java | 74 +++++++++++++++---- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 012468888dd..e4987d1f9c7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -65,6 +65,8 @@ * Fix session description (SDP) parsing to use a HashMap-like behaviour for duplicated attributes. ([#9014](https://github.com/google/ExoPlayer/issues/9014)). + * Allow using absolute URI in the control attribute in a media description + ([#9183](https://github.com/google/ExoPlayer/issues/9183)). ### 2.14.1 (2021-06-11) 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 c30adfe2a3c..2f6a3c3c01c 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 @@ -48,6 +48,8 @@ /** Prefix for the RFC6381 codecs string for AVC formats. */ private static final String H264_CODECS_PREFIX = "avc1."; + private static final String GENERIC_CONTROL_ATTR = "*"; + /** The track's associated {@link RtpPayloadFormat}. */ public final RtpPayloadFormat payloadFormat; /** The track's URI. */ @@ -62,11 +64,7 @@ public RtspMediaTrack(MediaDescription mediaDescription, Uri sessionUri) { checkArgument(mediaDescription.attributes.containsKey(ATTR_CONTROL)); payloadFormat = generatePayloadFormat(mediaDescription); - uri = - sessionUri - .buildUpon() - .appendEncodedPath(castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))) - .build(); + uri = extractTrackUri(sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))); } @Override @@ -219,4 +217,24 @@ private static byte[] getH264InitializationDataFromParameterSet(String parameter decodedParameterNalData.length); return decodedParameterNalUnit; } + + /** + * Extracts the track URI. + * + *

The processing logic is specified in RFC2326 Section C.1.1. + * + * @param sessionUri The session URI. + * @param controlAttributeString The control attribute from the track's {@link MediaDescription}. + * @return The extracted track URI. + */ + private static Uri extractTrackUri(Uri sessionUri, String controlAttributeString) { + Uri controlAttributeUri = Uri.parse(controlAttributeString); + if (controlAttributeUri.isAbsolute()) { + return controlAttributeUri; + } else if (controlAttributeString.equals(GENERIC_CONTROL_ATTR)) { + return sessionUri; + } else { + return sessionUri.buildUpon().appendEncodedPath(controlAttributeString).build(); + } + } } 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 7cbf1cff23e..363533536e6 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 @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.AacUtil; @@ -38,9 +39,10 @@ public class RtspMediaTrackTest { @Test - public void generatePayloadFormat_withH264MediaDescription_succeeds() throws Exception { + public void generatePayloadFormat_withH264MediaDescription_succeeds() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_VIDEO, 0, RTP_AVP_PROFILE, 96) + 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") @@ -79,9 +81,10 @@ public void generatePayloadFormat_withH264MediaDescription_succeeds() throws Exc } @Test - public void generatePayloadFormat_withAacMediaDescription_succeeds() throws Exception { + public void generatePayloadFormat_withAacMediaDescription_succeeds() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 0, RTP_AVP_PROFILE, 97) + new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) .setConnection("IN IP4 0.0.0.0") .setBitrate(96_000) .addAttribute(ATTR_RTPMAP, "97 MPEG4-GENERIC/44100") @@ -122,10 +125,10 @@ public void generatePayloadFormat_withAacMediaDescription_succeeds() throws Exce } @Test - public void generatePayloadFormat_withAc3MediaDescriptionWithDefaultChannelCount_succeeds() - throws Exception { + public void generatePayloadFormat_withAc3MediaDescriptionWithDefaultChannelCount_succeeds() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 0, RTP_AVP_PROFILE, 97) + new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) .setConnection("IN IP4 0.0.0.0") .setBitrate(48_000) .addAttribute(ATTR_RTPMAP, "97 AC3/48000") @@ -149,9 +152,10 @@ public void generatePayloadFormat_withAc3MediaDescriptionWithDefaultChannelCount } @Test - public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exception { + public void generatePayloadFormat_withAc3MediaDescription_succeeds() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 0, RTP_AVP_PROFILE, 97) + new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) .setConnection("IN IP4 0.0.0.0") .setBitrate(48_000) .addAttribute(ATTR_RTPMAP, "97 AC3/48000/2") @@ -174,6 +178,35 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce assertThat(format).isEqualTo(expectedFormat); } + @Test + public void rtspMediaTrack_mediaDescriptionContainsRelativeUri_setsCorrectTrackUri() { + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path1/track2"); + + RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/track2")); + } + + @Test + public void rtspMediaTrack_mediaDescriptionContainsAbsoluteUri_setsCorrectTrackUri() { + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("rtsp://test.com/foo"); + + RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/foo")); + } + + @Test + public void rtspMediaTrack_mediaDescriptionContainsGenericUri_setsCorrectTrackUri() { + MediaDescription mediaDescription = createGenericMediaDescriptionWithControlAttribute("*"); + + RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com")); + } + @Test public void generatePayloadFormat_withH264MediaDescriptionMissingProfileLevel_generatesCorrectProfileLevel() { @@ -195,7 +228,8 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce public void generatePayloadFormat_withAacMediaDescriptionMissingFmtpAttribute_throwsIllegalArgumentException() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 0, RTP_AVP_PROFILE, 97) + new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) .setConnection("IN IP4 0.0.0.0") .setBitrate(96_000) .addAttribute(ATTR_RTPMAP, "97 MPEG4-GENERIC/44100") @@ -210,7 +244,8 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce public void generatePayloadFormat_withMediaDescriptionMissingProfileLevel_throwsIllegalArgumentException() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_AUDIO, 0, RTP_AVP_PROFILE, 97) + new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) .setConnection("IN IP4 0.0.0.0") .setBitrate(96_000) .addAttribute(ATTR_RTPMAP, "97 MPEG4-GENERIC/44100") @@ -228,7 +263,8 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce public void generatePayloadFormat_withH264MediaDescriptionMissingFmtpAttribute_throwsIllegalArgumentException() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_VIDEO, 0, RTP_AVP_PROFILE, 96) + 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") @@ -243,7 +279,8 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce public void generatePayloadFormat_withH264MediaDescriptionMissingSpropParameter_throwsIllegalArgumentException() { MediaDescription mediaDescription = - new MediaDescription.Builder(MEDIA_TYPE_VIDEO, 0, RTP_AVP_PROFILE, 96) + 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") @@ -254,4 +291,15 @@ public void generatePayloadFormat_withAc3MediaDescription_succeeds() throws Exce IllegalArgumentException.class, () -> RtspMediaTrack.generatePayloadFormat(mediaDescription)); } + + private static MediaDescription createGenericMediaDescriptionWithControlAttribute( + String controlAttribute) { + return new MediaDescription.Builder( + MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 97) + .setConnection("IN IP4 0.0.0.0") + .setBitrate(48_000) + .addAttribute(ATTR_RTPMAP, "97 AC3/48000/6") + .addAttribute(ATTR_CONTROL, controlAttribute) + .build(); + } }