Commit e7567d20 by Manisha Jajoo

Fix review comments in RtpMPEG4Reader

parent dfef2d13
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package androidx.media3.common.util; package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
...@@ -85,14 +87,15 @@ public final class CodecSpecificDataUtil { ...@@ -85,14 +87,15 @@ public final class CodecSpecificDataUtil {
* to parse. * to parse.
* @return A pair consisting of the width and the height. * @return A pair consisting of the width and the height.
*/ */
public static Pair<Integer, Integer> parseMpeg4VideoSpecificConfig(byte[] videoSpecificConfig) { public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
byte[] videoSpecificConfig) {
int offset = 0; int offset = 0;
boolean foundVOL = false; boolean foundVOL = false;
ParsableByteArray scdScratchBytes = new ParsableByteArray(videoSpecificConfig); ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
while (offset + 3 < videoSpecificConfig.length) { while (offset + 3 < videoSpecificConfig.length) {
if (scdScratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|| (videoSpecificConfig[offset + 3] & 0xf0) != VISUAL_OBJECT_LAYER_START) { || (videoSpecificConfig[offset + 3] & 0xf0) != VISUAL_OBJECT_LAYER_START) {
scdScratchBytes.setPosition(scdScratchBytes.getPosition() - 2); scratchBytes.setPosition(scratchBytes.getPosition() - 2);
offset++; offset++;
continue; continue;
} }
...@@ -100,57 +103,59 @@ public final class CodecSpecificDataUtil { ...@@ -100,57 +103,59 @@ public final class CodecSpecificDataUtil {
break; break;
} }
Assertions.checkArgument(foundVOL, "Invalid input. VOL not found"); checkArgument(foundVOL, "Invalid input: VOL not found.");
ParsableBitArray scdScratchBits = new ParsableBitArray(videoSpecificConfig); ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
scdScratchBits.skipBits((offset + 4) * 8); // Skip the start codecs from the bitstream
scdScratchBits.skipBits(1); // random_accessible_vol scratchBits.skipBits((offset + 4) * 8);
scdScratchBits.skipBits(8); // video_object_type_indication scratchBits.skipBits(1); // random_accessible_vol
scratchBits.skipBits(8); // video_object_type_indication
if (scdScratchBits.readBit()) { // object_layer_identifier if (scratchBits.readBit()) { // object_layer_identifier
scdScratchBits.skipBits(4); // video_object_layer_verid scratchBits.skipBits(4); // video_object_layer_verid
scdScratchBits.skipBits(3); // video_object_layer_priority scratchBits.skipBits(3); // video_object_layer_priority
} }
int aspectRatioInfo = scdScratchBits.readBits(4); int aspectRatioInfo = scratchBits.readBits(4);
if (aspectRatioInfo == EXTENDED_PAR) { if (aspectRatioInfo == EXTENDED_PAR) {
scdScratchBits.skipBits(8); // par_width scratchBits.skipBits(8); // par_width
scdScratchBits.skipBits(8); // par_height scratchBits.skipBits(8); // par_height
} }
if (scdScratchBits.readBit()) { // vol_control_parameters if (scratchBits.readBit()) { // vol_control_parameters
scdScratchBits.skipBits(2); // chroma_format scratchBits.skipBits(2); // chroma_format
scdScratchBits.skipBits(1); // low_delay scratchBits.skipBits(1); // low_delay
if (scdScratchBits.readBit()) { // vbv_parameters if (scratchBits.readBit()) { // vbv_parameters
scdScratchBits.skipBits(79); scratchBits.skipBits(79);
} }
} }
int videoObjectLayerShape = scdScratchBits.readBits(2); int videoObjectLayerShape = scratchBits.readBits(2);
Assertions.checkArgument(videoObjectLayerShape == RECTANGULAR, "Unsupported feature"); checkArgument(
videoObjectLayerShape == RECTANGULAR, "Only supports rectangular video object layer shape");
Assertions.checkArgument(scdScratchBits.readBit(), "Invalid input"); // marker_bit checkArgument(scratchBits.readBit()); // marker_bit
int vopTimeIncrementResolution = scdScratchBits.readBits(16); int vopTimeIncrementResolution = scratchBits.readBits(16);
Assertions.checkArgument(scdScratchBits.readBit(), "Invalid input"); // marker_bit checkArgument(scratchBits.readBit()); // marker_bit
if (scdScratchBits.readBit()) { // fixed_vop_rate if (scratchBits.readBit()) { // fixed_vop_rate
Assertions.checkArgument(vopTimeIncrementResolution > 0, "Invalid input"); checkArgument(vopTimeIncrementResolution > 0);
--vopTimeIncrementResolution; vopTimeIncrementResolution--;
int numBits = 0; int numBitsToSkip = 0;
while (vopTimeIncrementResolution > 0) { while (vopTimeIncrementResolution > 0) {
++numBits; numBitsToSkip++;
vopTimeIncrementResolution >>= 1; vopTimeIncrementResolution >>= 1;
} }
scdScratchBits.skipBits(numBits); // fixed_vop_time_increment scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
} }
Assertions.checkArgument(scdScratchBits.readBit(), "Invalid input"); // marker_bit checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerWidth = scdScratchBits.readBits(13); int videoObjectLayerWidth = scratchBits.readBits(13);
Assertions.checkArgument(scdScratchBits.readBit(), "Invalid input"); // marker_bit checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerHeight = scdScratchBits.readBits(13); int videoObjectLayerHeight = scratchBits.readBits(13);
Assertions.checkArgument(scdScratchBits.readBit(), "Invalid input"); // marker_bit checkArgument(scratchBits.readBit()); // marker_bit
scdScratchBits.skipBits(1); // interlaced scratchBits.skipBits(1); // interlaced
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight); return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
} }
......
...@@ -174,17 +174,14 @@ import com.google.common.collect.ImmutableMap; ...@@ -174,17 +174,14 @@ import com.google.common.collect.ImmutableMap;
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG); @Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG);
if (configInput != null) { if (configInput != null) {
byte[] csd = Util.getBytesFromHexString(configInput); byte[] csd = Util.getBytesFromHexString(configInput);
ImmutableList<byte[]> initializationData = ImmutableList.of(csd); formatBuilder.setInitializationData(ImmutableList.of(csd));
formatBuilder.setInitializationData(initializationData); Pair<Integer, Integer> resolution =
Pair<Integer, Integer> dimensions = CodecSpecificDataUtil.parseMpeg4VideoSpecificConfig(csd); CodecSpecificDataUtil.getVideoResolutionFromMpeg4VideoConfig(csd);
formatBuilder.setWidth(dimensions.first); formatBuilder.setWidth(resolution.first);
formatBuilder.setHeight(dimensions.second); formatBuilder.setHeight(resolution.second);
} }
@Nullable String profileLevel = fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID); @Nullable String profileLevel = fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID);
if (profileLevel == null) { formatBuilder.setCodecs(MPEG4_CODECS_PREFIX + (profileLevel == null ? "1" : profileLevel));
profileLevel = "1"; // default
}
formatBuilder.setCodecs(MPEG4_CODECS_PREFIX + profileLevel);
} }
private static void processH264FmtpAttribute( private static void processH264FmtpAttribute(
......
...@@ -23,6 +23,7 @@ import androidx.media3.common.ParserException; ...@@ -23,6 +23,7 @@ import androidx.media3.common.ParserException;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.rtsp.RtpPacket;
import androidx.media3.exoplayer.rtsp.RtpPayloadFormat; import androidx.media3.exoplayer.rtsp.RtpPayloadFormat;
import androidx.media3.extractor.ExtractorOutput; import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.TrackOutput; import androidx.media3.extractor.TrackOutput;
...@@ -38,9 +39,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -38,9 +39,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final long MEDIA_CLOCK_FREQUENCY = 90_000; private static final long MEDIA_CLOCK_FREQUENCY = 90_000;
/** /** VOP unit type. */
* VOP unit type.
*/
private static final int I_VOP = 0; private static final int I_VOP = 0;
private final RtpPayloadFormat payloadFormat; private final RtpPayloadFormat payloadFormat;
...@@ -66,22 +65,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -66,22 +65,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public void onReceivingFirstPacket(long timestamp, int sequenceNumber) { public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {}
Log.i(TAG, "RtpMPEG4Reader onReceivingFirstPacket");
}
@Override @Override
public void consume(ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) public void consume(ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker)
throws ParserException { throws ParserException {
if (previousSequenceNumber != C.INDEX_UNSET && sequenceNumber != (previousSequenceNumber + 1)) {
Log.e(TAG, "Packet loss");
}
checkStateNotNull(trackOutput); checkStateNotNull(trackOutput);
// Check that this packet is in the sequence of the previous packet.
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."
+ " Dropping packet.",
expectedSequenceNumber, sequenceNumber));
return;
}
}
// Parse VOP Type and get the buffer flags
int limit = data.bytesLeft(); int limit = data.bytesLeft();
trackOutput.sampleData(data, limit); trackOutput.sampleData(data, limit);
if (sampleLength == 0) bufferFlags = getBufferFlagsFromVop(data);
sampleLength += limit; sampleLength += limit;
parseVopType(data);
// Marker (M) bit: The marker bit is set to 1 to indicate the last RTP // Marker (M) bit: The marker bit is set to 1 to indicate the last RTP
// packet(or only RTP packet) of a VOP. When multiple VOPs are carried // packet(or only RTP packet) of a VOP. When multiple VOPs are carried
...@@ -95,7 +103,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -95,7 +103,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trackOutput.sampleMetadata(timeUs, bufferFlags, sampleLength, 0, null); trackOutput.sampleMetadata(timeUs, bufferFlags, sampleLength, 0, null);
sampleLength = 0; sampleLength = 0;
} }
previousSequenceNumber = sequenceNumber; previousSequenceNumber = sequenceNumber;
} }
...@@ -109,20 +116,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -109,20 +116,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Internal methods. // Internal methods.
/** /**
* Parses VOP Coding type * Parses VOP Coding type.
* *
* Sets {@link #bufferFlags} according to the VOP Coding type. * Sets {@link #bufferFlags} according to the VOP Coding type.
*/ */
private void parseVopType(ParsableByteArray data) { @C.BufferFlags
private static int getBufferFlagsFromVop(ParsableByteArray data) {
int flags = 0;
// search for VOP_START_CODE (00 00 01 B6) // search for VOP_START_CODE (00 00 01 B6)
byte[] inputData = data.getData(); byte[] inputData = data.getData();
byte[] startCode = {0x0, 0x0, 0x01, (byte) 0xB6}; byte[] startCode = new byte[] {0x0, 0x0, 0x1, (byte) 0xB6};
int vopStartCodePos = Bytes.indexOf(inputData, startCode); int vopStartCodePos = Bytes.indexOf(inputData, startCode);
if (vopStartCodePos != -1) { if (vopStartCodePos != -1) {
data.setPosition(vopStartCodePos + 4); data.setPosition(vopStartCodePos + 4);
int vopType = data.peekUnsignedByte() >> 6; int vopType = data.peekUnsignedByte() >> 6;
bufferFlags = getBufferFlagsFromVopType(vopType); flags = vopType == I_VOP ? C.BUFFER_FLAG_KEY_FRAME : 0;
} }
return flags;
} }
private static long toSampleUs( private static long toSampleUs(
...@@ -133,9 +143,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -133,9 +143,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* multiplier= */ C.MICROS_PER_SECOND, /* multiplier= */ C.MICROS_PER_SECOND,
/* divisor= */ MEDIA_CLOCK_FREQUENCY); /* divisor= */ MEDIA_CLOCK_FREQUENCY);
} }
@C.BufferFlags
private static int getBufferFlagsFromVopType(int vopType) {
return vopType == I_VOP ? C.BUFFER_FLAG_KEY_FRAME : 0;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment