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.TrackGroupArray;
import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener; 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.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.source.rtsp.rtp.RtpDataLoadable;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; 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;
import com.google.android.exoplayer2.upstream.Loader.Loadable; import com.google.android.exoplayer2.upstream.Loader.Loadable;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -72,6 +72,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
private final InternalListener internalListener; private final InternalListener internalListener;
private final RtspClient rtspClient; private final RtspClient rtspClient;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private final List<RtspLoaderWrapper> rtspLoaderWrappers; private final List<RtspLoaderWrapper> rtspLoaderWrappers;
private final List<RtpLoadInfo> selectedLoadInfos; 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 allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param rtspTracks A list of tracks in an RTSP playback session. * @param rtspTracks A list of tracks in an RTSP playback session.
* @param rtspClient The {@link RtspClient} for the current RTSP playback. * @param rtspClient The {@link RtspClient} for the current RTSP playback.
* @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}.
*/ */
public RtspMediaPeriod( public RtspMediaPeriod(
Allocator allocator, List<RtspMediaTrack> rtspTracks, RtspClient rtspClient) { Allocator allocator,
List<RtspMediaTrack> rtspTracks,
RtspClient rtspClient,
RtpDataChannel.Factory rtpDataChannelFactory) {
this.allocator = allocator; this.allocator = allocator;
handler = Util.createHandlerForCurrentLooper(); handler = Util.createHandlerForCurrentLooper();
@ -103,6 +108,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
rtspLoaderWrappers = new ArrayList<>(rtspTracks.size()); rtspLoaderWrappers = new ArrayList<>(rtspTracks.size());
this.rtspClient = rtspClient; this.rtspClient = rtspClient;
this.rtspClient.setPlaybackEventListener(internalListener); this.rtspClient.setPlaybackEventListener(internalListener);
this.rtpDataChannelFactory = rtpDataChannelFactory;
for (int i = 0; i < rtspTracks.size(); i++) { for (int i = 0; i < rtspTracks.size(); i++) {
RtspMediaTrack rtspMediaTrack = rtspTracks.get(i); RtspMediaTrack rtspMediaTrack = rtspTracks.get(i);
@ -506,7 +512,7 @@ public final class RtspMediaPeriod implements MediaPeriod {
playbackException = error; 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) { private void handleSocketTimeout(RtpDataLoadable loadable) {
// TODO(b/172331505) Allow for retry when loading is not ending. // TODO(b/172331505) Allow for retry when loading is not ending.
if (getBufferedPositionUs() == Long.MIN_VALUE) { if (getBufferedPositionUs() == Long.MIN_VALUE) {
@ -648,7 +654,8 @@ public final class RtspMediaPeriod implements MediaPeriod {
trackId, trackId,
mediaTrack, mediaTrack,
/* eventListener= */ transportEventListener, /* 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.MediaSourceFactory;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener; 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.Allocator;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@ -131,6 +132,7 @@ public final class RtspMediaSource extends BaseMediaSource {
} }
private final MediaItem mediaItem; private final MediaItem mediaItem;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private @MonotonicNonNull RtspClient rtspClient; private @MonotonicNonNull RtspClient rtspClient;
@Nullable private ImmutableList<RtspMediaTrack> rtspMediaTracks; @Nullable private ImmutableList<RtspMediaTrack> rtspMediaTracks;
@ -138,6 +140,7 @@ public final class RtspMediaSource extends BaseMediaSource {
private RtspMediaSource(MediaItem mediaItem) { private RtspMediaSource(MediaItem mediaItem) {
this.mediaItem = mediaItem; this.mediaItem = mediaItem;
rtpDataChannelFactory = RtpDataChannel.DEFAULT_FACTORY;
} }
@Override @Override
@ -174,7 +177,8 @@ public final class RtspMediaSource extends BaseMediaSource {
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { 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 @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.source.rtsp.RtspMediaTrack;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.UdpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 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 * A {@link Loader.Loadable} that sets up a sockets listening to incoming RTP traffic, carried by
* UDP packets. * UDP packets.
* *
* <p>Uses a {@link UdpDataSource} to listen on incoming packets. The local UDP port is selected by * <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 UdpDataSource} for RTCP on the RTP UDP port * 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 * 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. * to open.
* *
* <p>Received RTP packets' payloads will be extracted by an {@link RtpExtractor}, and will be * <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 EventListener eventListener;
private final ExtractorOutput output; private final ExtractorOutput output;
private final Handler playbackThreadHandler; private final Handler playbackThreadHandler;
private final RtpDataChannel.Factory rtpDataChannelFactory;
private @MonotonicNonNull RtpExtractor extractor; private @MonotonicNonNull RtpExtractor extractor;
@ -92,17 +92,20 @@ public final class RtpDataLoadable implements Loader.Loadable {
* @param rtspMediaTrack The {@link RtspMediaTrack} to load. * @param rtspMediaTrack The {@link RtspMediaTrack} to load.
* @param eventListener The {@link EventListener}. * @param eventListener The {@link EventListener}.
* @param output A {@link ExtractorOutput} instance to which the received and extracted data will * @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( public RtpDataLoadable(
int trackId, int trackId,
RtspMediaTrack rtspMediaTrack, RtspMediaTrack rtspMediaTrack,
EventListener eventListener, EventListener eventListener,
ExtractorOutput output) { ExtractorOutput output,
RtpDataChannel.Factory rtpDataChannelFactory) {
this.trackId = trackId; this.trackId = trackId;
this.rtspMediaTrack = rtspMediaTrack; this.rtspMediaTrack = rtspMediaTrack;
this.eventListener = eventListener; this.eventListener = eventListener;
this.output = output; this.output = output;
this.playbackThreadHandler = Util.createHandlerForCurrentLooper(); this.playbackThreadHandler = Util.createHandlerForCurrentLooper();
this.rtpDataChannelFactory = rtpDataChannelFactory;
pendingSeekPositionUs = C.TIME_UNSET; pendingSeekPositionUs = C.TIME_UNSET;
} }
@ -139,30 +142,30 @@ public final class RtpDataLoadable implements Loader.Loadable {
@Override @Override
public void load() throws IOException { public void load() throws IOException {
@Nullable UdpDataSource firstDataSource = null; @Nullable RtpDataChannel firstDataChannel = null;
@Nullable UdpDataSource secondDataSource = null; @Nullable RtpDataChannel secondDataChannel = null;
try { 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 // 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 and the corresponding RTCP stream SHOULD use the next higher (odd) destination
// port number". Some RTSP servers are strict about this rule. // port number". Some RTSP servers are strict about this rule. We open a data channel first,
// We open a data source first, and depending its port number, open the next data source with // and depending its port number, open the next data channel with a port number that is either
// a port number that is either the higher or the lower. // the higher or the lower.
firstDataSource = new UdpDataSource(); firstDataChannel = rtpDataChannelFactory.createDataChannel();
firstDataSource.open(new DataSpec(Uri.parse(RTP_BIND_ADDRESS))); firstDataChannel.open(new DataSpec(Uri.parse(RTP_BIND_ADDRESS)));
int firstPort = firstDataSource.getLocalPort(); int firstPort = firstDataChannel.getLocalPort();
boolean isFirstPortNumberEven = (firstPort % 2 == 0); boolean isFirstPortNumberEven = (firstPort % 2 == 0);
int secondPort = isFirstPortNumberEven ? firstPort + 1 : firstPort - 1; int secondPort = isFirstPortNumberEven ? firstPort + 1 : firstPort - 1;
// RTCP always uses the immediate next port. // RTCP always uses the immediate next port.
secondDataSource = new UdpDataSource(); secondDataChannel = rtpDataChannelFactory.createDataChannel();
secondDataSource.open(new DataSpec(Uri.parse(RTP_ANY_INCOMING_IPV4 + ":" + secondPort))); secondDataChannel.open(new DataSpec(Uri.parse(RTP_ANY_INCOMING_IPV4 + ":" + secondPort)));
// RTP data port is always the lower and even-numbered port. // RTP data port is always the lower and even-numbered port.
UdpDataSource dataSource = isFirstPortNumberEven ? firstDataSource : secondDataSource; RtpDataChannel dataChannel = isFirstPortNumberEven ? firstDataChannel : secondDataChannel;
int dataPort = dataSource.getLocalPort(); int dataPort = dataChannel.getLocalPort();
int rtcpPort = dataPort + 1; int rtcpPort = dataPort + 1;
String transport = Util.formatInvariant(DEFAULT_TRANSPORT_FORMAT, dataPort, rtcpPort); String transport = Util.formatInvariant(DEFAULT_TRANSPORT_FORMAT, dataPort, rtcpPort);
playbackThreadHandler.post(() -> eventListener.onTransportReady(transport)); playbackThreadHandler.post(() -> eventListener.onTransportReady(transport));
@ -170,7 +173,7 @@ public final class RtpDataLoadable implements Loader.Loadable {
// Sets up the extractor. // Sets up the extractor.
ExtractorInput extractorInput = ExtractorInput extractorInput =
new DefaultExtractorInput( 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 = new RtpExtractor(rtspMediaTrack.payloadFormat, trackId);
extractor.init(output); extractor.init(output);
@ -183,8 +186,8 @@ public final class RtpDataLoadable implements Loader.Loadable {
extractor.read(extractorInput, /* seekPosition= */ new PositionHolder()); extractor.read(extractorInput, /* seekPosition= */ new PositionHolder());
} }
} finally { } finally {
closeQuietly(firstDataSource); closeQuietly(firstDataChannel);
closeQuietly(secondDataSource); 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 androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.MediaPeriod; 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.MediaDescription;
import com.google.android.exoplayer2.source.rtsp.sdp.SessionDescription; import com.google.android.exoplayer2.source.rtsp.sdp.SessionDescription;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
@ -68,7 +69,8 @@ public class RtspMediaPeriodTest {
.addAttribute(SessionDescription.ATTR_CONTROL, "track1") .addAttribute(SessionDescription.ATTR_CONTROL, "track1")
.build(), .build(),
Uri.parse("rtsp://localhost/test"))), Uri.parse("rtsp://localhost/test"))),
PLACEHOLDER_RTSP_CLIENT); PLACEHOLDER_RTSP_CLIENT,
RtpDataChannel.DEFAULT_FACTORY);
AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false); AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false);
rtspMediaPeriod.prepare( rtspMediaPeriod.prepare(
@ -95,7 +97,8 @@ public class RtspMediaPeriodTest {
new RtspMediaPeriod( new RtspMediaPeriod(
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE), new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
ImmutableList.of(), ImmutableList.of(),
PLACEHOLDER_RTSP_CLIENT); PLACEHOLDER_RTSP_CLIENT,
RtpDataChannel.DEFAULT_FACTORY);
assertThat(rtspMediaPeriod.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE); assertThat(rtspMediaPeriod.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE);
} }