From a38059d1098535a0c558346c5453626011b430e4 Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Wed, 13 Jul 2022 17:43:03 +0530 Subject: [PATCH 1/6] Update H263 Reader to handle missing frames/fragments. Change-Id: I43dfbabcbe686c31cb54e6b95688af1fa35a3d24 --- .../exoplayer/rtsp/reader/RtpH263Reader.java | 65 ++++++++++++++----- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java index 4aedc65aad8..147887e665e 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.rtsp.reader; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; import androidx.media3.common.C; @@ -61,12 +62,22 @@ private boolean isKeyFrame; private boolean isOutputFormatSet; private long startTimeOffsetUs; + private long sampleTimeUsOfFragmentedSample; + /** + * Whether the first packet of a H263 frame is received, it mark the start of a H263 partition. A + * H263 frame can be split into multiple RTP packets. + */ + private boolean gotFirstPacketOfH263Frame; /** Creates an instance. */ public RtpH263Reader(RtpPayloadFormat payloadFormat) { this.payloadFormat = payloadFormat; firstReceivedTimestamp = C.TIME_UNSET; previousSequenceNumber = C.INDEX_UNSET; + isKeyFrame = false; + fragmentedSampleSizeBytes = 0; + sampleTimeUsOfFragmentedSample = C.TIME_UNSET; + gotFirstPacketOfH263Frame = false; } @Override @@ -76,7 +87,10 @@ public void createTracks(ExtractorOutput extractorOutput, int trackId) { } @Override - public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {} + public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { + checkState(firstReceivedTimestamp == C.TIME_UNSET); + firstReceivedTimestamp = timestamp; + } @Override public void consume( @@ -103,6 +117,12 @@ public void consume( } if (pBitIsSet) { + if (gotFirstPacketOfH263Frame && fragmentedSampleSizeBytes > 0) { + // Received new H263 fragment, output data of previous fragment to decoder. + outputSampleMetadataForFragmentedPackets(); + } + gotFirstPacketOfH263Frame = true; + int payloadStartCode = data.peekUnsignedByte() & 0xFC; // Packets that begin with a Picture Start Code(100000). Refer RFC4629 Section 6.1. if (payloadStartCode < PICTURE_START_CODE) { @@ -113,10 +133,10 @@ public void consume( data.getData()[currentPosition] = 0; data.getData()[currentPosition + 1] = 0; data.setPosition(currentPosition); - } else { + } else if (gotFirstPacketOfH263Frame) { // Check that this packet is in the sequence of the previous packet. int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber); - if (sequenceNumber != expectedSequenceNumber) { + if (sequenceNumber < expectedSequenceNumber) { Log.w( TAG, Util.formatInvariant( @@ -125,6 +145,12 @@ public void consume( expectedSequenceNumber, sequenceNumber)); return; } + } else { + Log.w( + TAG, + "First payload octet of the H263 packet is not the beginning of a new H263 partition," + + " Dropping current packet."); + return; } if (fragmentedSampleSizeBytes == 0) { @@ -141,20 +167,11 @@ public void consume( // Write the video sample. trackOutput.sampleData(data, fragmentSize); fragmentedSampleSizeBytes += fragmentSize; + sampleTimeUsOfFragmentedSample = + toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp); if (rtpMarker) { - if (firstReceivedTimestamp == C.TIME_UNSET) { - firstReceivedTimestamp = timestamp; - } - long timeUs = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp); - trackOutput.sampleMetadata( - timeUs, - isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0, - fragmentedSampleSizeBytes, - /* offset= */ 0, - /* cryptoData= */ null); - fragmentedSampleSizeBytes = 0; - isKeyFrame = false; + outputSampleMetadataForFragmentedPackets(); } previousSequenceNumber = sequenceNumber; } @@ -211,6 +228,24 @@ private void parseVopHeader(ParsableByteArray data, boolean gotResolution) { isKeyFrame = false; } + /** + * Outputs sample metadata. + * + *

Call this method only when receiving a end of VP8 partition + */ + private void outputSampleMetadataForFragmentedPackets() { + trackOutput.sampleMetadata( + sampleTimeUsOfFragmentedSample, + isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0, + fragmentedSampleSizeBytes, + /* offset= */ 0, + /* cryptoData= */ null); + fragmentedSampleSizeBytes = 0; + sampleTimeUsOfFragmentedSample = C.TIME_UNSET; + isKeyFrame = false; + gotFirstPacketOfH263Frame = false; + } + private static long toSampleUs( long startTimeOffsetUs, long rtpTimestamp, long firstReceivedRtpTimestamp) { return startTimeOffsetUs From da47771d105bd6d45135e6258c5775cf3ab563b9 Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Tue, 5 Jul 2022 18:00:54 +0530 Subject: [PATCH 2/6] Add test for Rtp H263 Reader Change-Id: I57d57881ef5c158d41be1bf1e3714332d50cd3a9 --- .../rtsp/reader/RtpH263ReaderTest.java | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java new file mode 100644 index 00000000000..7e5560ce9fc --- /dev/null +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java @@ -0,0 +1,198 @@ +/* + * 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.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 RtpH263Reader}. + */ +@RunWith(AndroidJUnit4.class) +public final class RtpH263ReaderTest { + private static final byte[] FRAME_1_FRAGMENT_1_DATA = + getBytesFromHexString("80020c0419b7b7d9591f03023e0c37b"); + private final RtpPacket FRAME_1_FRAGMENT_1 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168056L) + .setSequenceNumber(40289) + .setMarker(false) + .setPayloadData( + Bytes.concat( + /*payload header */ getBytesFromHexString("0400"), FRAME_1_FRAGMENT_1_DATA)) + .build(); + private static final byte[] FRAME_1_FRAGMENT_2_DATA = + getBytesFromHexString("03140e0e77d5e83021a0c37"); + private static final RtpPacket FRAME_1_FRAGMENT_2 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168056L) + .setSequenceNumber(40290) + .setMarker(true) + .setPayloadData( + Bytes.concat( + /*payload header */ getBytesFromHexString("0000"), FRAME_1_FRAGMENT_2_DATA)) + .build(); + private static final byte[] FRAME_1_DATA = + Bytes.concat(getBytesFromHexString("0000"), FRAME_1_FRAGMENT_1_DATA, FRAME_1_FRAGMENT_2_DATA); + + private static final byte[] FRAME_2_FRAGMENT_1_DATA = + getBytesFromHexString("800a0e023ffffffffffffffffff"); + private final RtpPacket FRAME_2_FRAGMENT_1 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168344L) + .setSequenceNumber(40291) + .setMarker(false) + .setPayloadData( + Bytes.concat( + /*payload header */ getBytesFromHexString("0400"), FRAME_2_FRAGMENT_1_DATA)) + .build(); + private static final byte[] FRAME_2_FRAGMENT_2_DATA = + getBytesFromHexString("830df80c501839dfccdbdbecac"); + private static final RtpPacket FRAME_2_FRAGMENT_2 = + new RtpPacket.Builder() + .setTimestamp((int) 2599168344L) + .setSequenceNumber(40292) + .setMarker(true) + .setPayloadData( + Bytes.concat( + /*payload header */ getBytesFromHexString("0000"), FRAME_2_FRAGMENT_2_DATA)) + .build(); + private static final byte[] FRAME_2_DATA = + Bytes.concat(getBytesFromHexString("0000"), FRAME_2_FRAGMENT_1_DATA, FRAME_2_FRAGMENT_2_DATA); + + private static final RtpPayloadFormat H263_FORMAT = + new RtpPayloadFormat( + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_H263) + .setWidth(352) + .setHeight(288) + .build(), + /* rtpPayloadType= */ 96, + /* clockRate= */ 90_000, + /* fmtpParameters= */ ImmutableMap.of()); + + @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() { + RtpH263Reader h263Reader = new RtpH263Reader(H263_FORMAT); + h263Reader.createTracks(extractorOutput, /* trackId= */ 0); + h263Reader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, FRAME_1_FRAGMENT_2); + consume(h263Reader, FRAME_2_FRAGMENT_1); + consume(h263Reader, 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() { + RtpH263Reader h263Reader = new RtpH263Reader(H263_FORMAT); + h263Reader.createTracks(extractorOutput, /* trackId= */ 0); + h263Reader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(h263Reader, FRAME_1_FRAGMENT_2); + consume(h263Reader, FRAME_2_FRAGMENT_1); + consume(h263Reader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(1); + assertThat(trackOutput.getSampleData(0)).isEqualTo(FRAME_2_DATA); + assertThat(trackOutput.getSampleTimeUs(0)).isEqualTo(3200); + } + + @Test + public void consume_fragmentedFrameMissingBoundaryFragment() { + RtpH263Reader h263Reader = new RtpH263Reader(H263_FORMAT); + h263Reader.createTracks(extractorOutput, /* trackId= */ 0); + h263Reader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, FRAME_2_FRAGMENT_1); + consume(h263Reader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)) + .isEqualTo(Bytes.concat(getBytesFromHexString("0000"), 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); + } + + @Test + public void consume_outOfOrderPackets() { + RtpH263Reader h263Reader = new RtpH263Reader(H263_FORMAT); + h263Reader.createTracks(extractorOutput, /* trackId= */ 0); + h263Reader.onReceivingFirstPacket( + FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); + consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, FRAME_2_FRAGMENT_1); + consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, FRAME_2_FRAGMENT_2); + + trackOutput = extractorOutput.trackOutputs.get(0); + assertThat(trackOutput.getSampleCount()).isEqualTo(2); + assertThat(trackOutput.getSampleData(0)) + .isEqualTo(Bytes.concat(getBytesFromHexString("0000"), 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(RtpH263Reader h263Reader, RtpPacket rtpPacket) { + ParsableByteArray packetData = new ParsableByteArray(); + packetData.reset(rtpPacket.payloadData); + h263Reader.consume( + packetData, + rtpPacket.timestamp, + rtpPacket.sequenceNumber, + /* isFrameBoundary= */ rtpPacket.marker); + } +} From 3bacb1646c9ecd01e403465c7643888e83a9e79d Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Mon, 18 Jul 2022 10:43:41 +0530 Subject: [PATCH 3/6] Keep the input data constant in consume method Earlier, the consume method of RtpH263Reader was changing the bytes of the input bitstream during header parse. This commit copies the input into local context and changes the local variable as per the specifications thus keeping the input constant. --- .../exoplayer/rtsp/reader/RtpH263Reader.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java index 147887e665e..341d64256c5 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java @@ -104,7 +104,8 @@ public void consume( // | RR |P|V| PLEN |PEBIT| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ int currentPosition = data.getPosition(); - int header = data.readUnsignedShort(); + ParsableByteArray bitstreamData = new ParsableByteArray(data.getData().clone()); + int header = bitstreamData.readUnsignedShort(); boolean pBitIsSet = (header & 0x400) > 0; // Check if optional V (Video Redundancy Coding), PLEN or PEBIT is present, RFC4629 Section 5.1. @@ -123,16 +124,16 @@ public void consume( } gotFirstPacketOfH263Frame = true; - int payloadStartCode = data.peekUnsignedByte() & 0xFC; + int payloadStartCode = bitstreamData.peekUnsignedByte() & 0xFC; // Packets that begin with a Picture Start Code(100000). Refer RFC4629 Section 6.1. if (payloadStartCode < PICTURE_START_CODE) { Log.w(TAG, "Picture start Code (PSC) missing, dropping packet."); return; } // Setting first two bytes of the start code. Refer RFC4629 Section 6.1.1. - data.getData()[currentPosition] = 0; - data.getData()[currentPosition + 1] = 0; - data.setPosition(currentPosition); + bitstreamData.getData()[currentPosition] = 0; + bitstreamData.getData()[currentPosition + 1] = 0; + bitstreamData.setPosition(currentPosition); } else if (gotFirstPacketOfH263Frame) { // Check that this packet is in the sequence of the previous packet. int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber); @@ -154,7 +155,7 @@ public void consume( } if (fragmentedSampleSizeBytes == 0) { - parseVopHeader(data, isOutputFormatSet); + parseVopHeader(bitstreamData, isOutputFormatSet); if (!isOutputFormatSet && isKeyFrame) { if (width != payloadFormat.format.width || height != payloadFormat.format.height) { trackOutput.format( @@ -163,9 +164,9 @@ public void consume( isOutputFormatSet = true; } } - int fragmentSize = data.bytesLeft(); + int fragmentSize = bitstreamData.bytesLeft(); // Write the video sample. - trackOutput.sampleData(data, fragmentSize); + trackOutput.sampleData(bitstreamData, fragmentSize); fragmentedSampleSizeBytes += fragmentSize; sampleTimeUsOfFragmentedSample = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp); From 69a716f633c466652bd6fabcae0502a1a0279398 Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Mon, 18 Jul 2022 10:44:51 +0530 Subject: [PATCH 4/6] fix review comments in RtpH263ReaderTest --- .../media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java index 7e5560ce9fc..7f010b6f6a8 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java @@ -42,7 +42,7 @@ public final class RtpH263ReaderTest { private static final byte[] FRAME_1_FRAGMENT_1_DATA = getBytesFromHexString("80020c0419b7b7d9591f03023e0c37b"); - private final RtpPacket FRAME_1_FRAGMENT_1 = + private static final RtpPacket FRAME_1_FRAGMENT_1 = new RtpPacket.Builder() .setTimestamp((int) 2599168056L) .setSequenceNumber(40289) @@ -67,7 +67,7 @@ public final class RtpH263ReaderTest { private static final byte[] FRAME_2_FRAGMENT_1_DATA = getBytesFromHexString("800a0e023ffffffffffffffffff"); - private final RtpPacket FRAME_2_FRAGMENT_1 = + private static final RtpPacket FRAME_2_FRAGMENT_1 = new RtpPacket.Builder() .setTimestamp((int) 2599168344L) .setSequenceNumber(40291) @@ -174,7 +174,7 @@ public void consume_outOfOrderPackets() { FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); consume(h263Reader, FRAME_1_FRAGMENT_1); consume(h263Reader, FRAME_2_FRAGMENT_1); - consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, FRAME_1_FRAGMENT_2); consume(h263Reader, FRAME_2_FRAGMENT_2); trackOutput = extractorOutput.trackOutputs.get(0); From c7fbf3437fba0b57c4039304c6240d4f5a05a614 Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Tue, 19 Jul 2022 11:40:01 +0530 Subject: [PATCH 5/6] Revert "Keep the input data constant in consume method" This reverts commit 3bacb1646c9ecd01e403465c7643888e83a9e79d. --- .../exoplayer/rtsp/reader/RtpH263Reader.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java index 341d64256c5..147887e665e 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/RtpH263Reader.java @@ -104,8 +104,7 @@ public void consume( // | RR |P|V| PLEN |PEBIT| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ int currentPosition = data.getPosition(); - ParsableByteArray bitstreamData = new ParsableByteArray(data.getData().clone()); - int header = bitstreamData.readUnsignedShort(); + int header = data.readUnsignedShort(); boolean pBitIsSet = (header & 0x400) > 0; // Check if optional V (Video Redundancy Coding), PLEN or PEBIT is present, RFC4629 Section 5.1. @@ -124,16 +123,16 @@ public void consume( } gotFirstPacketOfH263Frame = true; - int payloadStartCode = bitstreamData.peekUnsignedByte() & 0xFC; + int payloadStartCode = data.peekUnsignedByte() & 0xFC; // Packets that begin with a Picture Start Code(100000). Refer RFC4629 Section 6.1. if (payloadStartCode < PICTURE_START_CODE) { Log.w(TAG, "Picture start Code (PSC) missing, dropping packet."); return; } // Setting first two bytes of the start code. Refer RFC4629 Section 6.1.1. - bitstreamData.getData()[currentPosition] = 0; - bitstreamData.getData()[currentPosition + 1] = 0; - bitstreamData.setPosition(currentPosition); + data.getData()[currentPosition] = 0; + data.getData()[currentPosition + 1] = 0; + data.setPosition(currentPosition); } else if (gotFirstPacketOfH263Frame) { // Check that this packet is in the sequence of the previous packet. int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber); @@ -155,7 +154,7 @@ public void consume( } if (fragmentedSampleSizeBytes == 0) { - parseVopHeader(bitstreamData, isOutputFormatSet); + parseVopHeader(data, isOutputFormatSet); if (!isOutputFormatSet && isKeyFrame) { if (width != payloadFormat.format.width || height != payloadFormat.format.height) { trackOutput.format( @@ -164,9 +163,9 @@ public void consume( isOutputFormatSet = true; } } - int fragmentSize = bitstreamData.bytesLeft(); + int fragmentSize = data.bytesLeft(); // Write the video sample. - trackOutput.sampleData(bitstreamData, fragmentSize); + trackOutput.sampleData(data, fragmentSize); fragmentedSampleSizeBytes += fragmentSize; sampleTimeUsOfFragmentedSample = toSampleUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp); From ef57a061b74d3d5ab5c2c5aa41aa725e70bbee6d Mon Sep 17 00:00:00 2001 From: Manisha Jajoo Date: Tue, 19 Jul 2022 11:45:39 +0530 Subject: [PATCH 6/6] Pass local copy of input to RtpH263ReaderTest's consume method This change is done to keep the frame data unchanged. RtpH263Reader changes the header data in input, so to send the same RTP packet across multiple tests, each test copies the frame data into a new packet and sends that to the reader. --- .../rtsp/reader/RtpH263ReaderTest.java | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java index 7f010b6f6a8..2bbef4befd0 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/reader/RtpH263ReaderTest.java @@ -28,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Bytes; +import java.util.Arrays; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -118,10 +119,10 @@ public void consume_validPackets() { h263Reader.createTracks(extractorOutput, /* trackId= */ 0); h263Reader.onReceivingFirstPacket( FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); - consume(h263Reader, FRAME_1_FRAGMENT_1); + consume(h263Reader, copyPacket(FRAME_1_FRAGMENT_1)); consume(h263Reader, FRAME_1_FRAGMENT_2); - consume(h263Reader, FRAME_2_FRAGMENT_1); - consume(h263Reader, FRAME_2_FRAGMENT_2); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_2)); trackOutput = extractorOutput.trackOutputs.get(0); assertThat(trackOutput.getSampleCount()).isEqualTo(2); @@ -137,9 +138,9 @@ public void consume_fragmentedFrameMissingFirstFragment() { h263Reader.createTracks(extractorOutput, /* trackId= */ 0); h263Reader.onReceivingFirstPacket( FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); - consume(h263Reader, FRAME_1_FRAGMENT_2); - consume(h263Reader, FRAME_2_FRAGMENT_1); - consume(h263Reader, FRAME_2_FRAGMENT_2); + consume(h263Reader, copyPacket(FRAME_1_FRAGMENT_2)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_2)); trackOutput = extractorOutput.trackOutputs.get(0); assertThat(trackOutput.getSampleCount()).isEqualTo(1); @@ -153,9 +154,9 @@ public void consume_fragmentedFrameMissingBoundaryFragment() { h263Reader.createTracks(extractorOutput, /* trackId= */ 0); h263Reader.onReceivingFirstPacket( FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); - consume(h263Reader, FRAME_1_FRAGMENT_1); - consume(h263Reader, FRAME_2_FRAGMENT_1); - consume(h263Reader, FRAME_2_FRAGMENT_2); + consume(h263Reader, copyPacket(FRAME_1_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_2)); trackOutput = extractorOutput.trackOutputs.get(0); assertThat(trackOutput.getSampleCount()).isEqualTo(2); @@ -172,10 +173,10 @@ public void consume_outOfOrderPackets() { h263Reader.createTracks(extractorOutput, /* trackId= */ 0); h263Reader.onReceivingFirstPacket( FRAME_1_FRAGMENT_1.timestamp, FRAME_1_FRAGMENT_1.sequenceNumber); - consume(h263Reader, FRAME_1_FRAGMENT_1); - consume(h263Reader, FRAME_2_FRAGMENT_1); - consume(h263Reader, FRAME_1_FRAGMENT_2); - consume(h263Reader, FRAME_2_FRAGMENT_2); + consume(h263Reader, copyPacket(FRAME_1_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_1)); + consume(h263Reader, copyPacket(FRAME_1_FRAGMENT_2)); + consume(h263Reader, copyPacket(FRAME_2_FRAGMENT_2)); trackOutput = extractorOutput.trackOutputs.get(0); assertThat(trackOutput.getSampleCount()).isEqualTo(2); @@ -195,4 +196,23 @@ private static void consume(RtpH263Reader h263Reader, RtpPacket rtpPacket) { rtpPacket.sequenceNumber, /* isFrameBoundary= */ rtpPacket.marker); } + + private static RtpPacket copyPacket(RtpPacket packet) { + RtpPacket.Builder builder = + new RtpPacket.Builder() + .setPadding(packet.padding) + .setMarker(packet.marker) + .setPayloadType(packet.payloadType) + .setSequenceNumber(packet.sequenceNumber) + .setTimestamp(packet.timestamp) + .setSsrc(packet.ssrc); + + if (packet.csrc.length > 0) { + builder.setCsrc(Arrays.copyOf(packet.csrc, packet.csrc.length)); + } + if (packet.payloadData.length > 0) { + builder.setPayloadData(Arrays.copyOf(packet.payloadData, packet.payloadData.length)); + } + return builder.build(); + } }