Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for RTSP Mp4a-Latm #162

Closed
wants to merge 11 commits into from
Expand Up @@ -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);
}
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -39,25 +41,30 @@
* 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";

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;
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved

/** 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
Expand All @@ -70,16 +77,24 @@ 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);
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved
} catch (ParserException e) {
e.printStackTrace();
}
}

@Override
public void consume(
ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker)
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++) {
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved
int sampleLength = 0;

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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(
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved
ImmutableMap<String, String> fmtpAttributes) throws ParserException {
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4A_CONFIG);
int numSubFrames = 0;
Expand All @@ -154,4 +160,20 @@ public static Integer getNumOfSubframesFromMpeg4AudioConfig(
}
return numSubFrames;
rakeshnitb marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Outputs sample metadata.
*
* <p>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;
}
}