Introduce RtpDataChannel interface.

#minor-release

PiperOrigin-RevId: 371326814
This commit is contained in:
claincly 2021-04-30 15:06:35 +01:00 committed by bachinger
parent 3ddf73fdcf
commit a64c6e80ed
6 changed files with 144 additions and 28 deletions

View File

@ -43,11 +43,11 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener;
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException;
import com.google.android.exoplayer2.source.rtsp.rtp.RtpDataChannel;
import com.google.android.exoplayer2.source.rtsp.rtp.RtpDataLoadable;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.Loadable;
import com.google.android.exoplayer2.util.Util;
@ -72,6 +72,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
private final InternalListener internalListener;
private final RtspClient rtspClient;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private final List<RtspLoaderWrapper> rtspLoaderWrappers;
private final List<RtpLoadInfo> selectedLoadInfos;
@ -93,9 +94,13 @@ public final class RtspMediaPeriod implements MediaPeriod {
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param rtspTracks A list of tracks in an RTSP playback session.
* @param rtspClient The {@link RtspClient} for the current RTSP playback.
* @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}.
*/
public RtspMediaPeriod(
Allocator allocator, List<RtspMediaTrack> rtspTracks, RtspClient rtspClient) {
Allocator allocator,
List<RtspMediaTrack> rtspTracks,
RtspClient rtspClient,
RtpDataChannel.Factory rtpDataChannelFactory) {
this.allocator = allocator;
handler = Util.createHandlerForCurrentLooper();
@ -103,6 +108,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
rtspLoaderWrappers = new ArrayList<>(rtspTracks.size());
this.rtspClient = rtspClient;
this.rtspClient.setPlaybackEventListener(internalListener);
this.rtpDataChannelFactory = rtpDataChannelFactory;
for (int i = 0; i < rtspTracks.size(); i++) {
RtspMediaTrack rtspMediaTrack = rtspTracks.get(i);
@ -506,7 +512,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
playbackException = error;
}
/** Handles the {@link Loadable} whose {@link DataSource} timed out. */
/** Handles the {@link Loadable} whose {@link RtpDataChannel} timed out. */
private void handleSocketTimeout(RtpDataLoadable loadable) {
// TODO(b/172331505) Allow for retry when loading is not ending.
if (getBufferedPositionUs() == Long.MIN_VALUE) {
@ -648,7 +654,8 @@ public final class RtspMediaPeriod implements MediaPeriod {
trackId,
mediaTrack,
/* eventListener= */ transportEventListener,
/* output= */ internalListener);
/* output= */ internalListener,
rtpDataChannelFactory);
}
/**

View File

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener;
import com.google.android.exoplayer2.source.rtsp.rtp.RtpDataChannel;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@ -131,6 +132,7 @@ public final class RtspMediaSource extends BaseMediaSource {
}
private final MediaItem mediaItem;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private @MonotonicNonNull RtspClient rtspClient;
@Nullable private ImmutableList<RtspMediaTrack> rtspMediaTracks;
@ -138,6 +140,7 @@ public final class RtspMediaSource extends BaseMediaSource {
private RtspMediaSource(MediaItem mediaItem) {
this.mediaItem = mediaItem;
rtpDataChannelFactory = RtpDataChannel.DEFAULT_FACTORY;
}
@Override
@ -174,7 +177,8 @@ public final class RtspMediaSource extends BaseMediaSource {
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return new RtspMediaPeriod(allocator, checkNotNull(rtspMediaTracks), checkNotNull(rtspClient));
return new RtspMediaPeriod(
allocator, checkNotNull(rtspMediaTracks), checkNotNull(rtspClient), rtpDataChannelFactory);
}
@Override

View File

@ -0,0 +1,35 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.source.rtsp.rtp;
import com.google.android.exoplayer2.upstream.DataSource;
/** An RTP {@link DataSource}. */
public interface RtpDataChannel extends DataSource {
/** Creates {@link RtpDataChannel} for RTSP streams. */
interface Factory {
/** Creates a new {@link RtpDataChannel} instance. */
RtpDataChannel createDataChannel();
}
/** The default {@link Factory} that returns a new {@link UdpDataSourceRtpDataChannel}. */
Factory DEFAULT_FACTORY = UdpDataSourceRtpDataChannel::new;
/** Returns the local port used in the underlying transport channel. */
int getLocalPort();
}

View File

@ -30,7 +30,6 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.source.rtsp.RtspMediaTrack;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.UdpDataSource;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -39,10 +38,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* A {@link Loader.Loadable} that sets up a sockets listening to incoming RTP traffic, carried by
* UDP packets.
*
* <p>Uses a {@link UdpDataSource} to listen on incoming packets. The local UDP port is selected by
* the runtime on opening; it also opens another {@link UdpDataSource} for RTCP on the RTP UDP port
* <p>Uses a {@link RtpDataChannel} to listen on incoming packets. The local UDP port is selected by
* the runtime on opening; it also opens another {@link RtpDataChannel} for RTCP on the RTP UDP port
* number plus one one. Pass a listener via constructor to receive a callback when the local port is
* opened. {@link #load} will throw an {@link IOException} if either of the two data sources fails
* opened. {@link #load} will throw an {@link IOException} if either of the two data channels fails
* to open.
*
* <p>Received RTP packets' payloads will be extracted by an {@link RtpExtractor}, and will be
@ -76,6 +75,7 @@ public final class RtpDataLoadable implements Loader.Loadable {
private final EventListener eventListener;
private final ExtractorOutput output;
private final Handler playbackThreadHandler;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private @MonotonicNonNull RtpExtractor extractor;
@ -92,17 +92,20 @@ public final class RtpDataLoadable implements Loader.Loadable {
* @param rtspMediaTrack The {@link RtspMediaTrack} to load.
* @param eventListener The {@link EventListener}.
* @param output A {@link ExtractorOutput} instance to which the received and extracted data will
* @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}.
*/
public RtpDataLoadable(
int trackId,
RtspMediaTrack rtspMediaTrack,
EventListener eventListener,
ExtractorOutput output) {
ExtractorOutput output,
RtpDataChannel.Factory rtpDataChannelFactory) {
this.trackId = trackId;
this.rtspMediaTrack = rtspMediaTrack;
this.eventListener = eventListener;
this.output = output;
this.playbackThreadHandler = Util.createHandlerForCurrentLooper();
this.rtpDataChannelFactory = rtpDataChannelFactory;
pendingSeekPositionUs = C.TIME_UNSET;
}
@ -139,30 +142,30 @@ public final class RtpDataLoadable implements Loader.Loadable {
@Override
public void load() throws IOException {
@Nullable UdpDataSource firstDataSource = null;
@Nullable UdpDataSource secondDataSource = null;
@Nullable RtpDataChannel firstDataChannel = null;
@Nullable RtpDataChannel secondDataChannel = null;
try {
// Open and set up the data sources.
// Open and set up the data channel.
// From RFC3550 Section 11: "For UDP and similar protocols, RTP SHOULD use an even destination
// port number and the corresponding RTCP stream SHOULD use the next higher (odd) destination
// port number". Some RTSP servers are strict about this rule.
// We open a data source first, and depending its port number, open the next data source with
// a port number that is either the higher or the lower.
firstDataSource = new UdpDataSource();
firstDataSource.open(new DataSpec(Uri.parse(RTP_BIND_ADDRESS)));
// port number". Some RTSP servers are strict about this rule. We open a data channel first,
// and depending its port number, open the next data channel with a port number that is either
// the higher or the lower.
firstDataChannel = rtpDataChannelFactory.createDataChannel();
firstDataChannel.open(new DataSpec(Uri.parse(RTP_BIND_ADDRESS)));
int firstPort = firstDataSource.getLocalPort();
int firstPort = firstDataChannel.getLocalPort();
boolean isFirstPortNumberEven = (firstPort % 2 == 0);
int secondPort = isFirstPortNumberEven ? firstPort + 1 : firstPort - 1;
// RTCP always uses the immediate next port.
secondDataSource = new UdpDataSource();
secondDataSource.open(new DataSpec(Uri.parse(RTP_ANY_INCOMING_IPV4 + ":" + secondPort)));
secondDataChannel = rtpDataChannelFactory.createDataChannel();
secondDataChannel.open(new DataSpec(Uri.parse(RTP_ANY_INCOMING_IPV4 + ":" + secondPort)));
// RTP data port is always the lower and even-numbered port.
UdpDataSource dataSource = isFirstPortNumberEven ? firstDataSource : secondDataSource;
int dataPort = dataSource.getLocalPort();
RtpDataChannel dataChannel = isFirstPortNumberEven ? firstDataChannel : secondDataChannel;
int dataPort = dataChannel.getLocalPort();
int rtcpPort = dataPort + 1;
String transport = Util.formatInvariant(DEFAULT_TRANSPORT_FORMAT, dataPort, rtcpPort);
playbackThreadHandler.post(() -> eventListener.onTransportReady(transport));
@ -170,7 +173,7 @@ public final class RtpDataLoadable implements Loader.Loadable {
// Sets up the extractor.
ExtractorInput extractorInput =
new DefaultExtractorInput(
checkNotNull(dataSource), /* position= */ 0, /* length= */ C.LENGTH_UNSET);
checkNotNull(dataChannel), /* position= */ 0, /* length= */ C.LENGTH_UNSET);
extractor = new RtpExtractor(rtspMediaTrack.payloadFormat, trackId);
extractor.init(output);
@ -183,8 +186,8 @@ public final class RtpDataLoadable implements Loader.Loadable {
extractor.read(extractorInput, /* seekPosition= */ new PositionHolder());
}
} finally {
closeQuietly(firstDataSource);
closeQuietly(secondDataSource);
closeQuietly(firstDataChannel);
closeQuietly(secondDataChannel);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2021 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 com.google.android.exoplayer2.source.rtsp.rtp;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.UdpDataSource;
import java.io.IOException;
/** An {@link RtpDataChannel} for UDP transport. */
public final class UdpDataSourceRtpDataChannel implements RtpDataChannel {
private final UdpDataSource dataSource;
/** Creates a new instance. */
public UdpDataSourceRtpDataChannel() {
dataSource = new UdpDataSource();
}
@Override
public int getLocalPort() {
return dataSource.getLocalPort();
}
@Override
public void addTransferListener(TransferListener transferListener) {
dataSource.addTransferListener(transferListener);
}
@Override
public long open(DataSpec dataSpec) throws IOException {
return dataSource.open(dataSpec);
}
@Nullable
@Override
public Uri getUri() {
return dataSource.getUri();
}
@Override
public void close() {
dataSource.close();
}
@Override
public int read(byte[] target, int offset, int length) throws IOException {
return dataSource.read(target, offset, length);
}
}

View File

@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.rtsp.rtp.RtpDataChannel;
import com.google.android.exoplayer2.source.rtsp.sdp.MediaDescription;
import com.google.android.exoplayer2.source.rtsp.sdp.SessionDescription;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
@ -68,7 +69,8 @@ public class RtspMediaPeriodTest {
.addAttribute(SessionDescription.ATTR_CONTROL, "track1")
.build(),
Uri.parse("rtsp://localhost/test"))),
PLACEHOLDER_RTSP_CLIENT);
PLACEHOLDER_RTSP_CLIENT,
RtpDataChannel.DEFAULT_FACTORY);
AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false);
rtspMediaPeriod.prepare(
@ -95,7 +97,8 @@ public class RtspMediaPeriodTest {
new RtspMediaPeriod(
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
ImmutableList.of(),
PLACEHOLDER_RTSP_CLIENT);
PLACEHOLDER_RTSP_CLIENT,
RtpDataChannel.DEFAULT_FACTORY);
assertThat(rtspMediaPeriod.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE);
}