From ac528d3ab0b6bbf134e29326e5c33ed16d422944 Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Fri, 11 Mar 2022 11:48:08 +0530 Subject: [PATCH 1/4] Add support for RTSP PCM/WAV and G711/WAV Added PCM RTP packet reader and added support for PCM 8 bit, 16 bit, ALAW and MULAW playback through RTSP. Change-Id: If0a187b55faa89850a159e17eae28358d6634799 --- .../exoplayer/rtsp/RtpPayloadFormat.java | 36 ++++++++ .../media3/exoplayer/rtsp/RtspMediaTrack.java | 10 ++- .../DefaultRtpPayloadReaderFactory.java | 4 + .../exoplayer/rtsp/reader/RtpPCMReader.java | 89 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java index 297353167b9..d835c647517 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java @@ -16,6 +16,7 @@ package androidx.media3.exoplayer.rtsp; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; @@ -40,6 +41,10 @@ public final class RtpPayloadFormat { private static final String RTP_MEDIA_MPEG4_GENERIC = "MPEG4-GENERIC"; private static final String RTP_MEDIA_H264 = "H264"; private static final String RTP_MEDIA_H265 = "H265"; + private static final String RTP_MEDIA_PCM_L8 = "L8"; + private static final String RTP_MEDIA_PCM_L16 = "L16"; + private static final String RTP_MEDIA_PCMA = "PCMA"; + private static final String RTP_MEDIA_PCMU = "PCMU"; /** Returns whether the format of a {@link MediaDescription} is supported. */ public static boolean isFormatSupported(MediaDescription mediaDescription) { @@ -48,6 +53,10 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) { case RTP_MEDIA_H264: case RTP_MEDIA_H265: case RTP_MEDIA_MPEG4_GENERIC: + case RTP_MEDIA_PCM_L8: + case RTP_MEDIA_PCM_L16: + case RTP_MEDIA_PCMA: + case RTP_MEDIA_PCMU: return true; default: return false; @@ -71,6 +80,33 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) { return MimeTypes.VIDEO_H265; case RTP_MEDIA_MPEG4_GENERIC: return MimeTypes.AUDIO_AAC; + case RTP_MEDIA_PCM_L8: + case RTP_MEDIA_PCM_L16: + return MimeTypes.AUDIO_RAW; + case RTP_MEDIA_PCMA: + return MimeTypes.AUDIO_ALAW; + case RTP_MEDIA_PCMU: + return MimeTypes.AUDIO_MLAW; + default: + throw new IllegalArgumentException(mediaType); + } + } + + /** + * Gets the PCM Encoding type for RAW track that is associated with the RTP media type. + * + *

For instance, RTP media type "L8" maps to {@link C.PcmEncoding#ENCODING_PCM_8BIT}. + * + * @throws IllegalArgumentException When the media type is not supported/recognized. + */ + public static int getPCMEncodingFromRtpMediaType(String mediaType) { + switch (mediaType) { + case RTP_MEDIA_PCM_L8: + // Refer to RFC3551#section-4.5.10 + return C.ENCODING_PCM_8BIT; + case RTP_MEDIA_PCM_L16: + // Refer to RFC3551#section-4.5.11 + return C.ENCODING_PCM_16BIT_BIG_ENDIAN; default: throw new IllegalArgumentException(mediaType); } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java index 7547f1ea188..a702260f28e 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java @@ -20,6 +20,7 @@ import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.exoplayer.rtsp.MediaDescription.MEDIA_TYPE_AUDIO; import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getMimeTypeFromRtpMediaType; +import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getPCMEncodingFromRtpMediaType; import static androidx.media3.exoplayer.rtsp.SessionDescription.ATTR_CONTROL; import static androidx.media3.extractor.NalUnitUtil.NAL_START_CODE; @@ -129,8 +130,15 @@ public int hashCode() { checkArgument(!fmtpParameters.isEmpty()); processH265FmtpAttribute(formatBuilder, fmtpParameters); break; + case MimeTypes.AUDIO_RAW: + int pcmEncoding = + getPCMEncodingFromRtpMediaType(mediaDescription.rtpMapAttribute.mediaEncoding); + formatBuilder.setPcmEncoding(pcmEncoding); + break; case MimeTypes.AUDIO_AC3: - // AC3 does not require a FMTP attribute. Fall through. + case MimeTypes.AUDIO_ALAW: + case MimeTypes.AUDIO_MLAW: + // Does not require a FMTP attribute. Fall through. default: // Do nothing. } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java index 888939b7e89..5c563e31a8b 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java @@ -40,6 +40,10 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) { return new RtpH264Reader(payloadFormat); case MimeTypes.VIDEO_H265: return new RtpH265Reader(payloadFormat); + case MimeTypes.AUDIO_RAW: + case MimeTypes.AUDIO_ALAW: + case MimeTypes.AUDIO_MLAW: + return new RtpPCMReader(payloadFormat); default: // No supported reader, returning null. } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java new file mode 100644 index 00000000000..a80d1d97174 --- /dev/null +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java @@ -0,0 +1,89 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.exoplayer.rtsp.reader; + +import static androidx.media3.common.util.Assertions.checkState; + +import androidx.media3.common.C; +import androidx.media3.common.util.ParsableByteArray; +import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; +import androidx.media3.extractor.ExtractorOutput; +import androidx.media3.extractor.TrackOutput; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * Parses byte stream carried on RTP packets, and extracts PCM frames. Refer to RFC3551 for more + * details. + */ +/* package */ public final class RtpPCMReader implements RtpPayloadReader { + + private final RtpPayloadFormat payloadFormat; + private @MonotonicNonNull TrackOutput trackOutput; + private long firstReceivedTimestamp; + private long startTimeOffsetUs; + + public RtpPCMReader(RtpPayloadFormat payloadFormat) { + this.payloadFormat = payloadFormat; + firstReceivedTimestamp = C.TIME_UNSET; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, int trackId) { + trackOutput = extractorOutput.track(trackId, C.TRACK_TYPE_AUDIO); + trackOutput.format(payloadFormat.format); + } + + @Override + public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { + checkState(firstReceivedTimestamp == C.TIME_UNSET); + firstReceivedTimestamp = timestamp; + } + + @Override + public void consume( + ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) { + long sampleTimeUs = + toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate); + int size = data.bytesLeft(); + trackOutput.sampleData(data, size); + trackOutput + .sampleMetadata( + /* timeUs= */ sampleTimeUs, + /* flags= */ C.BUFFER_FLAG_KEY_FRAME, + /* size= */ size, + /* offset= */ 0, + /* cryptoData= */ null); + } + + @Override + public void seek(long nextRtpTimestamp, long timeUs) { + firstReceivedTimestamp = nextRtpTimestamp; + startTimeOffsetUs = timeUs; + } + + /** + * Returns the correct sample time from RTP timestamp, accounting for the given sampling rate. + */ + private static long toSampleUs( + long startTimeOffsetUs, long rtpTimestamp, long firstReceivedRtpTimestamp, int sampleRate) { + return startTimeOffsetUs + + Util.scaleLargeTimestamp( + rtpTimestamp - firstReceivedRtpTimestamp, + /* multiplier= */ C.MICROS_PER_SECOND, + /* divisor= */ sampleRate); + } +} From 500c879b8ac5f04e2708550d45d3f7179284fa6c Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Fri, 11 Mar 2022 11:48:08 +0530 Subject: [PATCH 2/4] Modify RtpPcmReader Rename a few variable to be more relevant Add detailed java docs --- .../media3/exoplayer/rtsp/RtpPayloadFormat.java | 17 +++++++++-------- .../media3/exoplayer/rtsp/RtspMediaTrack.java | 8 ++++---- .../reader/DefaultRtpPayloadReaderFactory.java | 2 +- .../{RtpPCMReader.java => RtpPcmReader.java} | 11 ++++++----- 4 files changed, 20 insertions(+), 18 deletions(-) rename libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/{RtpPCMReader.java => RtpPcmReader.java} (92%) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java index d835c647517..b90d6820fd2 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java @@ -93,22 +93,23 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) { } /** - * Gets the PCM Encoding type for RAW track that is associated with the RTP media type. + * Gets the PCM Encoding type for audio encoding of track. * - *

For instance, RTP media type "L8" maps to {@link C.PcmEncoding#ENCODING_PCM_8BIT}. + *

For instance, Audio encoding "L8" has 8 bits/sample and thus maps to + * {@link C.PcmEncoding#ENCODING_PCM_8BIT}. Refer to RFC3551 Section 4.5 Table 1. * - * @throws IllegalArgumentException When the media type is not supported/recognized. + * @throws IllegalArgumentException When the audio encoding is not supported/recognized. */ - public static int getPCMEncodingFromRtpMediaType(String mediaType) { - switch (mediaType) { + public static int getPcmEncodingFromAudioEncoding(String mediaEncoding) { + switch (mediaEncoding) { case RTP_MEDIA_PCM_L8: - // Refer to RFC3551#section-4.5.10 + // Refer to RFC3551 Section 4.5.10 return C.ENCODING_PCM_8BIT; case RTP_MEDIA_PCM_L16: - // Refer to RFC3551#section-4.5.11 + // Refer to RFC3551 Section 4.5.11 return C.ENCODING_PCM_16BIT_BIG_ENDIAN; default: - throw new IllegalArgumentException(mediaType); + throw new IllegalArgumentException("Unsupported RAW Audio Encoding " + mediaEncoding); } } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java index a702260f28e..5b682db2b64 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java @@ -20,7 +20,7 @@ import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.exoplayer.rtsp.MediaDescription.MEDIA_TYPE_AUDIO; import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getMimeTypeFromRtpMediaType; -import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getPCMEncodingFromRtpMediaType; +import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getPcmEncodingFromAudioEncoding; import static androidx.media3.exoplayer.rtsp.SessionDescription.ATTR_CONTROL; import static androidx.media3.extractor.NalUnitUtil.NAL_START_CODE; @@ -103,8 +103,9 @@ public int hashCode() { } int rtpPayloadType = mediaDescription.rtpMapAttribute.payloadType; + String mediaEncoding = mediaDescription.rtpMapAttribute.mediaEncoding; - String mimeType = getMimeTypeFromRtpMediaType(mediaDescription.rtpMapAttribute.mediaEncoding); + String mimeType = getMimeTypeFromRtpMediaType(mediaEncoding); formatBuilder.setSampleMimeType(mimeType); int clockRate = mediaDescription.rtpMapAttribute.clockRate; @@ -131,8 +132,7 @@ public int hashCode() { processH265FmtpAttribute(formatBuilder, fmtpParameters); break; case MimeTypes.AUDIO_RAW: - int pcmEncoding = - getPCMEncodingFromRtpMediaType(mediaDescription.rtpMapAttribute.mediaEncoding); + int pcmEncoding = getPcmEncodingFromAudioEncoding(mediaEncoding); formatBuilder.setPcmEncoding(pcmEncoding); break; case MimeTypes.AUDIO_AC3: diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java index 5c563e31a8b..1455695775a 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java @@ -43,7 +43,7 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) { case MimeTypes.AUDIO_RAW: case MimeTypes.AUDIO_ALAW: case MimeTypes.AUDIO_MLAW: - return new RtpPCMReader(payloadFormat); + return new RtpPcmReader(payloadFormat); default: // No supported reader, returning null. } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java similarity index 92% rename from libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java rename to libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java index a80d1d97174..f8b1cb25418 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPCMReader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java @@ -29,16 +29,17 @@ * Parses byte stream carried on RTP packets, and extracts PCM frames. Refer to RFC3551 for more * details. */ -/* package */ public final class RtpPCMReader implements RtpPayloadReader { +/* package */ public final class RtpPcmReader implements RtpPayloadReader { private final RtpPayloadFormat payloadFormat; private @MonotonicNonNull TrackOutput trackOutput; private long firstReceivedTimestamp; private long startTimeOffsetUs; - public RtpPCMReader(RtpPayloadFormat payloadFormat) { + public RtpPcmReader(RtpPayloadFormat payloadFormat) { this.payloadFormat = payloadFormat; firstReceivedTimestamp = C.TIME_UNSET; + startTimeOffsetUs = 0; } @Override @@ -62,9 +63,9 @@ public void consume( trackOutput.sampleData(data, size); trackOutput .sampleMetadata( - /* timeUs= */ sampleTimeUs, - /* flags= */ C.BUFFER_FLAG_KEY_FRAME, - /* size= */ size, + sampleTimeUs, + C.BUFFER_FLAG_KEY_FRAME, + size, /* offset= */ 0, /* cryptoData= */ null); } From 3f8a68004c2c63b02f080fc7f84efd97e8846c4b Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Mon, 21 Mar 2022 17:15:51 +0530 Subject: [PATCH 3/4] Add PCM and G711 Reader Test Add RtpPcmReader tests and warning for out of order packets in RtpPcmReader. Change-Id: I1554fa0a944dad00248a0a41fefad958da073a21 --- .../exoplayer/rtsp/reader/RtpPcmReader.java | 17 ++ .../rtsp/reader/RtpPcmReaderTest.java | 182 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java index f8b1cb25418..9d3402c94ca 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReader.java @@ -17,9 +17,11 @@ import static androidx.media3.common.util.Assertions.checkState; +import android.util.Log; import androidx.media3.common.C; import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.rtsp.RtpPacket; import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; import androidx.media3.extractor.ExtractorOutput; import androidx.media3.extractor.TrackOutput; @@ -31,15 +33,18 @@ */ /* package */ public final class RtpPcmReader implements RtpPayloadReader { + private static final String TAG = "RtpPcmReader"; private final RtpPayloadFormat payloadFormat; private @MonotonicNonNull TrackOutput trackOutput; private long firstReceivedTimestamp; private long startTimeOffsetUs; + private int previousSequenceNumber; public RtpPcmReader(RtpPayloadFormat payloadFormat) { this.payloadFormat = payloadFormat; firstReceivedTimestamp = C.TIME_UNSET; startTimeOffsetUs = 0; + previousSequenceNumber = C.INDEX_UNSET; } @Override @@ -57,6 +62,17 @@ public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { @Override public void consume( ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) { + if (previousSequenceNumber != C.INDEX_UNSET) { + int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber); + if (sequenceNumber < expectedSequenceNumber) { + Log.w( + TAG, + Util.formatInvariant( + "Received RTP packet with unexpected sequence number. Expected: %d; received: %d.", + expectedSequenceNumber, sequenceNumber)); + } + } + long sampleTimeUs = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate); int size = data.bytesLeft(); @@ -68,6 +84,7 @@ public void consume( size, /* offset= */ 0, /* cryptoData= */ null); + previousSequenceNumber = sequenceNumber; } @Override diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java new file mode 100644 index 00000000000..3118649acca --- /dev/null +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.exoplayer.rtsp.reader; + +import static androidx.media3.common.util.Util.getBytesFromHexString; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.util.ParsableByteArray; +import androidx.media3.exoplayer.rtsp.RtpPacket; +import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; +import androidx.media3.extractor.ExtractorOutput; +import androidx.media3.test.utils.FakeTrackOutput; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableMap; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** + * Unit test for {@link RtpPcmReader}. + */ +@RunWith(AndroidJUnit4.class) +public final class RtpPcmReaderTest { + + private static final String FRAMEDATA1 = "01020304"; + private static final String FRAMEDATA2 = "05060708"; + + private static final RtpPacket FRAME1 = + createRtpPacket(2599168056L, 40289, getBytesFromHexString(FRAMEDATA1)); + private static final RtpPacket FRAME2 = + createRtpPacket(2599169592L, 40290, getBytesFromHexString(FRAMEDATA2)); + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + private ParsableByteArray packetData; + private FakeTrackOutput trackOutput; + private RtpPcmReader pcmReader; + @Mock + private ExtractorOutput extractorOutput; + + @Before + public void setUp() { + packetData = new ParsableByteArray(); + trackOutput = new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ true); + when(extractorOutput.track(anyInt(), anyInt())).thenReturn(trackOutput); + } + + @Test + public void consume_AllPackets_8bit() { + pcmReader = new RtpPcmReader( + new RtpPayloadFormat( + new Format.Builder() + .setChannelCount(2) + .setSampleMimeType(MimeTypes.AUDIO_WAV) + .setPcmEncoding(C.ENCODING_PCM_8BIT) + .setSampleRate(48_000) + .build(), + /* rtpPayloadType= */ 97, + /* clockRate= */ 48_000, + /* fmtpParameters= */ ImmutableMap.of())); + pcmReader.createTracks(extractorOutput, /* trackId= */ 0); + pcmReader.onReceivingFirstPacket(FRAME1.timestamp, FRAME1.sequenceNumber); + consume(FRAME1); + consume(FRAME2); + + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(getBytesFromHexString(FRAMEDATA1)); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(getBytesFromHexString(FRAMEDATA2)); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(32000); + } + + @Test + public void consume_AllPackets_16bit() { + pcmReader = new RtpPcmReader( + new RtpPayloadFormat( + new Format.Builder() + .setChannelCount(1) + .setSampleMimeType(MimeTypes.AUDIO_WAV) + .setPcmEncoding(C.ENCODING_PCM_16BIT_BIG_ENDIAN) + .setSampleRate(60_000) + .build(), + /* rtpPayloadType= */ 97, + /* clockRate= */ 60_000, + /* fmtpParameters= */ ImmutableMap.of())); + pcmReader.createTracks(extractorOutput, /* trackId= */ 0); + pcmReader.onReceivingFirstPacket(FRAME1.timestamp, FRAME1.sequenceNumber); + consume(FRAME1); + consume(FRAME2); + + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(getBytesFromHexString(FRAMEDATA1)); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(getBytesFromHexString(FRAMEDATA2)); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(25600); + } + + @Test + public void consume_AllPackets_ALAW() { + pcmReader = new RtpPcmReader( + new RtpPayloadFormat( + new Format.Builder() + .setChannelCount(2) + .setSampleMimeType(MimeTypes.AUDIO_ALAW) + .setSampleRate(16_000) + .build(), + /* rtpPayloadType= */ 97, + /* clockRate= */ 16_000, + /* fmtpParameters= */ ImmutableMap.of())); + pcmReader.createTracks(extractorOutput, /* trackId= */ 0); + pcmReader.onReceivingFirstPacket(FRAME1.timestamp, FRAME1.sequenceNumber); + consume(FRAME1); + consume(FRAME2); + + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(getBytesFromHexString(FRAMEDATA1)); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(getBytesFromHexString(FRAMEDATA2)); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(96000); + } + + @Test + public void consume_AllPackets_MLAW() { + pcmReader = new RtpPcmReader( + new RtpPayloadFormat( + new Format.Builder() + .setChannelCount(2) + .setSampleMimeType(MimeTypes.AUDIO_MLAW) + .setSampleRate(24_000) + .build(), + /* rtpPayloadType= */ 97, + /* clockRate= */ 24_000, + /* fmtpParameters= */ ImmutableMap.of())); + pcmReader.createTracks(extractorOutput, /* trackId= */ 0); + pcmReader.onReceivingFirstPacket(FRAME1.timestamp, FRAME1.sequenceNumber); + consume(FRAME1); + consume(FRAME2); + + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(getBytesFromHexString(FRAMEDATA1)); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(getBytesFromHexString(FRAMEDATA2)); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(64000); + } + + private static RtpPacket createRtpPacket( + long timestamp, int sequenceNumber, byte[] payloadData) { + return new RtpPacket.Builder() + .setTimestamp((int) timestamp) + .setSequenceNumber(sequenceNumber) + .setMarker(false) + .setPayloadData(payloadData) + .build(); + } + + private void consume(RtpPacket frame) { + packetData.reset(frame.payloadData); + pcmReader.consume(packetData, frame.timestamp, frame.sequenceNumber, frame.marker); + } +} From e3dcf385e9e996237d858357cc919cfe93814529 Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Tue, 29 Mar 2022 10:22:31 +0530 Subject: [PATCH 4/4] Update RtpPcmReaderTest Create FakeTrackOutput with deduplicateConsecutiveFormats as false Change-Id: Id3d551465a77d5148638b00ae2b164b505044ae2 --- .../androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java index 3118649acca..942c6fdb273 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpPcmReaderTest.java @@ -63,7 +63,7 @@ public final class RtpPcmReaderTest { @Before public void setUp() { packetData = new ParsableByteArray(); - trackOutput = new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ true); + trackOutput = new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ false); when(extractorOutput.track(anyInt(), anyInt())).thenReturn(trackOutput); }