UdpDataSourceRtpDataChannelFactory.java

/*
 * 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 androidx.media3.exoplayer.rtsp;

import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSourceUtil;
import java.io.IOException;

/** Factory for {@link UdpDataSourceRtpDataChannel}. */
@UnstableApi
/* package */ final class UdpDataSourceRtpDataChannelFactory implements RtpDataChannel.Factory {

  private final long socketTimeoutMs;

  /**
   * Creates a new instance.
   *
   * @param socketTimeoutMs A positive number of milliseconds to wait before lack of received RTP
   *     packets is treated as the end of input.
   */
  public UdpDataSourceRtpDataChannelFactory(long socketTimeoutMs) {
    this.socketTimeoutMs = socketTimeoutMs;
  }

  @Override
  public RtpDataChannel createAndOpenDataChannel(int trackId) throws IOException {
    UdpDataSourceRtpDataChannel firstChannel = new UdpDataSourceRtpDataChannel(socketTimeoutMs);
    UdpDataSourceRtpDataChannel secondChannel = new UdpDataSourceRtpDataChannel(socketTimeoutMs);

    try {
      // 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 channel first,
      // and depending its port number, open the next data channel with a port number that is either
      // the higher or the lower.

      // Using port zero will cause the system to generate a port.
      firstChannel.open(RtpUtils.getIncomingRtpDataSpec(/* portNumber= */ 0));
      int firstPort = firstChannel.getLocalPort();
      boolean isFirstPortEven = firstPort % 2 == 0;
      int portToOpen = isFirstPortEven ? firstPort + 1 : firstPort - 1;
      secondChannel.open(RtpUtils.getIncomingRtpDataSpec(/* portNumber= */ portToOpen));

      if (isFirstPortEven) {
        firstChannel.setRtcpChannel(secondChannel);
        return firstChannel;
      } else {
        secondChannel.setRtcpChannel(firstChannel);
        return secondChannel;
      }
    } catch (IOException e) {
      DataSourceUtil.closeQuietly(firstChannel);
      DataSourceUtil.closeQuietly(secondChannel);
      throw e;
    }
  }

  @Override
  public RtpDataChannel.Factory createFallbackDataChannelFactory() {
    return new TransferRtpDataChannelFactory(/* timeoutMs= */ socketTimeoutMs);
  }
}