Skip to content

Commit ade3452

Browse files
Merge pull request #53 from ittiam-systems:rtp_opus
PiperOrigin-RevId: 453490088
2 parents 527db57 + 165e706 commit ade3452

File tree

5 files changed

+372
-0
lines changed

5 files changed

+372
-0
lines changed

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtpPayloadFormat.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class RtpPayloadFormat {
4444
private static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
4545
private static final String RTP_MEDIA_H264 = "H264";
4646
private static final String RTP_MEDIA_H265 = "H265";
47+
private static final String RTP_MEDIA_OPUS = "OPUS";
4748
private static final String RTP_MEDIA_PCM_L8 = "L8";
4849
private static final String RTP_MEDIA_PCM_L16 = "L16";
4950
private static final String RTP_MEDIA_PCMA = "PCMA";
@@ -61,6 +62,7 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) {
6162
case RTP_MEDIA_H265:
6263
case RTP_MEDIA_MPEG4_VIDEO:
6364
case RTP_MEDIA_MPEG4_GENERIC:
65+
case RTP_MEDIA_OPUS:
6466
case RTP_MEDIA_PCM_L8:
6567
case RTP_MEDIA_PCM_L16:
6668
case RTP_MEDIA_PCMA:
@@ -90,6 +92,8 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
9092
return MimeTypes.AUDIO_AMR_WB;
9193
case RTP_MEDIA_MPEG4_GENERIC:
9294
return MimeTypes.AUDIO_AAC;
95+
case RTP_MEDIA_OPUS:
96+
return MimeTypes.AUDIO_OPUS;
9397
case RTP_MEDIA_PCM_L8:
9498
case RTP_MEDIA_PCM_L16:
9599
return MimeTypes.AUDIO_RAW;

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@
9999
*/
100100
private static final int DEFAULT_VP8_HEIGHT = 240;
101101

102+
/** RFC7587 Section 6.1 Sampling rate for OPUS is fixed at 48KHz. */
103+
private static final int OPUS_CLOCK_RATE = 48_000;
104+
102105
/**
103106
* Default width for VP9.
104107
*
@@ -199,6 +202,12 @@ public int hashCode() {
199202
!fmtpParameters.containsKey(PARAMETER_AMR_INTERLEAVING),
200203
"Interleaving mode is not currently supported.");
201204
break;
205+
case MimeTypes.AUDIO_OPUS:
206+
checkArgument(channelCount != C.INDEX_UNSET);
207+
// RFC7587 Section 6.1: the RTP timestamp is incremented with a 48000 Hz clock rate
208+
// for all modes of Opus and all sampling rates.
209+
checkArgument(clockRate == OPUS_CLOCK_RATE, "Invalid OPUS clock rate.");
210+
break;
202211
case MimeTypes.VIDEO_MP4V:
203212
checkArgument(!fmtpParameters.isEmpty());
204213
processMPEG4FmtpAttribute(formatBuilder, fmtpParameters);

library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/reader/DefaultRtpPayloadReaderFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) {
3737
case MimeTypes.AUDIO_AMR_NB:
3838
case MimeTypes.AUDIO_AMR_WB:
3939
return new RtpAmrReader(payloadFormat);
40+
case MimeTypes.AUDIO_OPUS:
41+
return new RtpOpusReader(payloadFormat);
4042
case MimeTypes.AUDIO_RAW:
4143
case MimeTypes.AUDIO_ALAW:
4244
case MimeTypes.AUDIO_MLAW:
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.exoplayer2.source.rtsp.reader;
17+
18+
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
19+
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
20+
21+
import com.google.android.exoplayer2.C;
22+
import com.google.android.exoplayer2.Format;
23+
import com.google.android.exoplayer2.audio.OpusUtil;
24+
import com.google.android.exoplayer2.extractor.ExtractorOutput;
25+
import com.google.android.exoplayer2.extractor.TrackOutput;
26+
import com.google.android.exoplayer2.source.rtsp.RtpPacket;
27+
import com.google.android.exoplayer2.source.rtsp.RtpPayloadFormat;
28+
import com.google.android.exoplayer2.util.Log;
29+
import com.google.android.exoplayer2.util.ParsableByteArray;
30+
import com.google.android.exoplayer2.util.Util;
31+
import java.util.List;
32+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
33+
34+
/**
35+
* Parses an OPUS byte stream carried on RTP packets and extracts individual samples. Refer to
36+
* RFC7845 for more details.
37+
*/
38+
/* package */ final class RtpOpusReader implements RtpPayloadReader {
39+
private static final String TAG = "RtpOpusReader";
40+
/* Opus uses a fixed 48KHz media clock RFC7845 Section 4. */
41+
private static final long MEDIA_CLOCK_FREQUENCY = 48_000;
42+
43+
private final RtpPayloadFormat payloadFormat;
44+
45+
private @MonotonicNonNull TrackOutput trackOutput;
46+
47+
/**
48+
* First received RTP timestamp. All RTP timestamps are dimension-less, the time base is defined
49+
* by {@link #MEDIA_CLOCK_FREQUENCY}.
50+
*/
51+
private long firstReceivedTimestamp;
52+
53+
private long startTimeOffsetUs;
54+
private int previousSequenceNumber;
55+
private boolean foundOpusIDHeader;
56+
private boolean foundOpusCommentHeader;
57+
58+
/** Creates an instance. */
59+
public RtpOpusReader(RtpPayloadFormat payloadFormat) {
60+
this.payloadFormat = payloadFormat;
61+
this.firstReceivedTimestamp = C.INDEX_UNSET;
62+
this.previousSequenceNumber = C.INDEX_UNSET;
63+
}
64+
65+
// RtpPayloadReader implementation.
66+
67+
@Override
68+
public void createTracks(ExtractorOutput extractorOutput, int trackId) {
69+
trackOutput = extractorOutput.track(trackId, C.TRACK_TYPE_AUDIO);
70+
trackOutput.format(payloadFormat.format);
71+
}
72+
73+
@Override
74+
public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {
75+
this.firstReceivedTimestamp = timestamp;
76+
}
77+
78+
@Override
79+
public void consume(
80+
ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) {
81+
checkStateNotNull(trackOutput);
82+
83+
/* RFC7845 Section 3.
84+
* +---------+ +----------------+ +--------------------+ +-----
85+
* |ID Header| | Comment Header | |Audio Data Packet 1 | | ...
86+
* +---------+ +----------------+ +--------------------+ +-----
87+
*/
88+
if (!foundOpusIDHeader) {
89+
validateOpusIdHeader(data);
90+
List<byte[]> initializationData = OpusUtil.buildInitializationData(data.getData());
91+
Format.Builder formatBuilder = payloadFormat.format.buildUpon();
92+
formatBuilder.setInitializationData(initializationData);
93+
trackOutput.format(formatBuilder.build());
94+
foundOpusIDHeader = true;
95+
} else if (!foundOpusCommentHeader) {
96+
// Comment Header RFC7845 Section 5.2.
97+
int sampleSize = data.limit();
98+
checkArgument(sampleSize >= 8, "Comment Header has insufficient data");
99+
String header = data.readString(8);
100+
checkArgument(header.equals("OpusTags"), "Comment Header should follow ID Header");
101+
foundOpusCommentHeader = true;
102+
} else {
103+
// Check that this packet is in the sequence of the previous packet.
104+
int expectedSequenceNumber = RtpPacket.getNextSequenceNumber(previousSequenceNumber);
105+
if (sequenceNumber != expectedSequenceNumber) {
106+
Log.w(
107+
TAG,
108+
Util.formatInvariant(
109+
"Received RTP packet with unexpected sequence number. Expected: %d; received: %d.",
110+
expectedSequenceNumber, sequenceNumber));
111+
}
112+
113+
// sending opus data.
114+
int size = data.bytesLeft();
115+
trackOutput.sampleData(data, size);
116+
long timeUs = toSampleTimeUs(startTimeOffsetUs, timestamp, firstReceivedTimestamp);
117+
trackOutput.sampleMetadata(
118+
timeUs, C.BUFFER_FLAG_KEY_FRAME, size, /* offset*/ 0, /* cryptoData*/ null);
119+
}
120+
previousSequenceNumber = sequenceNumber;
121+
}
122+
123+
@Override
124+
public void seek(long nextRtpTimestamp, long timeUs) {
125+
firstReceivedTimestamp = nextRtpTimestamp;
126+
startTimeOffsetUs = timeUs;
127+
}
128+
129+
// Internal methods.
130+
131+
/**
132+
* Validates the OPUS ID Header at {@code data}'s current position, throws {@link
133+
* IllegalArgumentException} if the header is invalid.
134+
*
135+
* <p>{@code data}'s position does not change after returning.
136+
*/
137+
private static void validateOpusIdHeader(ParsableByteArray data) {
138+
int currPosition = data.getPosition();
139+
int sampleSize = data.limit();
140+
checkArgument(sampleSize > 18, "ID Header has insufficient data");
141+
String header = data.readString(8);
142+
// Identification header RFC7845 Section 5.1.
143+
checkArgument(header.equals("OpusHead"), "ID Header missing");
144+
checkArgument(data.readUnsignedByte() == 1, "version number must always be 1");
145+
data.setPosition(currPosition);
146+
}
147+
148+
/** Returns the correct sample time from RTP timestamp, accounting for the OPUS sampling rate. */
149+
private static long toSampleTimeUs(
150+
long startTimeOffsetUs, long rtpTimestamp, long firstReceivedRtpTimestamp) {
151+
return startTimeOffsetUs
152+
+ Util.scaleLargeTimestamp(
153+
rtpTimestamp - firstReceivedRtpTimestamp,
154+
/* multiplier= */ C.MICROS_PER_SECOND,
155+
/* divisor= */ MEDIA_CLOCK_FREQUENCY);
156+
}
157+
}

0 commit comments

Comments
 (0)