mirror of
https://github.com/androidx/media.git
synced 2025-05-06 23:20:42 +08:00
Introduce RtpDataChannel interface.
#minor-release PiperOrigin-RevId: 371326814
This commit is contained in:
parent
3ddf73fdcf
commit
a64c6e80ed
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user