Add support for static RTP payload types.

Some RTP foramts are statically assigned, so they don't have the rtpmap
attribute. Create the missing rtpmap attribute in this case.

PiperOrigin-RevId: 448239724
This commit is contained in:
claincly 2022-05-12 15:07:20 +01:00 committed by Ian Baker
parent dd365cbeb8
commit 010a00e458
2 changed files with 181 additions and 4 deletions

View File

@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.source.rtsp.RtspMessageUtil.parseInt
import static com.google.android.exoplayer2.source.rtsp.SessionDescription.ATTR_FMTP;
import static com.google.android.exoplayer2.source.rtsp.SessionDescription.ATTR_RTPMAP;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import androidx.annotation.Nullable;
@ -103,6 +102,17 @@ import java.util.HashMap;
/** Builder class for {@link MediaDescription}. */
public static final class Builder {
/**
* RTPMAP attribute format: {@code <payloadType> <mediaEncoding>/<clockRate>/<channelCount>}.
*/
private static final String RTP_MAP_ATTR_AUDIO_FMT = "%d %s/%d/%d";
private static final int RTP_STATIC_PAYLOAD_TYPE_PCMU = 0;
private static final int RTP_STATIC_PAYLOAD_TYPE_PCMA = 8;
private static final int RTP_STATIC_PAYLOAD_TYPE_L16_STEREO = 10;
private static final int RTP_STATIC_PAYLOAD_TYPE_L16_MONO = 11;
private final String mediaType;
private final int port;
private final String transportProtocol;
@ -197,15 +207,55 @@ import java.util.HashMap;
*/
public MediaDescription build() {
try {
// rtpmap attribute is mandatory in RTSP (RFC2326 Section C.1.3).
checkState(attributes.containsKey(ATTR_RTPMAP));
RtpMapAttribute rtpMapAttribute =
RtpMapAttribute.parse(castNonNull(attributes.get(ATTR_RTPMAP)));
attributes.containsKey(ATTR_RTPMAP)
? RtpMapAttribute.parse(castNonNull(attributes.get(ATTR_RTPMAP)))
: RtpMapAttribute.parse(getRtpMapStringByPayloadType(payloadType));
return new MediaDescription(this, ImmutableMap.copyOf(attributes), rtpMapAttribute);
} catch (ParserException e) {
throw new IllegalStateException(e);
}
}
private static String getRtpMapStringByPayloadType(int rtpPayloadType) {
checkArgument(rtpPayloadType < 96);
switch (rtpPayloadType) {
// See RFC3551 Section 6.
case RTP_STATIC_PAYLOAD_TYPE_PCMU:
return constructAudioRtpMap(
RTP_STATIC_PAYLOAD_TYPE_PCMU,
/* mediaEncoding= */ "PCMU",
/* clockRate= */ 8_000,
/* channelCount= */ 1);
case RTP_STATIC_PAYLOAD_TYPE_PCMA:
return constructAudioRtpMap(
RTP_STATIC_PAYLOAD_TYPE_PCMA,
/* mediaEncoding= */ "PCMA",
/* clockRate= */ 8_000,
/* channelCount= */ 1);
case RTP_STATIC_PAYLOAD_TYPE_L16_STEREO:
return constructAudioRtpMap(
RTP_STATIC_PAYLOAD_TYPE_L16_STEREO,
/* mediaEncoding= */ "L16",
/* clockRate= */ 44_100,
/* channelCount= */ 2);
case RTP_STATIC_PAYLOAD_TYPE_L16_MONO:
return constructAudioRtpMap(
RTP_STATIC_PAYLOAD_TYPE_L16_MONO,
/* mediaEncoding= */ "L16",
/* clockRate= */ 44_100,
/* channelCount= */ 1);
default:
throw new IllegalStateException("Unsupported static paylod type " + rtpPayloadType);
}
}
private static String constructAudioRtpMap(
int payloadType, String mediaEncoding, int clockRate, int channelCount) {
return Util.formatInvariant(
RTP_MAP_ATTR_AUDIO_FMT, payloadType, mediaEncoding, clockRate, channelCount);
}
}
/** The media types allowed in a SDP media description. */

View File

@ -26,6 +26,7 @@ import static org.junit.Assert.assertThrows;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AacUtil;
import com.google.android.exoplayer2.util.MimeTypes;
@ -80,6 +81,132 @@ public class RtspMediaTrackTest {
assertThat(format).isEqualTo(expectedFormat);
}
@Test
public void generatePayloadFormat_withPcmuMediaDescription_succeeds() {
MediaDescription mediaDescription =
new MediaDescription.Builder(
MEDIA_TYPE_AUDIO, /* port= */ 0, RTP_AVP_PROFILE, /* payloadType= */ 0)
.setConnection("IN IP4 0.0.0.0")
.addAttribute(ATTR_CONTROL, "track2")
.build();
RtpPayloadFormat format = RtspMediaTrack.generatePayloadFormat(mediaDescription);
RtpPayloadFormat expectedFormat =
new RtpPayloadFormat(
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_MLAW)
.setChannelCount(1)
.setSampleRate(8_000)
.build(),
/* rtpPayloadType= */ 0,
/* clockRate= */ 8_000,
/* fmtpParameters= */ ImmutableMap.of());
assertThat(format).isEqualTo(expectedFormat);
}
@Test
public void generatePayloadFormat_withPcmaMediaDescription_succeeds() {
// m=audio 0 RTP/AVP 0
// c=IN IP4 0.0.0.0
// a=control:track2
int pcmaPayloadType = 8;
int pcmaClockRate = 8_000;
MediaDescription mediaDescription =
new MediaDescription.Builder(
MEDIA_TYPE_AUDIO,
/* port= */ 0,
RTP_AVP_PROFILE,
/* payloadType= */ pcmaPayloadType)
.setConnection("IN IP4 0.0.0.0")
.addAttribute(ATTR_CONTROL, "track2")
.build();
RtpPayloadFormat format = RtspMediaTrack.generatePayloadFormat(mediaDescription);
RtpPayloadFormat expectedFormat =
new RtpPayloadFormat(
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_ALAW)
.setChannelCount(1)
.setSampleRate(pcmaClockRate)
.build(),
/* rtpPayloadType= */ pcmaPayloadType,
/* clockRate= */ pcmaClockRate,
/* fmtpParameters= */ ImmutableMap.of());
assertThat(format).isEqualTo(expectedFormat);
}
@Test
public void generatePayloadFormat_withL16StereoMediaDescription_succeeds() {
// m=audio 0 RTP/AVP 0
// c=IN IP4 0.0.0.0
// a=control:track2
int l16StereoPayloadType = 10;
int l16StereoClockRate = 44_100;
MediaDescription mediaDescription =
new MediaDescription.Builder(
MEDIA_TYPE_AUDIO,
/* port= */ 0,
RTP_AVP_PROFILE,
/* payloadType= */ l16StereoPayloadType)
.setConnection("IN IP4 0.0.0.0")
.addAttribute(ATTR_CONTROL, "track2")
.build();
RtpPayloadFormat format = RtspMediaTrack.generatePayloadFormat(mediaDescription);
RtpPayloadFormat expectedFormat =
new RtpPayloadFormat(
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_RAW)
.setChannelCount(2)
.setSampleRate(l16StereoClockRate)
.setPcmEncoding(C.ENCODING_PCM_16BIT_BIG_ENDIAN)
.build(),
/* rtpPayloadType= */ l16StereoPayloadType,
/* clockRate= */ l16StereoClockRate,
/* fmtpParameters= */ ImmutableMap.of());
assertThat(format).isEqualTo(expectedFormat);
}
@Test
public void generatePayloadFormat_withL16MonoMediaDescription_succeeds() {
// m=audio 0 RTP/AVP 0
// c=IN IP4 0.0.0.0
// a=control:track2
int l16MonoPayloadType = 11;
int l16MonoClockRate = 44_100;
MediaDescription mediaDescription =
new MediaDescription.Builder(
MEDIA_TYPE_AUDIO,
/* port= */ 0,
RTP_AVP_PROFILE,
/* payloadType= */ l16MonoPayloadType)
.setConnection("IN IP4 0.0.0.0")
.addAttribute(ATTR_CONTROL, "track2")
.build();
RtpPayloadFormat format = RtspMediaTrack.generatePayloadFormat(mediaDescription);
RtpPayloadFormat expectedFormat =
new RtpPayloadFormat(
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_RAW)
.setChannelCount(1)
.setSampleRate(l16MonoClockRate)
.setPcmEncoding(C.ENCODING_PCM_16BIT_BIG_ENDIAN)
.build(),
/* rtpPayloadType= */ l16MonoPayloadType,
/* clockRate= */ l16MonoClockRate,
/* fmtpParameters= */ ImmutableMap.of());
assertThat(format).isEqualTo(expectedFormat);
}
@Test
public void generatePayloadFormat_withFmtpTrailingSemicolon_succeeds() {
MediaDescription mediaDescription =