Skip to content

Commit fd1eb4b

Browse files
Merge pull request #53 from ittiam-systems:rtp_opus
PiperOrigin-RevId: 453490088 (cherry picked from commit a2a4504)
1 parent 2ed7efb commit fd1eb4b

File tree

6 files changed

+374
-0
lines changed

6 files changed

+374
-0
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
([#10165](https://github.com/google/ExoPlayer/issues/10165)).
128128
* Add RTP reader for VP9
129129
([#47](https://github.com/androidx/media/pull/64)).
130+
* Add RTP reader for OPUS
131+
([#53](https://github.com/androidx/media/pull/53)).
130132
* Session:
131133
* Fix NPE in MediaControllerImplLegacy
132134
([#59](https://github.com/androidx/media/pull/59)).

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtpPayloadFormat.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public final class RtpPayloadFormat {
4646
private static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
4747
private static final String RTP_MEDIA_H264 = "H264";
4848
private static final String RTP_MEDIA_H265 = "H265";
49+
private static final String RTP_MEDIA_OPUS = "OPUS";
4950
private static final String RTP_MEDIA_PCM_L8 = "L8";
5051
private static final String RTP_MEDIA_PCM_L16 = "L16";
5152
private static final String RTP_MEDIA_PCMA = "PCMA";
@@ -63,6 +64,7 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) {
6364
case RTP_MEDIA_H265:
6465
case RTP_MEDIA_MPEG4_VIDEO:
6566
case RTP_MEDIA_MPEG4_GENERIC:
67+
case RTP_MEDIA_OPUS:
6668
case RTP_MEDIA_PCM_L8:
6769
case RTP_MEDIA_PCM_L16:
6870
case RTP_MEDIA_PCMA:
@@ -92,6 +94,8 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
9294
return MimeTypes.AUDIO_AMR_WB;
9395
case RTP_MEDIA_MPEG4_GENERIC:
9496
return MimeTypes.AUDIO_AAC;
97+
case RTP_MEDIA_OPUS:
98+
return MimeTypes.AUDIO_OPUS;
9599
case RTP_MEDIA_PCM_L8:
96100
case RTP_MEDIA_PCM_L16:
97101
return MimeTypes.AUDIO_RAW;

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java

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

104+
/** RFC7587 Section 6.1 Sampling rate for OPUS is fixed at 48KHz. */
105+
private static final int OPUS_CLOCK_RATE = 48_000;
106+
104107
/**
105108
* Default width for VP9.
106109
*
@@ -201,6 +204,12 @@ public int hashCode() {
201204
!fmtpParameters.containsKey(PARAMETER_AMR_INTERLEAVING),
202205
"Interleaving mode is not currently supported.");
203206
break;
207+
case MimeTypes.AUDIO_OPUS:
208+
checkArgument(channelCount != C.INDEX_UNSET);
209+
// RFC7587 Section 6.1: the RTP timestamp is incremented with a 48000 Hz clock rate
210+
// for all modes of Opus and all sampling rates.
211+
checkArgument(clockRate == OPUS_CLOCK_RATE, "Invalid OPUS clock rate.");
212+
break;
204213
case MimeTypes.VIDEO_MP4V:
205214
checkArgument(!fmtpParameters.isEmpty());
206215
processMPEG4FmtpAttribute(formatBuilder, fmtpParameters);

libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/reader/DefaultRtpPayloadReaderFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) {
3939
case MimeTypes.AUDIO_AMR_NB:
4040
case MimeTypes.AUDIO_AMR_WB:
4141
return new RtpAmrReader(payloadFormat);
42+
case MimeTypes.AUDIO_OPUS:
43+
return new RtpOpusReader(payloadFormat);
4244
case MimeTypes.AUDIO_RAW:
4345
case MimeTypes.AUDIO_ALAW:
4446
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 androidx.media3.exoplayer.rtsp.reader;
17+
18+
import static androidx.media3.common.util.Assertions.checkArgument;
19+
import static androidx.media3.common.util.Assertions.checkStateNotNull;
20+
21+
import androidx.media3.common.C;
22+
import androidx.media3.common.Format;
23+
import androidx.media3.common.util.Log;
24+
import androidx.media3.common.util.ParsableByteArray;
25+
import androidx.media3.common.util.Util;
26+
import androidx.media3.exoplayer.rtsp.RtpPacket;
27+
import androidx.media3.exoplayer.rtsp.RtpPayloadFormat;
28+
import androidx.media3.extractor.ExtractorOutput;
29+
import androidx.media3.extractor.OpusUtil;
30+
import androidx.media3.extractor.TrackOutput;
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)