From 048aaf34dc969faef4849e5405d21491a58aadc3 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Wed, 24 Aug 2022 18:48:06 +0530 Subject: [PATCH 1/8] Add support for RTSP Mp4a-Latm Added Mp4a-latm RTP Packet reader and added support for Mp4a-latm playback through RTSP. Change-Id: Ia590393f53ca880af926907843f6bea9ff0f4b35 --- .../exoplayer/rtsp/RtpPayloadFormat.java | 9 +- .../media3/exoplayer/rtsp/RtspMediaTrack.java | 3 +- .../DefaultRtpPayloadReaderFactory.java | 6 +- .../rtsp/reader/RtpMp4aPayloadReader.java | 157 ++++++++++++++++++ 4 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.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 55bb804642..8327c72bfd 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 @@ -55,6 +55,7 @@ public final class RtpPayloadFormat { private static final String RTP_MEDIA_PCMU = "PCMU"; private static final String RTP_MEDIA_VP8 = "VP8"; private static final String RTP_MEDIA_VP9 = "VP9"; + public static final String RTP_MEDIA_MPEG4_AUDIO = "MP4A-LATM"; /** Returns whether the format of a {@link MediaDescription} is supported. */ public static boolean isFormatSupported(MediaDescription mediaDescription) { @@ -66,6 +67,7 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) { case RTP_MEDIA_H263_2000: case RTP_MEDIA_H264: case RTP_MEDIA_H265: + case RTP_MEDIA_MPEG4_AUDIO: case RTP_MEDIA_MPEG4_VIDEO: case RTP_MEDIA_MPEG4_GENERIC: case RTP_MEDIA_OPUS: @@ -97,6 +99,7 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) { case RTP_MEDIA_AMR_WB: return MimeTypes.AUDIO_AMR_WB; case RTP_MEDIA_MPEG4_GENERIC: + case RTP_MEDIA_MPEG4_AUDIO: return MimeTypes.AUDIO_AAC; case RTP_MEDIA_OPUS: return MimeTypes.AUDIO_OPUS; @@ -142,6 +145,7 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) { public final Format format; /** The format parameters, mapped from the SDP FMTP attribute (RFC2327 Page 22). */ public final ImmutableMap fmtpParameters; + public final String mediaEncoding; /** * Creates a new instance. @@ -154,12 +158,13 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) { * empty if unset. The keys and values are specified in the RFCs for specific formats. For * instance, RFC3640 Section 4.1 defines keys like profile-level-id and config. */ - public RtpPayloadFormat( - Format format, int rtpPayloadType, int clockRate, Map fmtpParameters) { + public RtpPayloadFormat(Format format, int rtpPayloadType, int clockRate, Map fmtpParameters, String mediaEncoding) { this.rtpPayloadType = rtpPayloadType; this.clockRate = clockRate; this.format = format; this.fmtpParameters = ImmutableMap.copyOf(fmtpParameters); + this.mediaEncoding = mediaEncoding; } @Override 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 c8de624326..55b5805ab0 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 @@ -267,7 +267,8 @@ public int hashCode() { } checkArgument(clockRate > 0); - return new RtpPayloadFormat(formatBuilder.build(), rtpPayloadType, clockRate, fmtpParameters); + return new RtpPayloadFormat( + formatBuilder.build(), rtpPayloadType, clockRate, fmtpParameters, mediaEncoding); } private static int inferChannelCount(int encodingParameter, String mimeType) { 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 0c1ee768b5..8acaed8295 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 @@ -35,7 +35,11 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) { case MimeTypes.AUDIO_AC3: return new RtpAc3Reader(payloadFormat); case MimeTypes.AUDIO_AAC: - return new RtpAacReader(payloadFormat); + if(payloadFormat.mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO)){ + return new RtpMp4aPayloadReader(payloadFormat); + } else { + return new RtpAacReader(payloadFormat); + } case MimeTypes.AUDIO_AMR_NB: case MimeTypes.AUDIO_AMR_WB: return new RtpAmrReader(payloadFormat); diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java new file mode 100644 index 0000000000..0f52fc60a2 --- /dev/null +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java @@ -0,0 +1,157 @@ +/* + * 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 static androidx.media3.common.util.Assertions.checkStateNotNull; +import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Util.castNonNull; + +import androidx.annotation.Nullable; +import androidx.media3.common.C; +import androidx.media3.common.ParserException; +import androidx.media3.common.util.ParsableBitArray; +import androidx.media3.common.util.ParsableByteArray; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; +import androidx.media3.extractor.ExtractorOutput; +import androidx.media3.extractor.TrackOutput; + +import com.google.common.collect.ImmutableMap; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * Parses an MP4A-LATM byte stream carried on RTP packets, and extracts MP4A-LATM Access Units. + * Refer to RFC3016 for more details. + */ +@UnstableApi +/* package */ final class RtpMp4aPayloadReader implements RtpPayloadReader { + private static final String TAG = "RtpMp4aLatmReader"; + + private static final String PARAMETER_MP4A_CONFIG = "config"; + + private final RtpPayloadFormat payloadFormat; + private @MonotonicNonNull TrackOutput trackOutput; + private long firstReceivedTimestamp; + /** The combined size of a sample that is fragmented into multiple subFrames. */ + private int fragmentedSampleSizeBytes; + private long startTimeOffsetUs; + + /** Creates an instance. */ + public RtpMp4aPayloadReader(RtpPayloadFormat payloadFormat) { + this.payloadFormat = payloadFormat; + firstReceivedTimestamp = C.TIME_UNSET; + fragmentedSampleSizeBytes = 0; + // The start time offset must be 0 until the first seek. + startTimeOffsetUs = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, int trackId) { + trackOutput = extractorOutput.track(trackId, C.TRACK_TYPE_VIDEO); + castNonNull(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) + throws ParserException { + checkStateNotNull(trackOutput); + + int sampleOffset = 0; + int numSubFrames = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters); + for (int subFrame = 0; subFrame <= numSubFrames; subFrame++) { + int sampleLength = 0; + + /* Each subframe starts with a variable length encoding */ + for (; sampleOffset < data.bytesLeft(); sampleOffset++) { + sampleLength += data.getData()[sampleOffset] & 0xff; + if ((data.getData()[sampleOffset] & 0xff) != 0xff) { + break; + } + } + sampleOffset++; + data.setPosition(sampleOffset); + + // Write the audio sample + trackOutput.sampleData(data, sampleLength); + sampleOffset += sampleLength; + fragmentedSampleSizeBytes += sampleLength; + } + if (rtpMarker) { + long timeUs = + toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate); + trackOutput.sampleMetadata( + timeUs, C.BUFFER_FLAG_KEY_FRAME, fragmentedSampleSizeBytes, 0, null); + fragmentedSampleSizeBytes = 0; + } + } + + @Override + public void seek(long nextRtpTimestamp, long timeUs) { + firstReceivedTimestamp = nextRtpTimestamp; + fragmentedSampleSizeBytes = 0; + startTimeOffsetUs = timeUs; + } + + // Internal methods. + 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); + } + + /** + * Parses an MPEG-4 Audio Stream Mux configuration, as defined in ISO/IEC14496-3. FMTP attribute + * contains config which is a byte array containing the MPEG-4 Audio Stream Mux configuration to + * parse. + * + * @param fmtpAttributes The format parameters, mapped from the SDP FMTP attribute. + * @return The number of subframes. + * @throws ParserException If the MPEG-4 Audio Stream Mux configuration cannot be parsed due to + * unsupported audioMuxVersion. + */ + public static Integer getNumOfSubframesFromMpeg4AudioConfig( + ImmutableMap fmtpAttributes) throws ParserException { + @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG); + int numSubFrames = 0; + if (configInput != null && configInput.length() % 2 == 0) { + byte[] configBuffer = Util.getBytesFromHexString(configInput); + ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); + int audioMuxVersion = scratchBits.readBits(1); + if (audioMuxVersion == 0) { + checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); + numSubFrames = scratchBits.readBits(6); + checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); + checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); + } else { + throw ParserException.createForMalformedDataOfUnknownType( + "unsupported audio mux version: " + audioMuxVersion, null); + } + } + return numSubFrames; + } +} From 9648529591400416ff991f3f355bc3412bab6eb9 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Tue, 6 Sep 2022 17:44:42 +0530 Subject: [PATCH 2/8] Fix review comment in RTP Mp4a-latm Reader Change-Id: I7c5c12d86589bdc3f88c336573759bcfb0e7ce1b --- .../DefaultRtpPayloadReaderFactory.java | 2 +- ...aPayloadReader.java => RtpMp4aReader.java} | 58 +++++++++++++------ 2 files changed, 41 insertions(+), 19 deletions(-) rename libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/{RtpMp4aPayloadReader.java => RtpMp4aReader.java} (76%) 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 8acaed8295..7120126561 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 @@ -36,7 +36,7 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) { return new RtpAc3Reader(payloadFormat); case MimeTypes.AUDIO_AAC: if(payloadFormat.mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO)){ - return new RtpMp4aPayloadReader(payloadFormat); + return new RtpMp4aReader(payloadFormat); } else { return new RtpAacReader(payloadFormat); } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java similarity index 76% rename from libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java rename to libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java index 0f52fc60a2..a171bcaf49 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aPayloadReader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java @@ -19,6 +19,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Util.castNonNull; +import static androidx.media3.exoplayer.rtsp.reader.RtpReaderUtils.toSampleTimeUs; import androidx.annotation.Nullable; import androidx.media3.common.C; @@ -27,6 +28,7 @@ import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.UnstableApi; 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; @@ -39,7 +41,7 @@ * Refer to RFC3016 for more details. */ @UnstableApi -/* package */ final class RtpMp4aPayloadReader implements RtpPayloadReader { +/* package */ final class RtpMp4aReader implements RtpPayloadReader { private static final String TAG = "RtpMp4aLatmReader"; private static final String PARAMETER_MP4A_CONFIG = "config"; @@ -47,17 +49,22 @@ private final RtpPayloadFormat payloadFormat; private @MonotonicNonNull TrackOutput trackOutput; private long firstReceivedTimestamp; + private int previousSequenceNumber; /** The combined size of a sample that is fragmented into multiple subFrames. */ private int fragmentedSampleSizeBytes; private long startTimeOffsetUs; + private long sampleTimeUsOfFragmentedSample; + private int numSubFrames; /** Creates an instance. */ - public RtpMp4aPayloadReader(RtpPayloadFormat payloadFormat) { + public RtpMp4aReader(RtpPayloadFormat payloadFormat) { this.payloadFormat = payloadFormat; firstReceivedTimestamp = C.TIME_UNSET; + previousSequenceNumber = C.INDEX_UNSET; fragmentedSampleSizeBytes = 0; // The start time offset must be 0 until the first seek. startTimeOffsetUs = 0; + sampleTimeUsOfFragmentedSample = C.TIME_UNSET; } @Override @@ -70,7 +77,12 @@ public void createTracks(ExtractorOutput extractorOutput, int trackId) { public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { checkState(firstReceivedTimestamp == C.TIME_UNSET); firstReceivedTimestamp = timestamp; - } + try { + numSubFrames = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters); + } catch (ParserException e) { + e.printStackTrace(); + } +} @Override public void consume( @@ -78,8 +90,11 @@ public void consume( throws ParserException { checkStateNotNull(trackOutput); + int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber); + if(fragmentedSampleSizeBytes > 0 && expectedSequenceNumber < sequenceNumber) { + outputSampleMetadataForFragmentedPackets(); + } int sampleOffset = 0; - int numSubFrames = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters); for (int subFrame = 0; subFrame <= numSubFrames; subFrame++) { int sampleLength = 0; @@ -98,13 +113,12 @@ public void consume( sampleOffset += sampleLength; fragmentedSampleSizeBytes += sampleLength; } + sampleTimeUsOfFragmentedSample = toSampleTimeUs(startTimeOffsetUs, timestamp, + firstReceivedTimestamp, payloadFormat.clockRate); if (rtpMarker) { - long timeUs = - toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate); - trackOutput.sampleMetadata( - timeUs, C.BUFFER_FLAG_KEY_FRAME, fragmentedSampleSizeBytes, 0, null); - fragmentedSampleSizeBytes = 0; + outputSampleMetadataForFragmentedPackets(); } + previousSequenceNumber = sequenceNumber; } @Override @@ -115,14 +129,6 @@ public void seek(long nextRtpTimestamp, long timeUs) { } // Internal methods. - 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); - } /** * Parses an MPEG-4 Audio Stream Mux configuration, as defined in ISO/IEC14496-3. FMTP attribute @@ -134,7 +140,7 @@ private static long toSampleUs( * @throws ParserException If the MPEG-4 Audio Stream Mux configuration cannot be parsed due to * unsupported audioMuxVersion. */ - public static Integer getNumOfSubframesFromMpeg4AudioConfig( + private static Integer getNumOfSubframesFromMpeg4AudioConfig( ImmutableMap fmtpAttributes) throws ParserException { @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG); int numSubFrames = 0; @@ -154,4 +160,20 @@ public static Integer getNumOfSubframesFromMpeg4AudioConfig( } return numSubFrames; } + + /** + * Outputs sample metadata. + * + *

Call this method only when receiving a end of Mpeg4 partition + */ + private void outputSampleMetadataForFragmentedPackets() { + trackOutput.sampleMetadata( + sampleTimeUsOfFragmentedSample, + C.BUFFER_FLAG_KEY_FRAME, + fragmentedSampleSizeBytes, + /* offset= */ 0, + /* cryptoData= */ null); + fragmentedSampleSizeBytes = 0; + sampleTimeUsOfFragmentedSample = C.TIME_UNSET; + } } From 15f9655e9f13b635f4aef73037ad7a9db565208b Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Mon, 5 Sep 2022 17:40:46 +0530 Subject: [PATCH 3/8] Added Rtp Mp4a-Latm Reader Test Change-Id: I0054e54183df0bb9370bf3fe7047076e285e1e8f --- .../rtsp/reader/RtpMp4aReaderTest.java | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReaderTest.java diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReaderTest.java new file mode 100644 index 0000000000..05e6100cea --- /dev/null +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReaderTest.java @@ -0,0 +1,175 @@ +/* + * 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 androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.ParserException; +import androidx.media3.common.util.ParsableByteArray; +import androidx.media3.exoplayer.rtsp.RtpPacket; +import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; +import androidx.media3.test.utils.FakeExtractorOutput; +import androidx.media3.test.utils.FakeTrackOutput; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Bytes; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit test for {@link RtpMp4aReader}. */ +@RunWith(AndroidJUnit4.class) +public final class RtpMp4aReaderTest { + private static final byte[] FRAME_1_FRAGMENT_1_DATA = + getBytesFromHexString("0102"); + private static final RtpPacket FRAME_1_FRAGMENT_1 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168056L) + .setSequenceNumber(40289) + .setMarker(false) + .setPayloadData(Bytes.concat( + /*payload size */ getBytesFromHexString("02"), FRAME_1_FRAGMENT_1_DATA)) + .build(); + private static final byte[] FRAME_1_FRAGMENT_2_DATA = + getBytesFromHexString("030405"); + private static final RtpPacket FRAME_1_FRAGMENT_2 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168056L) + .setSequenceNumber(40290) + .setMarker(true) + .setPayloadData(Bytes.concat( + /*payload size */ getBytesFromHexString("03"), FRAME_1_FRAGMENT_2_DATA)) + .build(); + private static final byte[] FRAME_1_DATA = + Bytes.concat(FRAME_1_FRAGMENT_1_DATA, FRAME_1_FRAGMENT_2_DATA); + + private static final byte[] FRAME_2_FRAGMENT_1_DATA = + getBytesFromHexString("0607"); + private static final RtpPacket FRAME_2_FRAGMENT_1 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168344L) + .setSequenceNumber(40291) + .setMarker(false) + .setPayloadData(Bytes.concat( + /*payload size */ getBytesFromHexString("02"), FRAME_2_FRAGMENT_1_DATA)) + .build(); + private static final byte[] FRAME_2_FRAGMENT_2_DATA = + getBytesFromHexString("0809"); + private static final RtpPacket FRAME_2_FRAGMENT_2 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168344L) + .setSequenceNumber(40292) + .setMarker(true) + .setPayloadData(Bytes.concat( + /*payload size */ getBytesFromHexString("02"), FRAME_2_FRAGMENT_2_DATA)) + .build(); + private static final byte[] FRAME_2_DATA = + Bytes.concat(FRAME_2_FRAGMENT_1_DATA, FRAME_2_FRAGMENT_2_DATA); + + private static final RtpPayloadFormat MP4ALATM_FORMAT = + new RtpPayloadFormat( + new Format.Builder() + .setSampleMimeType(MimeTypes.AUDIO_AAC) + .setWidth(352) + .setHeight(288) + .build(), + /* rtpPayloadType= */ 96, + /* clockRate= */ 90_000, + /* fmtpParameters= */ ImmutableMap.of(), RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO); + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + private FakeTrackOutput trackOutput; + + private FakeExtractorOutput extractorOutput; + + @Before + public void setUp() { + extractorOutput = new FakeExtractorOutput(); + } + + @Test + public void consume_validPackets() throws ParserException { + RtpMp4aReader mp4aLatmReader = new RtpMp4aReader(MP4ALATM_FORMAT); + mp4aLatmReader.createTracks(extractorOutput, /* trackId= */ 0); + mp4aLatmReader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(mp4aLatmReader, FRAME_1_FRAGMENT_1); + consume(mp4aLatmReader, FRAME_1_FRAGMENT_2); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_1); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(FRAME_1_DATA); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(FRAME_2_DATA); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(3200); + } + + @Test + public void consume_fragmentedFrameMissingFirstFragment() throws ParserException { + RtpMp4aReader mp4aLatmReader = new RtpMp4aReader(MP4ALATM_FORMAT); + mp4aLatmReader.createTracks(extractorOutput, /* trackId= */ 0); + mp4aLatmReader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(mp4aLatmReader, FRAME_1_FRAGMENT_2); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_1); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(FRAME_1_FRAGMENT_2_DATA); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(FRAME_2_DATA); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(3200); + } + + @Test + public void consume_fragmentedFrameMissingBoundaryFragment() throws ParserException { + RtpMp4aReader mp4aLatmReader = new RtpMp4aReader(MP4ALATM_FORMAT); + mp4aLatmReader.createTracks(extractorOutput, /* trackId= */ 0); + mp4aLatmReader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(mp4aLatmReader, FRAME_1_FRAGMENT_1); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_1); + consume(mp4aLatmReader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)).isEqualTo(FRAME_1_FRAGMENT_1_DATA); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(0); + assertThat(trackOutput.getSampleData(1)).isEqualTo(FRAME_2_DATA); + assertThat(trackOutput.getSampleTimeUs(1)).isEqualTo(3200); + } + + private static void consume(RtpMp4aReader mpeg4Reader, RtpPacket rtpPacket) + throws ParserException { + ParsableByteArray packetData = new ParsableByteArray(); + packetData.reset(rtpPacket.payloadData); + mpeg4Reader.consume( + packetData, + rtpPacket.timestamp, + rtpPacket.sequenceNumber, + /* isFrameBoundary= */ rtpPacket.marker); + } +} \ No newline at end of file From 97afe69e9203ea48057a83eb3249a850056692e3 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Tue, 20 Sep 2022 02:14:55 +0530 Subject: [PATCH 4/8] Added support for CSD parsing in RTSP Mp4a-Latm Reader Added support for parsing CSD data to get sample Rate and Channel Count. Change-Id: I69fb0fa2cb11453d3b9e416925d3776eb6dc19a3 --- .../media3/exoplayer/rtsp/RtspMediaTrack.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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 55b5805ab0..9a2f226429 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 @@ -31,7 +31,9 @@ import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; +import androidx.media3.common.ParserException; import androidx.media3.common.util.CodecSpecificDataUtil; +import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.extractor.AacUtil; @@ -208,6 +210,13 @@ public int hashCode() { case MimeTypes.AUDIO_AAC: checkArgument(channelCount != C.INDEX_UNSET); checkArgument(!fmtpParameters.isEmpty()); + if(mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO)) { + Pair cfgOpts = getSampleRateAndChannelCountFromAudioConfig( + fmtpParameters, channelCount, clockRate); + channelCount = cfgOpts.first; + clockRate = cfgOpts.second; + formatBuilder.setSampleRate(clockRate).setChannelCount(channelCount); + } processAacFmtpAttribute(formatBuilder, fmtpParameters, channelCount, clockRate); break; case MimeTypes.AUDIO_AMR_NB: @@ -301,6 +310,38 @@ private static void processAacFmtpAttribute( AacUtil.buildAacLcAudioSpecificConfig(sampleRate, channelCount))); } + /** + * Parses an MPEG-4 Audio Stream Mux configuration, as defined in ISO/IEC14496-3. FMTP attribute + * contains config which is a byte array containing the MPEG-4 Audio Stream Mux configuration to + * parse. + */ + private static Pair getSampleRateAndChannelCountFromAudioConfig( + ImmutableMap fmtpAttributes, + int channelCount, + int sampleRate) { + @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG); + if (configInput != null && configInput.length() % 2 == 0) { + byte[] configBuffer = Util.getBytesFromHexString(configInput); + ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); + int audioMuxVersion = scratchBits.readBits(1); + if (audioMuxVersion == 0) { + checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); + scratchBits.readBits(6); + checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); + checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); + AacUtil.Config aacConfig = null; + try { + aacConfig = AacUtil.parseAudioSpecificConfig(scratchBits, false); + } catch (ParserException e) { + throw new IllegalArgumentException(e); + } + sampleRate = aacConfig.sampleRateHz; + channelCount = aacConfig.channelCount; + } + } + return Pair.create(channelCount, sampleRate); + } + private static void processMPEG4FmtpAttribute( Format.Builder formatBuilder, ImmutableMap fmtpAttributes) { @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG); From 4880057f9257bf881c0e63f07fb3c5d67fa438db Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Fri, 30 Sep 2022 13:55:52 +0530 Subject: [PATCH 5/8] Fix some more review comment in RTP Mp4a-Latm Reader Change-Id: I9033d0bd93c6129c64c41ce70fef26bf8a6e4b6e --- .../exoplayer/rtsp/reader/RtpMp4aReader.java | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java index a171bcaf49..76e62b4585 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java @@ -54,7 +54,7 @@ private int fragmentedSampleSizeBytes; private long startTimeOffsetUs; private long sampleTimeUsOfFragmentedSample; - private int numSubFrames; + private int numberOfSubframes; /** Creates an instance. */ public RtpMp4aReader(RtpPayloadFormat payloadFormat) { @@ -78,9 +78,9 @@ public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { checkState(firstReceivedTimestamp == C.TIME_UNSET); firstReceivedTimestamp = timestamp; try { - numSubFrames = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters); + numberOfSubframes = getNumOfSubframesFromMpeg4AudioConfig(payloadFormat.fmtpParameters); } catch (ParserException e) { - e.printStackTrace(); + throw new IllegalArgumentException(e); } } @@ -94,23 +94,30 @@ public void consume( if(fragmentedSampleSizeBytes > 0 && expectedSequenceNumber < sequenceNumber) { outputSampleMetadataForFragmentedPackets(); } - int sampleOffset = 0; - for (int subFrame = 0; subFrame <= numSubFrames; subFrame++) { + int audioPayloadOffset = 0; + for (int subFrame = 0; subFrame < numberOfSubframes; subFrame++) { int sampleLength = 0; - /* Each subframe starts with a variable length encoding */ - for (; sampleOffset < data.bytesLeft(); sampleOffset++) { - sampleLength += data.getData()[sampleOffset] & 0xff; - if ((data.getData()[sampleOffset] & 0xff) != 0xff) { + /** + * This implements PayloadLengthInfo() in Chapter 1.7.3.1, it's only support one program and + * one layer. + * Each subframe starts with a variable length encoding. + */ + for (; audioPayloadOffset < data.bytesLeft(); audioPayloadOffset++) { + int payloadMuxLength = data.peekUnsignedByte(); + sampleLength += payloadMuxLength; + if (payloadMuxLength != 0xff) { break; + } else { + data.setPosition(audioPayloadOffset + 1); } } - sampleOffset++; - data.setPosition(sampleOffset); + audioPayloadOffset++; + data.setPosition(audioPayloadOffset); - // Write the audio sample + /* Write the audio sample */ trackOutput.sampleData(data, sampleLength); - sampleOffset += sampleLength; + audioPayloadOffset+= sampleLength; fragmentedSampleSizeBytes += sampleLength; } sampleTimeUsOfFragmentedSample = toSampleTimeUs(startTimeOffsetUs, timestamp, @@ -140,17 +147,17 @@ public void seek(long nextRtpTimestamp, long timeUs) { * @throws ParserException If the MPEG-4 Audio Stream Mux configuration cannot be parsed due to * unsupported audioMuxVersion. */ - private static Integer getNumOfSubframesFromMpeg4AudioConfig( + private static int getNumOfSubframesFromMpeg4AudioConfig( ImmutableMap fmtpAttributes) throws ParserException { @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG); - int numSubFrames = 0; + int numberOfSubframes = 0; if (configInput != null && configInput.length() % 2 == 0) { byte[] configBuffer = Util.getBytesFromHexString(configInput); ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); int audioMuxVersion = scratchBits.readBits(1); if (audioMuxVersion == 0) { checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); - numSubFrames = scratchBits.readBits(6); + numberOfSubframes = scratchBits.readBits(6); checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); } else { @@ -158,7 +165,7 @@ private static Integer getNumOfSubframesFromMpeg4AudioConfig( "unsupported audio mux version: " + audioMuxVersion, null); } } - return numSubFrames; + return numberOfSubframes + 1; } /** From 9f8d69929df0af324eb47e5f298f90e42cc6dccc Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Fri, 30 Sep 2022 14:07:27 +0530 Subject: [PATCH 6/8] Fix review comment in CSD parsing of Mp4a-Latm Reader Change-Id: I6fc07d88a7dbc52fc2fbe0e5ad45a53f8f25c4fd --- .../media3/exoplayer/rtsp/RtspMediaTrack.java | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) 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 9a2f226429..577ffe2e24 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 @@ -55,6 +55,7 @@ private static final String PARAMETER_H265_SPROP_VPS = "sprop-vps"; private static final String PARAMETER_H265_SPROP_MAX_DON_DIFF = "sprop-max-don-diff"; private static final String PARAMETER_MP4V_CONFIG = "config"; + private static final String PARAMETER_MP4A_CPRESENT = "cpresent"; /** Prefix for the RFC6381 codecs string for AAC formats. */ private static final String AAC_CODECS_PREFIX = "mp4a.40."; @@ -211,11 +212,18 @@ public int hashCode() { checkArgument(channelCount != C.INDEX_UNSET); checkArgument(!fmtpParameters.isEmpty()); if(mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO)) { - Pair cfgOpts = getSampleRateAndChannelCountFromAudioConfig( - fmtpParameters, channelCount, clockRate); - channelCount = cfgOpts.first; - clockRate = cfgOpts.second; - formatBuilder.setSampleRate(clockRate).setChannelCount(channelCount); + boolean isCPresent = true; + if (fmtpParameters.get(PARAMETER_MP4A_CPRESENT) != null && fmtpParameters.get( + PARAMETER_MP4A_CPRESENT).equals("0")) { + isCPresent = false; + } + @Nullable String configInput = fmtpParameters.get(PARAMETER_MP4V_CONFIG); + if (!isCPresent && configInput != null && configInput.length() % 2 == 0) { + Pair configParameters = getSampleRateAndChannelCount(configInput); + channelCount = configParameters.first; + clockRate = configParameters.second; + formatBuilder.setSampleRate(clockRate).setChannelCount(channelCount); + } } processAacFmtpAttribute(formatBuilder, fmtpParameters, channelCount, clockRate); break; @@ -311,33 +319,30 @@ private static void processAacFmtpAttribute( } /** - * Parses an MPEG-4 Audio Stream Mux configuration, as defined in ISO/IEC14496-3. FMTP attribute - * contains config which is a byte array containing the MPEG-4 Audio Stream Mux configuration to - * parse. + * Returns a {@link Pair} of sample rate and channel count, by parsing the + * MPEG4 Audio Stream Mux configuration. + * + *

fmtp attribute {@code config} includes the MPEG4 Audio Stream Mux + * configuration (ISO/IEC14496-3, Chapter 1.7.3). */ - private static Pair getSampleRateAndChannelCountFromAudioConfig( - ImmutableMap fmtpAttributes, - int channelCount, - int sampleRate) { - @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG); - if (configInput != null && configInput.length() % 2 == 0) { - byte[] configBuffer = Util.getBytesFromHexString(configInput); - ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); - int audioMuxVersion = scratchBits.readBits(1); - if (audioMuxVersion == 0) { - checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); - scratchBits.readBits(6); - checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); - checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); - AacUtil.Config aacConfig = null; - try { - aacConfig = AacUtil.parseAudioSpecificConfig(scratchBits, false); - } catch (ParserException e) { - throw new IllegalArgumentException(e); - } - sampleRate = aacConfig.sampleRateHz; - channelCount = aacConfig.channelCount; + private static Pair getSampleRateAndChannelCount(String configInput) { + int channelCount = 0, sampleRate = 0; + byte[] configBuffer = Util.getBytesFromHexString(configInput); + ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); + int audioMuxVersion = scratchBits.readBits(1); + if (audioMuxVersion == 0) { + checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); + scratchBits.readBits(6); + checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); + checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); + @Nullable AacUtil.Config aacConfig = null; + try { + aacConfig = AacUtil.parseAudioSpecificConfig(scratchBits, false); + } catch (ParserException e) { + throw new IllegalArgumentException(e); } + sampleRate = aacConfig.sampleRateHz; + channelCount = aacConfig.channelCount; } return Pair.create(channelCount, sampleRate); } From 0ac84fe16f0af7a61c6a576816786f3db8655af1 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Mon, 3 Oct 2022 17:38:22 +0530 Subject: [PATCH 7/8] Fix review comment in mp4a-latm Reader Change-Id: I004f4a9ed9bd7cc48708d82a01c945789d1e9e5e --- .../media3/exoplayer/rtsp/RtspMediaTrack.java | 33 +++++++++---------- .../exoplayer/rtsp/reader/RtpMp4aReader.java | 8 ++--- 2 files changed, 17 insertions(+), 24 deletions(-) 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 577ffe2e24..7146dc0033 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 @@ -55,7 +55,7 @@ private static final String PARAMETER_H265_SPROP_VPS = "sprop-vps"; private static final String PARAMETER_H265_SPROP_MAX_DON_DIFF = "sprop-max-don-diff"; private static final String PARAMETER_MP4V_CONFIG = "config"; - private static final String PARAMETER_MP4A_CPRESENT = "cpresent"; + private static final String PARAMETER_MP4A_C_PRESENT = "cpresent"; /** Prefix for the RFC6381 codecs string for AAC formats. */ private static final String AAC_CODECS_PREFIX = "mp4a.40."; @@ -212,13 +212,13 @@ public int hashCode() { checkArgument(channelCount != C.INDEX_UNSET); checkArgument(!fmtpParameters.isEmpty()); if(mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_AUDIO)) { - boolean isCPresent = true; - if (fmtpParameters.get(PARAMETER_MP4A_CPRESENT) != null && fmtpParameters.get( - PARAMETER_MP4A_CPRESENT).equals("0")) { - isCPresent = false; + boolean isConfigPresent = true; + if (fmtpParameters.get(PARAMETER_MP4A_C_PRESENT) != null && fmtpParameters.get( + PARAMETER_MP4A_C_PRESENT).equals("0")) { + isConfigPresent = false; } @Nullable String configInput = fmtpParameters.get(PARAMETER_MP4V_CONFIG); - if (!isCPresent && configInput != null && configInput.length() % 2 == 0) { + if (!isConfigPresent && configInput != null && configInput.length() % 2 == 0) { Pair configParameters = getSampleRateAndChannelCount(configInput); channelCount = configParameters.first; clockRate = configParameters.second; @@ -326,25 +326,22 @@ private static void processAacFmtpAttribute( * configuration (ISO/IEC14496-3, Chapter 1.7.3). */ private static Pair getSampleRateAndChannelCount(String configInput) { - int channelCount = 0, sampleRate = 0; - byte[] configBuffer = Util.getBytesFromHexString(configInput); - ParsableBitArray scratchBits = new ParsableBitArray(configBuffer); - int audioMuxVersion = scratchBits.readBits(1); + ParsableBitArray config = new ParsableBitArray(Util.getBytesFromHexString(configInput)); + int audioMuxVersion = config .readBits(1); if (audioMuxVersion == 0) { - checkArgument(scratchBits.readBits(1) == 1, "Invalid allStreamsSameTimeFraming."); - scratchBits.readBits(6); - checkArgument(scratchBits.readBits(4) == 0, "Invalid numProgram."); - checkArgument(scratchBits.readBits(3) == 0, "Invalid numLayer."); + checkArgument(config .readBits(1) == 1, "Only supports one allStreamsSameTimeFraming."); + config .readBits(6); + checkArgument(config .readBits(4) == 0, "Only supports one program."); + checkArgument(config .readBits(3) == 0, "Only supports one numLayer."); @Nullable AacUtil.Config aacConfig = null; try { - aacConfig = AacUtil.parseAudioSpecificConfig(scratchBits, false); + aacConfig = AacUtil.parseAudioSpecificConfig(config , false); } catch (ParserException e) { throw new IllegalArgumentException(e); } - sampleRate = aacConfig.sampleRateHz; - channelCount = aacConfig.channelCount; + return Pair.create(aacConfig.channelCount, aacConfig.sampleRateHz); } - return Pair.create(channelCount, sampleRate); + throw new IllegalArgumentException ("Only support audio mux version 0"); } private static void processMPEG4FmtpAttribute( diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java index 76e62b4585..23e850ecdf 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpMp4aReader.java @@ -104,18 +104,14 @@ public void consume( * Each subframe starts with a variable length encoding. */ for (; audioPayloadOffset < data.bytesLeft(); audioPayloadOffset++) { - int payloadMuxLength = data.peekUnsignedByte(); + int payloadMuxLength = data.readUnsignedByte(); sampleLength += payloadMuxLength; if (payloadMuxLength != 0xff) { break; - } else { - data.setPosition(audioPayloadOffset + 1); } } - audioPayloadOffset++; - data.setPosition(audioPayloadOffset); - /* Write the audio sample */ + // Write the audio sample trackOutput.sampleData(data, sampleLength); audioPayloadOffset+= sampleLength; fragmentedSampleSizeBytes += sampleLength; From ce98d6da842bc5eb5ebad921a4648643fb814a25 Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Date: Tue, 4 Oct 2022 17:45:59 +0530 Subject: [PATCH 8/8] Fix review comment in CSD parsing of Mp4a-Latm Change-Id: I70c412870952e18826f43d218b074b2829127e10 --- .../java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 7146dc0033..a72a84540f 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 @@ -217,8 +217,9 @@ public int hashCode() { PARAMETER_MP4A_C_PRESENT).equals("0")) { isConfigPresent = false; } + checkArgument(!isConfigPresent, "cpresent == 0 means we need to parse config"); @Nullable String configInput = fmtpParameters.get(PARAMETER_MP4V_CONFIG); - if (!isConfigPresent && configInput != null && configInput.length() % 2 == 0) { + if (configInput != null && configInput.length() % 2 == 0) { Pair configParameters = getSampleRateAndChannelCount(configInput); channelCount = configParameters.first; clockRate = configParameters.second;