diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle
index a9fa27ad586..bd0464fd7e0 100644
--- a/demos/cast/build.gradle
+++ b/demos/cast/build.gradle
@@ -15,7 +15,7 @@ apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
- compileSdkVersion project.ext.compileSdkVersion
+ compileSdkVersion 25
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
@@ -42,10 +42,4 @@ android {
}
dependencies {
- compile project(modulePrefix + 'library-core')
- compile project(modulePrefix + 'library-dash')
- compile project(modulePrefix + 'library-hls')
- compile project(modulePrefix + 'library-smoothstreaming')
- compile project(modulePrefix + 'library-ui')
- compile project(modulePrefix + 'extension-cast')
}
diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml
index 4f90cef623d..2299fa3c0d0 100644
--- a/demos/main/src/main/AndroidManifest.xml
+++ b/demos/main/src/main/AndroidManifest.xml
@@ -21,6 +21,9 @@
+
+
+
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
index b5db4c018d4..e000e3f68b0 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java
@@ -16,12 +16,18 @@
package com.google.android.exoplayer2.demo;
import android.app.Application;
+import android.util.Log;
+
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.RtpDataSource;
+import com.google.android.exoplayer2.upstream.RtpDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.util.rtp.AluRtpDistributionFeedback;
+import com.google.android.exoplayer2.util.rtp.RtpDistributionFeedback;
/**
* Placeholder application to facilitate overriding Application methods for debugging and testing.
@@ -30,6 +36,50 @@ public class DemoApplication extends Application {
protected String userAgent;
+ protected RtpDistributionFeedback.RtpFeedbackEventListener eventListener =
+ new RtpDistributionFeedback.RtpFeedbackEventListener () {
+
+ @Override
+ public void onRtpFeedbackEvent(
+ RtpDistributionFeedback.RtpFeedbackEvent event) {
+
+ if (event instanceof AluRtpDistributionFeedback.AluRtpFeedbackConfigDiscoveryStarted) {
+
+ Log.v("AluRtpFeedback", "ALU RTP Feedback Configuration Discovery Started");
+ }
+ else if (event instanceof AluRtpDistributionFeedback.AluRtpFeedbackConfigDiscoveryEnded) {
+
+ Log.v("AluRtpFeedback", "ALU RTP Feedback Configuration Discovery Ended");
+ }
+ else if (event instanceof AluRtpDistributionFeedback.AluDefaultRtpBurstServerEvent) {
+
+ AluRtpDistributionFeedback.AluDefaultRtpBurstServerEvent serverEvent =
+ (AluRtpDistributionFeedback.AluDefaultRtpBurstServerEvent) event;
+
+ Log.v("AluRtpFeedback", "default burst=[" + serverEvent.getBurstServer() + "]");
+ }
+ else if (event instanceof AluRtpDistributionFeedback.AluDefaultRtpRetransmissionServerEvent) {
+
+ AluRtpDistributionFeedback.AluDefaultRtpRetransmissionServerEvent serverEvent =
+ (AluRtpDistributionFeedback.AluDefaultRtpRetransmissionServerEvent) event;
+
+ Log.v("AluRtpFeedback", "default retransmission=[" + serverEvent.getRetransmissionServer() + "]");
+ }
+ else if (event instanceof AluRtpDistributionFeedback.AluRtpMulticastGroupInfoEvent) {
+
+ AluRtpDistributionFeedback.AluRtpMulticastGroupInfoEvent groupInfo =
+ (AluRtpDistributionFeedback.AluRtpMulticastGroupInfoEvent) event;
+
+ Log.v("AluRtpFeedback", "mcast=[" + groupInfo.getMulticastGroup() + "], " +
+ "first burst=[" + groupInfo.getFirstBurstServer() + "], " +
+ "second burst=[" + groupInfo.getSecondBurstServer() + "], " +
+ "first retrans=[" + groupInfo.getFirstRetransmissionServer() + "], " +
+ "second retrans=[" + groupInfo.getSecondRetransmissionServer() + "]");
+ }
+ }
+ };
+
+
@Override
public void onCreate() {
super.onCreate();
@@ -45,6 +95,49 @@ public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter b
return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
}
+
+ public RtpDataSource.Factory buildRtpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter,
+ String vendor, boolean feedback_events,
+ String burst_uri,
+ String retransmission_uri) {
+
+ RtpDataSourceFactory dataSourceFactory = new RtpDataSourceFactory(bandwidthMeter);
+
+ if ((vendor != null) && ("alu".equalsIgnoreCase(vendor))) {
+
+ dataSourceFactory.setFeedbackProperty(RtpDistributionFeedback.Properties.FB_VENDOR,
+ RtpDistributionFeedback.Providers.ALU);
+
+ if (feedback_events) {
+ dataSourceFactory.setFeedbackProperty(RtpDistributionFeedback.Properties.FB_EVENTS_CALLBACK,
+ eventListener);
+ }
+
+ int flagsScheme = 0;
+
+ if (burst_uri != null) {
+ dataSourceFactory.setFeedbackProperty(RtpDistributionFeedback.Properties.FB_RAMS_URI,
+ burst_uri);
+
+ flagsScheme |= RtpDistributionFeedback.Schemes.FB_RAMS;
+ }
+
+ if (retransmission_uri != null) {
+ dataSourceFactory.setFeedbackProperty(
+ RtpDistributionFeedback.Properties.FB_CONGESTION_CONTROL_URI,
+ retransmission_uri);
+
+ flagsScheme |= RtpDistributionFeedback.Schemes.FB_CONGESTION_CONTROL;
+ }
+
+ dataSourceFactory.setFeedbackProperty(RtpDistributionFeedback.Properties.FB_SCHEME,
+ flagsScheme);
+
+ }
+
+ return dataSourceFactory;
+ }
+
public boolean useExtensionRenderers() {
return BuildConfig.FLAVOR.equals("withExtensions");
}
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
index b2750a93bbf..dbbb0236759 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
@@ -72,7 +72,10 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.RtpDataSource;
import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.util.rtp.RtpExtractorsFactory;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.CookieHandler;
@@ -93,6 +96,10 @@ public class PlayerActivity extends Activity implements OnClickListener, EventLi
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
public static final String EXTENSION_EXTRA = "extension";
+ public static final String VENDOR_EXTRA = "vendor";
+ public static final String FEEDBACK_EVENTS_EXTRA = "feedback_events";
+ public static final String BURST_URI_EXTRA = "burst_uri";
+ public static final String RETRANSMISSION_URI_EXTRA = "retransmission_uri";
public static final String ACTION_VIEW_LIST =
"com.google.android.exoplayer.demo.action.VIEW_LIST";
@@ -329,8 +336,13 @@ private void initializePlayer() {
}
MediaSource[] mediaSources = new MediaSource[uris.length];
for (int i = 0; i < uris.length; i++) {
- mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
+ mediaSources[i] = buildMediaSource(uris[i], extensions[i],
+ intent.getStringExtra(VENDOR_EXTRA),
+ intent.getBooleanExtra(FEEDBACK_EVENTS_EXTRA, false),
+ intent.getStringExtra(BURST_URI_EXTRA),
+ intent.getStringExtra(RETRANSMISSION_URI_EXTRA));
}
+
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
@@ -357,7 +369,9 @@ private void initializePlayer() {
updateButtonVisibilities();
}
- private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
+ private MediaSource buildMediaSource(Uri uri, String overrideExtension, String vendor,
+ boolean feedback_events, String burst_uri,
+ String retransmission_uri) {
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
: Util.inferContentType("." + overrideExtension);
switch (type) {
@@ -370,6 +384,13 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
case C.TYPE_OTHER:
+ if (uri.getScheme().equals("rtp")) {
+ return new ExtractorMediaSource(uri, buildRtpDataSourceFactory(true, vendor,
+ feedback_events, burst_uri,retransmission_uri), new RtpExtractorsFactory(),
+ mainHandler, eventLogger);
+
+ }
+
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, eventLogger);
default: {
@@ -440,6 +461,22 @@ private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMe
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
}
+ /**
+ + * Returns a new RtpDataSource factory.
+ + *
+ + * @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new
+ + * DataSource factory.
+ + * @param vendor RTP distribution and feedback vendor architecture identifier.
+ + * @return A new RtpDataSource factory.
+ + */
+ private RtpDataSource.Factory buildRtpDataSourceFactory(boolean useBandwidthMeter, String vendor,
+ boolean feedback_events, String burst_uri,
+ String retransmission_uri) {
+ return ((DemoApplication) getApplication())
+ .buildRtpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null, vendor,
+ feedback_events, burst_uri, retransmission_uri);
+ }
+
/**
* Returns an ads media source, reusing the ads loader if one exists.
*
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
index c0edb1d1b88..9ec3a63f089 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
@@ -178,6 +178,10 @@ private void readSampleGroup(JsonReader reader, List groups) throws
private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
String sampleName = null;
String uri = null;
+ String vendor = null;
+ boolean feedback_events = false;
+ String burst_uri = null;
+ String retransmission_uri = null;
String extension = null;
UUID drmUuid = null;
String drmLicenseUrl = null;
@@ -196,6 +200,18 @@ private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOExc
case "uri":
uri = reader.nextString();
break;
+ case "vendor":
+ vendor = reader.nextString();
+ break;
+ case "feedback_events":
+ feedback_events = "yes".equalsIgnoreCase(reader.nextString()) ? true : false;
+ break;
+ case "burst_uri":
+ burst_uri = reader.nextString();
+ break;
+ case "retransmission_uri":
+ retransmission_uri = reader.nextString();
+ break;
case "extension":
extension = reader.nextString();
break;
@@ -250,7 +266,8 @@ private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOExc
preferExtensionDecoders, playlistSamplesArray);
} else {
return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
- preferExtensionDecoders, uri, extension, adTagUri);
+ preferExtensionDecoders, uri, vendor, feedback_events, burst_uri, retransmission_uri,
+ extension, adTagUri);
}
}
@@ -405,14 +422,23 @@ public Intent buildIntent(Context context) {
private static final class UriSample extends Sample {
public final String uri;
+ public final String vendor;
+ public final boolean feedback_events;
+ public final String burst_uri;
+ public final String retransmission_uri;
public final String extension;
public final String adTagUri;
public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri,
- String extension, String adTagUri) {
+ String vendor, boolean feedback_events, String burst_uri, String retransmission_uri,
+ String extension, String adTagUri) {
super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
this.uri = uri;
+ this.vendor = vendor;
+ this.feedback_events =feedback_events;
+ this.burst_uri = burst_uri;
+ this.retransmission_uri = retransmission_uri;
this.extension = extension;
this.adTagUri = adTagUri;
}
@@ -421,6 +447,10 @@ public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
public Intent buildIntent(Context context) {
return super.buildIntent(context)
.setData(Uri.parse(uri))
+ .putExtra(PlayerActivity.VENDOR_EXTRA, vendor)
+ .putExtra(PlayerActivity.FEEDBACK_EVENTS_EXTRA, feedback_events)
+ .putExtra(PlayerActivity.BURST_URI_EXTRA, burst_uri)
+ .putExtra(PlayerActivity.RETRANSMISSION_URI_EXTRA, retransmission_uri)
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
.putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri)
.setAction(PlayerActivity.ACTION_VIEW);
diff --git a/library/core/src/main/AndroidManifest.xml b/library/core/src/main/AndroidManifest.xml
index 430930a3cad..9278ac11c00 100644
--- a/library/core/src/main/AndroidManifest.xml
+++ b/library/core/src/main/AndroidManifest.xml
@@ -14,4 +14,4 @@
limitations under the License.
-->
-
+
\ No newline at end of file
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSource.java
new file mode 100644
index 00000000000..c3aad583c8c
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSource.java
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (C) 2017 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.upstream;
+
+import android.net.Uri;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.util.rtp.DefaultRtpDistributionFeedbackFactory;
+import com.google.android.exoplayer2.util.rtp.AluRtpDistributionFeedbackFactory;
+import com.google.android.exoplayer2.util.rtp.RtpDistributionFeedback;
+import com.google.android.exoplayer2.util.rtp.RtpPacket;
+import com.google.android.exoplayer2.util.rtp.RtpPacketQueue;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpSessionUtils;
+
+import java.io.IOException;
+
+/**
+ * A RTP {@link DataSource}.
+ */
+public final class RtpDataSource implements DataSource {
+
+ /**
+ * Thrown when an error is encountered when trying to read from a {@link RtpDataSource}.
+ */
+ public static final class RtpDataSourceException extends IOException {
+
+ public RtpDataSourceException(String message) {
+ super(message);
+ }
+
+ public RtpDataSourceException(Exception cause) {
+ super(cause);
+ }
+
+ }
+
+ /**
+ * The maximum transfer unit, in bytes.
+ */
+ public static final int MTU_SIZE = 1500;
+
+ private final TransferListener super RtpDataSource> listener;
+
+ /**
+ * The RTP distribution and feedback scheme implementation
+ */
+ private RtpDistributionFeedback distributionFeedback;
+
+ /**
+ * The RTP feedback properties
+ */
+ private final RtpDistributionFeedback.RtpFeedbackProperties feedbackProperties;
+
+ /**
+ * The RTP source holders
+ */
+ private RtpBurstSourceHolder burstSourceHolder;
+ private RtpAuthTokenSourceHolder authTokenSourceHolder;
+ private RtpDistributionSourceHolder distributionSourceHolder;
+ private RtpRetransmissionSourceHolder retransmissionSourceHolder;
+
+ private long lastTimeStampBytesReaded;
+
+ private DataSpec dataSpec;
+ private boolean opened;
+
+ /**
+ * @param listener An optional listener.
+ */
+ public RtpDataSource(TransferListener super RtpDataSource> listener) {
+ this(listener, new RtpDistributionFeedback.RtpFeedbackProperties());
+ }
+
+ /**
+ * @param listener An optional listener.
+ * @param feedbackProperties The feedback properties to be set to the data source.
+ */
+ public RtpDataSource(TransferListener super RtpDataSource> listener,
+ RtpDistributionFeedback.RtpFeedbackProperties feedbackProperties) {
+ this.listener = listener;
+ this.feedbackProperties = (feedbackProperties == null) ?
+ new RtpDistributionFeedback.RtpFeedbackProperties() : feedbackProperties;
+ }
+
+ /**
+ * Sets the value of a feedback property. The value will be used to establish a specific a
+ * feedback scheme and model.
+ *
+ * @param property The name of the feedback property.
+ * @param value The value of the feedback property.
+ */
+
+ public void setFeedbackProperty(int property, Object value) {
+ feedbackProperties.set(property, value);
+ }
+
+ /**
+ * Builds specific distribution and feedback implementation from vendor.
+ * A default distribution and feedback implementation will be created whether no vendor model
+ * was given from feedback properties.
+ *
+ * @return The {@link RtpDistributionFeedback}.
+ */
+
+ private RtpDistributionFeedback buildRtpDistributionFeedback() {
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.FB_VENDOR)) {
+
+ int fbVendor = (int) feedbackProperties.getSnapshot().get(RtpDistributionFeedback.Properties.
+ FB_VENDOR);
+
+ switch (fbVendor) {
+ case RtpDistributionFeedback.Providers.ALU:
+ return new AluRtpDistributionFeedbackFactory(RtcpSessionUtils.SSRC(),
+ RtcpSessionUtils.CNAME()).createDistributionFeedback();
+
+ default:
+ return new DefaultRtpDistributionFeedbackFactory(RtcpSessionUtils.SSRC(),
+ RtcpSessionUtils.CNAME()).createDistributionFeedback();
+ }
+
+ } else {
+ return new DefaultRtpDistributionFeedbackFactory(RtcpSessionUtils.SSRC(),
+ RtcpSessionUtils.CNAME()).createDistributionFeedback();
+ }
+ }
+
+ @Override
+ public long open(DataSpec dataSpec) throws RtpDataSourceException {
+ this.dataSpec = dataSpec;
+
+ distributionFeedback = buildRtpDistributionFeedback();
+ distributionSourceHolder = new RtpDistributionSourceHolder(distributionFeedback);
+
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.
+ FB_EVENTS_CALLBACK)) {
+
+ distributionFeedback.setFeedbackEventListener(
+ (RtpDistributionFeedback.RtpFeedbackEventListener)
+ feedbackProperties.getSnapshot().get(RtpDistributionFeedback.Properties.
+ FB_EVENTS_CALLBACK));
+ }
+
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.FB_SCHEME)) {
+
+ int fbScheme = (int) feedbackProperties.getSnapshot().get(RtpDistributionFeedback.
+ Properties.FB_SCHEME);
+
+ if ((fbScheme & RtpDistributionFeedback.Schemes.FB_PORT_MAPPING) ==
+ RtpDistributionFeedback.Schemes.FB_PORT_MAPPING) {
+
+ authTokenSourceHolder = new RtpAuthTokenSourceHolder(distributionFeedback);
+
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.
+ FB_PORT_MAPPING_URI)) {
+ try {
+
+ authTokenSourceHolder.open(Uri.parse((String)feedbackProperties.getSnapshot().
+ get(RtpDistributionFeedback.Properties.FB_PORT_MAPPING_URI)));
+
+ } catch (IOException ex) {
+ // ....
+ }
+ }
+ }
+
+ if ((fbScheme & RtpDistributionFeedback.Schemes.FB_RAMS) ==
+ RtpDistributionFeedback.Schemes.FB_RAMS) {
+
+ burstSourceHolder = new RtpBurstSourceHolder(distributionFeedback);
+
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.
+ FB_RAMS_URI)) {
+
+ try {
+
+ if ((authTokenSourceHolder == null) || (!authTokenSourceHolder.isOpened())) {
+ burstSourceHolder.open(Uri.parse((String) feedbackProperties.getSnapshot().
+ get(RtpDistributionFeedback.Properties.FB_RAMS_URI)));
+ }
+
+ } catch (IOException ex) {
+
+ try {
+
+ distributionSourceHolder.open(dataSpec.uri);
+
+ } catch (IOException ex2) {
+ throw new RtpDataSourceException(ex2);
+ }
+
+ }
+ }
+ }
+
+ if ((burstSourceHolder == null) || !burstSourceHolder.isOpened()) {
+ try {
+
+ if (!distributionSourceHolder.isOpened()) {
+ distributionSourceHolder.open(dataSpec.uri);
+ }
+
+ } catch (IOException ex2) {
+ throw new RtpDataSourceException(ex2);
+ }
+ }
+
+ if ((fbScheme & RtpDistributionFeedback.Schemes.FB_CONGESTION_CONTROL) ==
+ RtpDistributionFeedback.Schemes.FB_CONGESTION_CONTROL) {
+
+ retransmissionSourceHolder = new RtpRetransmissionSourceHolder(distributionFeedback);
+
+ if (feedbackProperties.getSnapshot().containsKey(RtpDistributionFeedback.Properties.
+ FB_CONGESTION_CONTROL_URI)) {
+
+ try {
+
+ retransmissionSourceHolder.open(Uri.parse((String) feedbackProperties.getSnapshot().
+ get(RtpDistributionFeedback.Properties.FB_CONGESTION_CONTROL_URI)));
+
+ } catch (IOException ex) {
+ // Do nothing
+ }
+ }
+ }
+
+ } else {
+
+ try {
+
+ distributionSourceHolder.open(dataSpec.uri);
+
+ } catch (IOException ex) {
+ throw new RtpDataSourceException(ex);
+ }
+ }
+
+ if (listener != null) {
+ listener.onTransferStart(this, dataSpec);
+ }
+
+ opened = true;
+ return C.LENGTH_UNSET;
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int readLength) throws RtpDataSourceException {
+ int length = 0;
+
+ if (distributionSourceHolder.isOpened()) {
+
+ if ((burstSourceHolder != null) &&
+ (burstSourceHolder.isOpened() || burstSourceHolder.isDataAvailable())) {
+ length = burstSourceHolder.read(buffer, offset, readLength);
+
+ } else {
+
+ if ((retransmissionSourceHolder != null)) {
+ if (retransmissionSourceHolder.isDataAvailable()) {
+ if (retransmissionSourceHolder.getFirstSequenceNumberAvailable() <
+ distributionSourceHolder.getFirstSequenceNumberAvailable()) {
+
+ if (retransmissionSourceHolder.getFirstTimeStampAvailable() <=
+ distributionSourceHolder.getFirstTimeStampAvailable()) {
+ length = retransmissionSourceHolder.read(buffer, offset, readLength);
+
+ } else {
+ length = distributionSourceHolder.read(buffer, offset, readLength);
+ }
+
+ } else if (retransmissionSourceHolder.getFirstTimeStampAvailable() <
+ distributionSourceHolder.getFirstTimeStampAvailable()) {
+ length = retransmissionSourceHolder.read(buffer, offset, readLength);
+
+ } else {
+ length = distributionSourceHolder.read(buffer, offset, readLength);
+ }
+
+ } else if (retransmissionSourceHolder.isDataPending()) {
+ long delay = (int) (System.currentTimeMillis() - lastTimeStampBytesReaded);
+
+ if (delay > retransmissionSourceHolder.getMaxDelayTimeForPending()) {
+ retransmissionSourceHolder.resetAllPacketsRecoveryPending(
+ distributionSourceHolder.getFirstTimeStampAvailable());
+
+ length = distributionSourceHolder.read(buffer, offset, readLength);
+ }
+
+ } else {
+ length = distributionSourceHolder.read(buffer, offset, readLength);
+ }
+ } else {
+ length = distributionSourceHolder.read(buffer, offset, readLength);
+ }
+ }
+
+ } else {
+
+ if (burstSourceHolder != null) {
+ length = burstSourceHolder.read(buffer, offset, readLength);
+ }
+ }
+
+ if (length > 0) {
+ listener.onBytesTransferred(this, length);
+ lastTimeStampBytesReaded = System.currentTimeMillis();
+ }
+
+ return length;
+ }
+
+ @Override
+ public Uri getUri() {
+ return dataSpec.uri;
+ }
+
+ @Override
+ public void close() {
+ if ((burstSourceHolder != null) && (burstSourceHolder.isOpened())) {
+ burstSourceHolder.close();
+ }
+
+ if ((retransmissionSourceHolder != null) && (retransmissionSourceHolder.isOpened())) {
+ retransmissionSourceHolder.close();
+ }
+
+ if ((distributionSourceHolder != null) && (distributionSourceHolder.isOpened())) {
+ distributionSourceHolder.close();
+ }
+
+ if (opened) {
+ opened = false;
+
+ if (listener != null) {
+ listener.onTransferEnd(this);
+ }
+
+ }
+ }
+
+
+ private class RtpAuthTokenSourceHolder implements
+ RtpDistributionFeedback.RtpFeedbackTargetSource.AuthTokenEventListener,
+ Handler.Callback {
+
+ private RtpDistributionFeedback.RtpAuthTokenSource authTokenSource;
+
+ private final Handler mediaHandler;
+ private final HandlerThread mediaThread;
+ private final Loader mediaLoader;
+
+ private boolean opened;
+ private boolean released;
+
+ private static final int MSG_SOURCE_RELEASE = 4;
+
+ public RtpAuthTokenSourceHolder(RtpDistributionFeedback distributionFeedback) {
+ mediaThread = new HandlerThread("Handler:RtpBurstSource", Process.THREAD_PRIORITY_AUDIO);
+ mediaThread.start();
+
+ mediaHandler = new Handler(mediaThread.getLooper(), this);
+ mediaLoader = new Loader("Loader:RtpBurstSource");
+
+ opened = false;
+ released = true;
+
+ try {
+
+ authTokenSource = distributionFeedback.createAuthTokenSource(this);
+
+ } catch (RtpDistributionFeedback.UnsupportedRtpDistributionFeedbackSourceException ex) {
+ mediaHandler.sendEmptyMessage(MSG_SOURCE_RELEASE);
+ }
+ }
+
+ void open(Uri uri) throws IOException {
+ if (!opened) {
+
+ authTokenSource.open(uri);
+
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.startLoading(authTokenSource, authTokenSource, 0);
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+
+ authTokenSource.sendAuthTokenRequest();
+
+ opened = true;
+ released = false;
+ }
+ }
+
+ void close() {
+ if (opened) {
+ if (!authTokenSource.isLoadCanceled()) {
+ authTokenSource.cancelLoad();
+ authTokenSource.close();
+ }
+
+ opened = false;
+ }
+
+ release();
+ }
+
+ void release() {
+ if (!released) {
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.release();
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+ mediaThread.quit();
+
+ released = true;
+ }
+ }
+
+ public boolean isOpened() {
+ return opened;
+ }
+
+
+ // RtpDistributionFeedback.RtpFeedbackEventListener.AuthTokenEventListener implementation
+ @Override
+ public void onAuthTokenResponse() {
+ try {
+
+ if (burstSourceHolder != null) {
+ burstSourceHolder.open(Uri.parse((String) feedbackProperties.getSnapshot().
+ get(RtpDistributionFeedback.Properties.FB_RAMS_URI)));
+ }
+
+ } catch (IOException ex) {}
+ }
+
+ @Override
+ public void onAuthTokenResponseBeforeTimeout() {
+ close();
+ }
+
+ @Override
+ public void onAuthTokenResponseBeforeError() {
+ close();
+ }
+
+ @Override
+ public void onAuthTokenResponseUnexpected() {
+ close();
+ }
+
+ @Override
+ public void onRtpAuthTokenSourceError() {
+ close();
+ }
+
+ @Override
+ public void onRtpAuthTokenSourceCanceled() {
+ close();
+ }
+
+ // Handler.Callback implementation
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case MSG_SOURCE_RELEASE: {
+ release();
+ return true;
+ }
+
+ default:
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ private class RtpBurstSourceHolder implements
+ RtpDistributionFeedback.RtpFeedbackTargetSource.BurstEventListener,
+ Handler.Callback {
+
+ private RtpPacketQueue packetQueue;
+ private RtpDistributionFeedback.RtpBurstSource burstSource;
+
+ private boolean opened;
+ private boolean error;
+ private boolean released;
+
+ private final Handler mediaHandler;
+ private final HandlerThread mediaThread;
+ private final Loader mediaLoader;
+
+ private final ConditionVariable loadCondition;
+
+ // Internal messages
+ private static final int MSG_SOURCE_RELEASE = 1;
+
+ public RtpBurstSourceHolder(RtpDistributionFeedback distributionFeedback) {
+
+ mediaThread = new HandlerThread("Handler:RtpBurstSource", Process.THREAD_PRIORITY_AUDIO);
+ mediaThread.start();
+
+ mediaHandler = new Handler(mediaThread.getLooper(), this);
+ mediaLoader = new Loader("Loader:RtpBurstSource");
+
+ loadCondition = new ConditionVariable();
+
+ opened = false;
+ error = false;
+ released = true;
+
+ try {
+
+ burstSource = distributionFeedback.createBurstSource(this);
+ packetQueue = new RtpPacketQueue(burstSource.getMaxBufferCapacity());
+
+ } catch (RtpDistributionFeedback.UnsupportedRtpDistributionFeedbackSourceException ex) {
+ mediaHandler.sendEmptyMessage(MSG_SOURCE_RELEASE);
+ }
+ }
+
+ void open(Uri uri) throws IOException {
+ if (!opened) {
+
+ burstSource.open(uri);
+
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.startLoading(burstSource, burstSource, 0);
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+
+ burstSource.sendBurstRapidAcquisitionRequest(dataSpec.uri);
+
+ opened = true;
+ released = false;
+ }
+ }
+
+ public int read(byte[] buffer, int offset, int readLength) throws RtpDataSourceException {
+ int rbytes;
+
+ if (error && !packetQueue.isDataAvailable()) {
+ throw new RtpDataSourceException("RtpBurstSource is closed");
+ }
+
+ if (!packetQueue.isDataAvailable()) {
+ loadCondition.block();
+ }
+
+ rbytes = packetQueue.get(buffer, offset, readLength);
+
+ loadCondition.close();
+
+ return rbytes;
+ }
+
+ void close() {
+ if (opened) {
+ if (!burstSource.isLoadCanceled()) {
+ burstSource.cancelLoad();
+ burstSource.close();
+ }
+
+ opened = false;
+ loadCondition.open();
+ }
+
+ release();
+ }
+
+ void release() {
+ if (!released) {
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.release();
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+ mediaThread.quit();
+
+ released = true;
+ }
+ }
+
+ boolean isDataAvailable() {
+ return (packetQueue != null) && (packetQueue.isDataAvailable());
+ }
+
+ public boolean isOpened() {
+ return opened;
+ }
+
+ public boolean isError() {
+ return error;
+ }
+
+ // RtpDistributionFeedback.RtpFeedbackEventListener.BurstEventListener implementation
+ @Override
+ public void onBurstRapidAcquisitionAccepted() {
+ }
+
+ @Override
+ public void onBurstRapidAcquisitionRejected() {
+ try {
+
+ burstSource.sendBurstTerminationRequest();
+
+ } catch (IOException ex) {
+ // Do nothing
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void oBurstRapidAcquisitionResponseBeforeTimeout() {
+ close();
+ }
+
+ @Override
+ public void onMulticastJoinSignal() {
+ try {
+
+ distributionSourceHolder.open(dataSpec.uri);
+
+ } catch (IOException ex) {
+ // Do nothing
+ close();
+ }
+ }
+
+ @Override
+ public void onBurstRapidAcquisitionCompleted() {
+ try {
+
+ burstSource.sendBurstTerminationRequest();
+
+ } catch (IOException ex) {
+ // Do nothing
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void onInvalidToken() {
+ close();
+ }
+
+ @Override
+ public void onRtpPacketBurstReceived(RtpPacket packet) {
+ packetQueue.push(packet);
+ loadCondition.open();
+ }
+
+ @Override
+ public void onRtpBurstSourceError() {
+
+ try {
+
+ if (!distributionSourceHolder.isOpened()) {
+ distributionSourceHolder.open(dataSpec.uri);
+ }
+
+ } catch (IOException ex) {
+ // Do nothing
+ } finally {
+ error = true;
+ close();
+ }
+ }
+
+ @Override
+ public void onRtpBurstSourceCanceled() {
+ close();
+ }
+
+
+ // Handler.Callback implementation
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ switch (msg.what) {
+
+ case MSG_SOURCE_RELEASE: {
+ release();
+ return true;
+ }
+
+ default:
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ private class RtpRetransmissionSourceHolder implements
+ RtpDistributionFeedback.RtpFeedbackTargetSource.RetransmissionEventListener,
+ Handler.Callback {
+
+ private RtpPacketQueue packetQueue;
+ private RtpDistributionFeedback.RtpRetransmissionSource retransmissionSource;
+
+ private boolean opened;
+ private boolean error;
+ private boolean released;
+
+ private final Handler mediaHandler;
+ private final HandlerThread mediaThread;
+ private final Loader mediaLoader;
+
+ private final ConditionVariable loadCondition;
+
+ // Internal messages
+ private static final int MSG_SOURCE_RELEASE = 1;
+
+ public RtpRetransmissionSourceHolder(RtpDistributionFeedback distributionFeedback) {
+
+ mediaThread = new HandlerThread("Handler:RtpRetransmissionSource", Process.THREAD_PRIORITY_AUDIO);
+ mediaThread.start();
+
+ mediaHandler = new Handler(mediaThread.getLooper(), this);
+ mediaLoader = new Loader("Loader:RtpRetransmissionSource");
+
+ loadCondition = new ConditionVariable();
+
+ opened = false;
+ released = true;
+
+ try {
+
+ retransmissionSource = distributionFeedback.createRetransmissionSource(this);
+ packetQueue = new RtpPacketQueue(retransmissionSource.getMaxBufferCapacity());
+
+ } catch (RtpDistributionFeedback.UnsupportedRtpDistributionFeedbackSourceException ex) {
+ mediaHandler.sendEmptyMessage(MSG_SOURCE_RELEASE);
+ }
+ }
+
+ void open(Uri uri) throws IOException {
+ if (!opened) {
+
+ retransmissionSource.open(uri);
+
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.startLoading(retransmissionSource, retransmissionSource, 0);
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+
+ opened = true;
+ released = false;
+ }
+ }
+
+ public int read(byte[] buffer, int offset, int readLength) throws RtpDataSourceException {
+ int rbytes;
+
+ if (error && !packetQueue.isDataAvailable()) {
+ throw new RtpDataSourceException("RtpRetransmissionSource is closed");
+ }
+
+ if (!packetQueue.isDataAvailable()) {
+ loadCondition.block();
+ }
+
+ rbytes = packetQueue.get(buffer, offset, readLength);
+
+ loadCondition.close();
+
+ return rbytes;
+ }
+
+ void close() {
+ if (opened) {
+ if (!retransmissionSource.isLoadCanceled()) {
+
+ try {
+
+ retransmissionSource.sendRetransmissionTerminationRequest();
+
+ } catch (IOException ex) { }
+
+ retransmissionSource.cancelLoad();
+ retransmissionSource.close();
+ }
+
+ opened = false;
+ loadCondition.open();
+ }
+
+ release();
+ }
+
+ void release() {
+ if (!released) {
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.release();
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+ mediaThread.quit();
+
+ released = true;
+ }
+ }
+
+ void resetAllPacketsRecoveryPending(long timestamp) {
+ retransmissionSource.resetAllPacketsRecoveryPending(timestamp);
+ }
+
+ long getMaxDelayTimeForPending() {
+ return retransmissionSource.getMaxDelayTimeForPending();
+ }
+
+ void lostPacketEvent(int lastSequenceReceived, int numLostPackets) {
+ if ((retransmissionSource.getPacketsRecoveryPending() + numLostPackets) <
+ retransmissionSource.getMaxPacketsRecoveryPending()) {
+ try {
+
+ retransmissionSource.sendRetransmissionPacketRequest(lastSequenceReceived, numLostPackets);
+
+ } catch (IOException ex) {
+ // Do nothing
+ }
+ } else {
+ retransmissionSource.resetAllPacketsRecoveryPending(0);
+ }
+ }
+
+ public boolean isOpened() {
+ return opened;
+ }
+
+ public boolean isError() {
+ return error;
+ }
+
+ boolean isDataAvailable() {
+ return (packetQueue != null) && (packetQueue.isDataAvailable());
+ }
+
+ int getFirstSequenceNumberAvailable() {
+ return packetQueue.front().getSequenceNumber();
+ }
+
+ long getFirstTimeStampAvailable() {
+ return packetQueue.front().getTimeStamp();
+ }
+
+ public boolean isDataPending() { return retransmissionSource.getPacketsRecoveryPending() > 0; }
+
+ // RtpDistributionFeedback.RtpFeedbackEventListener.RetransmissionEventListener implementation
+ @Override
+ public void onInvalidToken() {
+ close();
+ }
+
+ @Override
+ public void onRtpPacketLossReceived(RtpPacket packet) {
+ packetQueue.push(packet);
+ loadCondition.open();
+ }
+
+ @Override
+ public void onRtpRetransmissionSourceError() {
+ error = true;
+ close();
+ }
+
+ @Override
+ public void onRtpRetransmissionSourceCanceled() {
+ close();
+ }
+
+ // Handler.Callback implementation
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ switch (msg.what) {
+
+ case MSG_SOURCE_RELEASE: {
+ release();
+ return true;
+ }
+
+ default:
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+
+ private class RtpDistributionSourceHolder implements
+ RtpDistributionFeedback.RtpDistributionEventListener,
+ Handler.Callback {
+
+ private RtpPacketQueue packetQueue;
+ private RtpDistributionFeedback.RtpDistributionSource distributionSource;
+
+ private boolean opened;
+ private boolean error;
+ private boolean released;
+
+ private final Handler mediaHandler;
+ private final HandlerThread mediaThread;
+ private final Loader mediaLoader;
+
+ private final ConditionVariable loadCondition;
+
+ // Internal messages
+ private static final int MSG_SOURCE_RELEASE = 1;
+
+ public RtpDistributionSourceHolder(RtpDistributionFeedback distributionFeedback) {
+ mediaThread = new HandlerThread("Handler:RtpDistributionSource", Process.THREAD_PRIORITY_AUDIO);
+ mediaThread.start();
+
+ mediaHandler = new Handler(mediaThread.getLooper(), this);
+ mediaLoader = new Loader("Loader:RtpDistributionSource");
+
+ loadCondition = new ConditionVariable();
+
+ opened = false;
+ released = true;
+
+ try {
+
+ distributionSource = distributionFeedback.createDistributionSource(this);
+ packetQueue = new RtpPacketQueue(distributionSource.getMaxBufferCapacity());
+
+ } catch (RtpDistributionFeedback.UnsupportedRtpDistributionFeedbackSourceException ex) {
+ mediaHandler.sendEmptyMessage(MSG_SOURCE_RELEASE);
+ }
+ }
+
+ void open(Uri uri) throws IOException {
+ if (!opened) {
+ distributionSource.open(uri);
+
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.startLoading(distributionSource, distributionSource, 0);
+ }
+ };
+
+ opened = true;
+ released = false;
+
+ mediaHandler.post(currentThreadTask);
+ }
+ }
+
+ public int read(byte[] buffer, int offset, int readLength) throws RtpDataSourceException {
+ int rbytes;
+
+ if (!opened || error) {
+ throw new RtpDataSourceException("RtpDistributionSource is closed");
+ }
+
+ if (!packetQueue.isDataAvailable()) {
+ loadCondition.block();
+ }
+
+ rbytes = packetQueue.get(buffer, offset, readLength);
+
+ loadCondition.close();
+
+ return rbytes;
+ }
+
+ void close() {
+ if (opened) {
+ if (!distributionSource.isLoadCanceled()) {
+
+ distributionSource.cancelLoad();
+ distributionSource.close();
+ }
+
+ opened = false;
+ loadCondition.open();
+ }
+
+ release();
+ }
+
+ void release() {
+ if (!released) {
+ Runnable currentThreadTask = new Runnable() {
+ @Override
+ public void run() {
+ mediaLoader.release();
+ }
+ };
+
+ mediaHandler.post(currentThreadTask);
+ mediaThread.quit();
+
+ released = true;
+ }
+ }
+
+ int getFirstSequenceNumberAvailable() {
+ return packetQueue.front().getSequenceNumber();
+ }
+
+ long getFirstTimeStampAvailable() {
+ return packetQueue.front().getTimeStamp();
+ }
+
+ public boolean isOpened() {
+ return opened;
+ }
+
+ public boolean isError() {
+ return error;
+ }
+
+ // RtpDistributionFeedback.RtpDistributionEventListener implementation
+ @Override
+ public void onRtpPacketReceived(RtpPacket packet) {
+ packetQueue.push(packet);
+ loadCondition.open();
+ }
+
+ @Override
+ public void onRtpLostPacketDetected(int lastSequenceReceived, int numLostPackets) {
+ if ((retransmissionSourceHolder != null) && retransmissionSourceHolder.isOpened()) {
+ retransmissionSourceHolder.lostPacketEvent(lastSequenceReceived, numLostPackets);
+ }
+ }
+
+ @Override
+ public void onRtpDistributionSourceError() {
+ error = true;
+ close();
+ }
+
+ @Override
+ public void onRtpDistributionSourceCanceled() {
+ close();
+ }
+
+ // Handler.Callback implementation
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ switch (msg.what) {
+
+ case MSG_SOURCE_RELEASE: {
+ release();
+ return true;
+ }
+
+ default:
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSourceFactory.java
new file mode 100644
index 00000000000..f969e7974e6
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RtpDataSourceFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.upstream;
+
+import com.google.android.exoplayer2.upstream.DataSource.Factory;
+import com.google.android.exoplayer2.util.rtp.RtpDistributionFeedback;
+
+/**
+ * A {@link Factory} that produces {@link RtpDataSourceFactory} for RTP data sources.
+ */
+public final class RtpDataSourceFactory implements Factory {
+
+ private final TransferListener super DataSource> listener;
+ private final RtpDistributionFeedback.RtpFeedbackProperties feedbackProperties;
+ public RtpDataSourceFactory() {
+ this(null);
+ }
+
+ /**
+ * @param listener An optional listener.
+ */
+ public RtpDataSourceFactory(TransferListener super DataSource> listener) {
+ this.listener = listener;
+ this.feedbackProperties = new RtpDistributionFeedback.RtpFeedbackProperties();
+ }
+
+ public final RtpDistributionFeedback.RtpFeedbackProperties getFeedbackProperties() {
+ return feedbackProperties;
+ }
+
+ public final void setFeedbackProperty(Integer id, Object value) {
+ feedbackProperties.set(id, value);
+ }
+
+ public final void clearFeedbackProperty(Integer id) {
+ feedbackProperties.remove(id);
+ }
+
+ @Override
+ public DataSource createDataSource() {
+ return new RtpDataSource(listener, feedbackProperties);
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/net/Connectivity.java b/library/core/src/main/java/com/google/android/exoplayer2/util/net/Connectivity.java
new file mode 100644
index 00000000000..8a7530a65dc
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/net/Connectivity.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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.util.net;
+
+import android.app.Application;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+
+/**
+ * Check device's network connectivity and speed
+ *
+ */
+public class Connectivity {
+
+ private static Application getApplicationContext() throws Exception {
+ return (Application) Class.forName("android.app.ActivityThread")
+ .getMethod("currentApplication").invoke(null, (Object[]) null);
+ }
+
+ /**
+ * Get the network info
+ * @return
+ */
+ public static NetworkInfo getNetworkInfo() throws Exception {
+ ConnectivityManager cm = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ return cm.getActiveNetworkInfo();
+ }
+
+ /**
+ * Check if there is any connectivity
+ * @return
+ */
+ public static boolean isConnected() throws Exception {
+ NetworkInfo info = Connectivity.getNetworkInfo();
+ return (info != null && info.isConnected());
+ }
+
+ /**
+ * Check if there is any connectivity to a Wifi network
+ * @return
+ */
+ public static boolean isConnectedWifi() throws Exception {
+ NetworkInfo info = Connectivity.getNetworkInfo();
+ return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI);
+ }
+
+ /**
+ * Check if there is any connectivity to a mobile network
+ * @return
+ */
+ public static boolean isConnectedMobile() throws Exception {
+ NetworkInfo info = Connectivity.getNetworkInfo();
+ return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE);
+ }
+
+ /**
+ * Check if there is any connectivity to a mobile network
+ * @return
+ */
+ public static boolean isConnectedEthernet() throws Exception {
+ NetworkInfo info = Connectivity.getNetworkInfo();
+ return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_ETHERNET);
+ }
+
+ /**
+ * Check if there is fast connectivity
+ * @return
+ */
+ public static boolean isConnectedFast() throws Exception {
+ NetworkInfo info = Connectivity.getNetworkInfo();
+ return (info != null && info.isConnected() && Connectivity.isConnectionFast(info.getType(),info.getSubtype()));
+ }
+
+ /**
+ * Check if the connection is fast
+ * @return
+ */
+ public static boolean isConnectionFast(int type, int subType){
+ if(type==ConnectivityManager.TYPE_WIFI){
+ return true;
+ }else if(type==ConnectivityManager.TYPE_MOBILE){
+ switch(subType){
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return false; // ~ 50-100 kbps
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ return false; // ~ 14-64 kbps
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ return false; // ~ 50-100 kbps
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ return true; // ~ 400-1000 kbps
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ return true; // ~ 600-1400 kbps
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ return false; // ~ 100 kbps
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ return true; // ~ 2-14 Mbps
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ return true; // ~ 700-1700 kbps
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ return true; // ~ 1-23 Mbps
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ return true; // ~ 400-7000 kbps
+ /*
+ * Above API level 7, make sure to set android:targetSdkVersion
+ * to appropriate level to use these
+ */
+ case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11
+ return true; // ~ 1-2 Mbps
+ case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9
+ return true; // ~ 5 Mbps
+ case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13
+ return true; // ~ 10-20 Mbps
+ case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8
+ return false; // ~25 kbps
+ case TelephonyManager.NETWORK_TYPE_LTE: // API level 11
+ return true; // ~ 10+ Mbps
+ // Unknown
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+ default:
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/net/NetworkUtils.java b/library/core/src/main/java/com/google/android/exoplayer2/util/net/NetworkUtils.java
new file mode 100644
index 00000000000..4645c2daa53
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/net/NetworkUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.util.net;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+public class NetworkUtils {
+ /**
+ * Returns MAC address of the given interface name.
+ * @param interfaceName eth0, wlan0 or NULL=use first interface
+ * @return mac address or null
+ */
+ public static String getMACAddress(String interfaceName) {
+ try {
+ List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+
+ for (NetworkInterface intf : interfaces) {
+ if (interfaceName != null) {
+ if (!intf.getName().equalsIgnoreCase(interfaceName)) continue;
+ }
+
+ byte[] mac = intf.getHardwareAddress();
+
+ if (mac==null) return "";
+ StringBuilder buf = new StringBuilder();
+
+ for (int idx=0; idx0) buf.deleteCharAt(buf.length()-1);
+
+ return buf.toString();
+ }
+
+ } catch (Exception ex) { }
+
+ return null;
+ }
+
+ /**
+ * Returns local address.
+ * @return ip address or null
+ */
+ public static String getLocalAddress() {
+ try {
+
+ for (Enumeration networkEnum = NetworkInterface.getNetworkInterfaces(); networkEnum.hasMoreElements();) {
+
+ NetworkInterface network = networkEnum.nextElement();
+
+ for (Enumeration enumIpAddr = network.getInetAddresses(); enumIpAddr.hasMoreElements();) {
+ InetAddress inetAddress = enumIpAddr.nextElement();
+
+ if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
+ return inetAddress.getHostAddress().toString();
+ }
+ }
+ }
+
+ } catch (Exception ex) { }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedback.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedback.java
new file mode 100644
index 00000000000..5796a07da9e
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedback.java
@@ -0,0 +1,852 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+import android.net.Uri;
+import android.util.SparseArray;
+
+import com.google.android.exoplayer2.util.net.NetworkUtils;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpFeedbackPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpPacketBuilder;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpPacketUtils;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpTokenPacket;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * The RTP Distribution and Feedback Model implementation based on Alcate-Lucent architecture
+ */
+public final class AluRtpDistributionFeedback implements RtpDistributionFeedback {
+
+ private final long ssrc;
+ private final String cname;
+
+ private long ssrcSender;
+ private boolean ssrcSenderReceived;
+
+ private int firstAudioSequence;
+ private int firstVideoSequence;
+
+ private int lastSequenceReceived;
+
+ private boolean multicastSwitched;
+
+ // Default socket time-out in milliseconds
+ private static final int BURST_SOURCE_TIMEOUT = 50;
+ private static final int DISTRIBUTION_SOURCE_TIMEOUT = 2000;
+
+ private final AluRtpHeaderExtensionParser rtpHeadExtParser;
+ private RtpFeedbackEventListener feedbackListener;
+
+ public AluRtpDistributionFeedback(long ssrc, String cname) {
+ this.ssrc = ssrc;
+ this.cname = cname;
+
+ ssrcSender = 0L;
+
+ multicastSwitched = false;
+ ssrcSenderReceived = false;
+
+ firstAudioSequence = UNKNOWN_SEQ;
+ firstVideoSequence = UNKNOWN_SEQ;
+
+ lastSequenceReceived = UNKNOWN_SEQ;
+
+ rtpHeadExtParser = new AluRtpHeaderExtensionParser();
+ }
+
+ @Override
+ public RtpAuthTokenSource createAuthTokenSource(
+ RtpFeedbackTargetSource.AuthTokenEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ throw new UnsupportedRtpDistributionFeedbackSourceException(
+ "Authentication Token Source unsupported");
+ }
+
+ @Override
+ public RtpBurstSource createBurstSource(
+ RtpFeedbackTargetSource.BurstEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new AluRtpBurstSource(BURST_SOURCE_TIMEOUT, eventListener);
+ }
+
+ @Override
+ public RtpRetransmissionSource createRetransmissionSource(
+ RtpFeedbackTargetSource.RetransmissionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new AluRtpRetransmissionSource(eventListener);
+ }
+
+ @Override
+ public RtpDistributionSource createDistributionSource(
+ RtpDistributionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new AluRtpDistributionSource(DISTRIBUTION_SOURCE_TIMEOUT, eventListener);
+ }
+
+ @Override
+ public void setFeedbackEventListener(
+ RtpFeedbackEventListener feedbackListener) {
+ this.feedbackListener = feedbackListener;
+ }
+
+ private static final boolean isAudioPacket(RtpPacket packet) {
+ return ((packet.getHeaderExtension()[5] & 0x3f) >> 4) == 1;
+ }
+
+ private static final boolean isMulticastJoinSignal(RtpPacket packet) {
+ return ((packet.getHeaderExtension()[5] & 0x0f) >> 3) == 1;
+ }
+
+ private static final boolean isAluExtension(RtpPacket packet) {
+ return (((packet.getHeaderExtension()[0] & 0xff) == 0xbe) &&
+ ((packet.getHeaderExtension()[1] & 0xff) == 0xde));
+ }
+
+
+ private final class AluRtpBurstSource extends RtpBurstSource {
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 1024;
+
+ private boolean audioSynch;
+ private boolean videoSynch;
+
+ private boolean multicastJoinSignal;
+
+ private final RtpFeedbackTargetSource.BurstEventListener eventListener;
+
+ public AluRtpBurstSource(int socketTimeoutMillis,
+ RtpFeedbackTargetSource.BurstEventListener eventListener) {
+ super(socketTimeoutMillis, eventListener);
+
+ this.eventListener = eventListener;
+
+ audioSynch = false;
+ videoSynch = false;
+
+ multicastJoinSignal = false;
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ boolean isRapidAcquisitionResponse(RtcpFeedbackPacket packet) {
+ // Not supported
+ return true;
+ }
+
+ @Override
+ boolean isRapidAcquisitionAccepted(RtcpFeedbackPacket packet) {
+ // Not supported
+ return true;
+ }
+
+ @Override
+ boolean isAuthTokenRejected(RtcpTokenPacket packet) {
+ // Not supported
+ return false;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+
+ if (!multicastJoinSignal && isMulticastJoinSignal(packet)) {
+ eventListener.onMulticastJoinSignal();
+ multicastJoinSignal = true;
+ }
+
+ if (!ssrcSenderReceived) {
+ ssrcSender = packet.getSsrc();
+ ssrcSenderReceived = true;
+ }
+
+ if (isAudioPacket(packet)) {
+
+ if (audioSynch)
+ return false;
+
+ if (firstAudioSequence == packet.getSequenceNumber()) {
+ audioSynch = true;
+
+ if (videoSynch) {
+ multicastSwitched = true;
+ eventListener.onBurstRapidAcquisitionCompleted();
+ }
+
+ return false;
+ }
+ else if (videoSynch) {
+ audioSynch = true;
+ multicastSwitched = true;
+ eventListener.onBurstRapidAcquisitionCompleted();
+ return false;
+ }
+ }
+ else {
+
+ if (videoSynch)
+ return false;
+
+ if (firstVideoSequence == packet.getSequenceNumber()) {
+ videoSynch = true;
+
+ if (audioSynch) {
+ multicastSwitched = true;
+ eventListener.onBurstRapidAcquisitionCompleted();
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // BurstMessages Implementation
+ @Override
+ public void sendBurstRapidAcquisitionRequest(Uri uri) throws IOException {
+ byte fccr_pkt[] = new byte [0];
+ InetAddress srcAddr, hostAddr;
+
+ try {
+
+ srcAddr = InetAddress.getByName(uri.getHost());
+ hostAddr = InetAddress.getByName(NetworkUtils.getLocalAddress());
+
+ } catch (UnknownHostException ex) {
+ throw new IOException(ex);
+ }
+
+ byte[] start = RtcpPacketUtils.longToBytes((long)300, 2);
+ byte[] sAddr = srcAddr.getAddress();
+ byte[] sPort = RtcpPacketUtils.longToBytes((long)uri.getPort(), 2);
+ byte[] hAddr = hostAddr.getAddress();
+ byte[] hPort = RtcpPacketUtils.longToBytes((long)0, 2);
+
+ fccr_pkt = RtcpPacketUtils.append(fccr_pkt, start);
+
+ fccr_pkt = RtcpPacketUtils.append(fccr_pkt, sPort);
+ fccr_pkt = RtcpPacketUtils.append(fccr_pkt, sAddr);
+
+ byte[] bounded = new byte [2];
+ fccr_pkt = RtcpPacketUtils.append (fccr_pkt, bounded);
+ fccr_pkt = RtcpPacketUtils.append(fccr_pkt, RtcpPacketUtils.swapBytes(hPort));
+ fccr_pkt = RtcpPacketUtils.append(fccr_pkt, RtcpPacketUtils.swapBytes(hAddr));
+
+ byte[] bytes = RtcpPacketBuilder.buildAppPacket(ssrc, cname,
+ "FCCR", fccr_pkt);
+
+ sendMessageFromBytes(bytes, bytes.length);
+
+ eventListener.onBurstRapidAcquisitionAccepted();
+ }
+
+ @Override
+ public void sendBurstTerminationRequest() throws IOException {
+ byte[] bytes = RtcpPacketBuilder.buildByePacket(ssrc, cname);
+ sendMessageFromBytes(bytes, bytes.length);
+ }
+ }
+
+ private final class AluRtpRetransmissionSource extends RtpRetransmissionSource {
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 512;
+
+ private static final int PACKET_LOSS_CAPACITY = 512;
+ private static final double PACKET_LOSS_PERCENT = 0.7;
+
+ // The maximum timeout delay for packet pending
+ private static final int MAX_TIMEOUT_DELAY = 1000;
+
+ private static final double PACKET_LOSS_ACCEPTABLE = PACKET_LOSS_CAPACITY * PACKET_LOSS_PERCENT;
+
+ // The current number of lost packet pending to recovery
+ private int lostPacketPending;
+
+ private final LinkedList keys;
+ private final SparseArray timestamps;
+
+ public AluRtpRetransmissionSource(RtpFeedbackTargetSource.RetransmissionEventListener
+ eventListener) {
+ super(eventListener);
+
+ timestamps = new SparseArray(PACKET_LOSS_CAPACITY);
+ keys = new LinkedList<>();
+
+ lostPacketPending = 0;
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ boolean isAuthTokenRejected(RtcpTokenPacket packet) {
+ return false;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+ Long timestamp = timestamps.get(packet.getSequenceNumber());
+
+ if (timestamp != null) {
+ packet.setTimestamp(timestamp);
+ timestamps.remove(packet.getSequenceNumber());
+ keys.remove((Integer)packet.getSequenceNumber());
+
+ lostPacketPending--;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void resetAllPacketsRecoveryPending(long timestamp) {
+ if (timestamp <= 0) {
+ keys.clear();
+ timestamps.clear();
+ lostPacketPending = 0;
+ }
+ else {
+ for (int index=0; index < keys.size(); index++) {
+ int seq = keys.get(index);
+
+ if (timestamps.get(seq) <= timestamp) {
+ keys.remove(index);
+ timestamps.remove(seq);
+
+ lostPacketPending--;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public long getMaxDelayTimeForPending() {
+ return MAX_TIMEOUT_DELAY;
+ }
+
+ @Override
+ public int getPacketsRecoveryPending() {
+ return lostPacketPending;
+ }
+
+ @Override
+ public int getMaxPacketsRecoveryPending() {
+ return (int) PACKET_LOSS_ACCEPTABLE;
+ }
+
+ // RetransmissionMessages implementation
+ @Override
+ public void sendRetransmissionPacketRequest(int lastSequenceReceived, int numLostPackets)
+ throws IOException {
+
+ List fbInformation = new ArrayList();
+
+ int bitmaskNextSequences, bitmaskShift;
+ int firstSequence, numPackets = 0;
+
+ long currentTime = System.currentTimeMillis();
+
+ while (numLostPackets > 0) {
+ numPackets++;
+
+ firstSequence = ((lastSequenceReceived + numPackets) < MAX_PACKET_SEQ) ?
+ (lastSequenceReceived + numPackets) :
+ ((lastSequenceReceived + numPackets) - MAX_PACKET_SEQ);
+
+ --numLostPackets;
+
+ timestamps.put(firstSequence, currentTime);
+ keys.add(firstSequence);
+
+ for (bitmaskShift = 0, bitmaskNextSequences = 0;
+ (bitmaskShift < BITMASK_LENGTH) && (numLostPackets > 0);
+ ++bitmaskShift, ++numPackets, --numLostPackets) {
+
+ bitmaskNextSequences |= ((0xffff) & (1 << bitmaskShift));
+
+ int sequence = ((firstSequence + bitmaskShift + 1) < MAX_PACKET_SEQ) ?
+ (firstSequence + bitmaskShift + 1) :
+ ((firstSequence + bitmaskShift + 1) - MAX_PACKET_SEQ);
+
+ timestamps.put(sequence, currentTime);
+ keys.add(sequence);
+ }
+
+ fbInformation.add(
+ new RtcpPacketBuilder.NackFbElement(firstSequence, bitmaskNextSequences));
+ }
+
+ if (fbInformation.size() > 0) {
+ byte[] bytes = RtcpPacketBuilder.buildNackPacket(ssrc, cname,
+ ssrcSender, fbInformation);
+
+ sendMessageFromBytes(bytes, bytes.length);
+ lostPacketPending += numPackets;
+ }
+ }
+
+ @Override
+ public void sendRetransmissionTerminationRequest() throws IOException {
+ byte[] bytes = RtcpPacketBuilder.buildByePacket(ssrc, cname);
+
+ sendMessageFromBytes(bytes, bytes.length);
+ lostPacketPending = 0;
+ }
+ }
+
+ private final class AluRtpDistributionSource extends RtpDistributionSource {
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 2048;
+
+ private final RtpDistributionEventListener eventListener;
+
+ public AluRtpDistributionSource(int socketTimeoutMillis,
+ RtpDistributionEventListener eventListener) {
+ super(socketTimeoutMillis, eventListener);
+
+ this.eventListener = eventListener;
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+ if ((feedbackListener != null) && packet.isExtension() && isAluExtension(packet)) {
+ if (!rtpHeadExtParser.isLoaded()) {
+ rtpHeadExtParser.parseHeader(packet.getHeaderExtension());
+ }
+ }
+
+ if (!ssrcSenderReceived) {
+ ssrcSender = packet.getSsrc();
+ ssrcSenderReceived = true;
+ }
+
+ if (lastSequenceReceived != UNKNOWN_SEQ) {
+ int nextSequence = ((lastSequenceReceived + 1) < MAX_PACKET_SEQ) ?
+ (lastSequenceReceived + 1) : (lastSequenceReceived + 1) - MAX_PACKET_SEQ;
+
+ if (nextSequence != packet.getSequenceNumber()) {
+ int numLostPackets = (packet.getSequenceNumber() > lastSequenceReceived) ?
+ (packet.getSequenceNumber() - lastSequenceReceived) :
+ (MAX_PACKET_SEQ - lastSequenceReceived) + packet.getSequenceNumber() + 1;
+
+ eventListener.onRtpLostPacketDetected(lastSequenceReceived, numLostPackets);
+ }
+ }
+
+ if (!multicastSwitched) {
+ if (isAudioPacket(packet)) {
+ if (firstAudioSequence == UNKNOWN_SEQ) {
+ firstAudioSequence = packet.getSequenceNumber();
+ }
+ } else {
+ if (firstVideoSequence == UNKNOWN_SEQ) {
+ firstVideoSequence = packet.getSequenceNumber();
+ }
+ }
+ }
+
+ packet.setTimestamp(System.currentTimeMillis());
+
+ lastSequenceReceived = packet.getSequenceNumber();
+
+ return true;
+ }
+ }
+
+ /*
+ Alcatel-Lucent RTP Header Extension Format
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 0xbede | length=in 32 bits words |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID=1 | len=3 |B|E| ST|S|r|PRI| FPRI|r| GOP end countdown |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID=4 | len=6 | ??? - payload row index | |
+ +-+-+-+-+-+-+-+-+-------------------------------+ |
+ | ALU/Alu RTP ext type 4 payload row |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID=5 | len=6 | |
+ +-+-+-+-+-+-+-+-+ ALU/Alu RTP ext type 5 payload |
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ID=7 | len=2 | TDEC_90kHz (signed - 90KHz units) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ private final class AluRtpHeaderExtensionParser {
+ private final static int INFO_SIZE = 32;
+
+ private int skip;
+ private int state;
+
+ private boolean begin;
+ private boolean loaded;
+
+ private final int NONE_SEEK = 0;
+ private final int TYPE_SEEK = 1;
+ private final int SIZE_SEEK = 2;
+ private final int CHECKSUM_SEEK = 3;
+ private final int INFO_SEEK = 4;
+
+ private final byte[] info;
+
+ public AluRtpHeaderExtensionParser() {
+ info = new byte[INFO_SIZE];
+ begin = loaded = false;
+ reset();
+ }
+
+ public boolean isLoaded() {
+ return loaded;
+ }
+
+ private void reset() {
+ skip = 0;
+ state = NONE_SEEK;
+ }
+
+ private String toIpAddress(byte[] bytes, int offset) {
+ return (((bytes[offset] & 0xf0) >> 4) * 16 + (bytes[offset] & 0x0f)) + "." +
+ (((bytes[offset+1] & 0xf0) >> 4) * 16 + (bytes[offset+1] & 0x0f)) + "." +
+ (((bytes[offset+2] & 0xf0) >> 4) * 16 + (bytes[offset+2] & 0x0f)) + "." +
+ (((bytes[offset+3] & 0xf0) >> 4) * 16 + (bytes[offset+3] & 0x0f));
+ }
+
+ private int toPortNumber(byte[] bytes, int offset) {
+ return (bytes[offset] & 0xff) * 256 + bytes[offset+1];
+ }
+
+ private void parseId5(byte[] bytes, int offset) {
+ int subtype = bytes[offset] & 0x0f;
+
+ if (begin) {
+
+ String ipAddr = toIpAddress(bytes, offset + 1);
+ int port = toPortNumber(bytes, offset + 5);
+
+ if (subtype == 1) {
+ feedbackListener.onRtpFeedbackEvent(
+ new AluDefaultRtpRetransmissionServerEvent(ipAddr + ":" + port));
+
+ } else if (subtype == 3) {
+ feedbackListener.onRtpFeedbackEvent(
+ new AluDefaultRtpBurstServerEvent(ipAddr + ":" + port));
+ }
+ }
+ }
+
+ private void parseInfo() {
+ String mcastIpAddr = toIpAddress(info, 0);
+ int mcastPort = toPortNumber(info, 4);
+
+ String firstBurstIpAddr = toIpAddress(info, 6);
+ int firstBurstPort = toPortNumber(info, 10);
+
+ String secondBurstIpAddr = toIpAddress(info, 12);
+ int secondBurstPort = toPortNumber(info, 16);
+
+ String firstRetransIpAddr = toIpAddress(info, 18);
+ int firstRetransPort = toPortNumber(info, 22);
+
+ String secondRetransIpAddr = toIpAddress(info, 24);
+ int secondRetransPort = toPortNumber(info, 28);
+
+ feedbackListener.onRtpFeedbackEvent(
+ new AluRtpMulticastGroupInfoEvent(mcastIpAddr + ":" + mcastPort,
+ firstBurstIpAddr + ":" + firstBurstPort,
+ secondBurstIpAddr + ":" + secondBurstPort,
+ firstRetransIpAddr + ":" + firstRetransPort,
+ secondRetransIpAddr + ":" + secondRetransPort));
+ }
+
+ private void parseId4(byte[] bytes, int offset, int length) {
+ int subtype = (bytes[offset] & 0xf0) >> 4;
+ int index = ((bytes[offset] & 0x0f) * 256) + (((bytes[offset+1] & 0xf0) >> 4) * 16) +
+ (bytes[offset+1] & 0x0f);
+
+ int seek = offset + 2; // (skip subtype and row index bytes: 2
+ int total = offset + length;
+
+ if (subtype == 1) {
+
+ if (index == 0) {
+
+ if (begin) {
+
+ loaded = true;
+ feedbackListener.onRtpFeedbackEvent(
+ new AluRtpFeedbackConfigDiscoveryEnded());
+
+ return;
+ }
+
+ begin = true;
+ feedbackListener.onRtpFeedbackEvent(
+ new AluRtpFeedbackConfigDiscoveryStarted());
+ }
+
+ if (begin) {
+
+ while (seek < total) {
+
+ switch (state) {
+
+ case NONE_SEEK: {
+ if ((bytes[seek] & 0xff) == 0x02) {
+ state = TYPE_SEEK;
+ }
+
+ seek++;
+ }
+
+ break;
+
+ case TYPE_SEEK: {
+ if ((bytes[seek] & 0xff) == 0x02) {
+ state = SIZE_SEEK;
+ skip = 2;
+ } else {
+ reset();
+ }
+
+ seek++;
+ }
+
+ break;
+
+ case SIZE_SEEK: {
+ switch (skip) {
+ case 2: {
+ if ((bytes[seek] & 0xff) == 0x00) {
+ skip--;
+ seek++;
+ } else {
+ state = TYPE_SEEK;
+ }
+ }
+
+ break;
+
+ case 1: {
+ if ((bytes[seek] & 0xff) == 0x20) {
+ state = CHECKSUM_SEEK;
+ skip = 2;
+ seek++;
+ } else {
+ reset();
+ }
+ }
+
+ break;
+
+ default: {
+ reset();
+ }
+
+ break;
+ }
+ }
+
+ break;
+
+ case CHECKSUM_SEEK: {
+ switch (skip) {
+ case 2: {
+ skip--;
+ seek++;
+ }
+
+ break;
+
+ case 1: {
+ state = INFO_SEEK;
+ skip = 0;
+ seek++;
+ }
+
+ break;
+
+ default: {
+ reset();
+ }
+
+ break;
+ }
+ }
+
+ break;
+
+ case INFO_SEEK: {
+ if (skip + 1 < INFO_SIZE) {
+ info[skip++] = bytes[seek++];
+
+ } else {
+ info[skip++] = bytes[seek++];
+ parseInfo();
+ reset();
+ return;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public boolean parseHeader(byte[] header) {
+ int seek=4;
+
+ while ((seek < header.length) && !loaded) {
+ int id = (header[seek] >> 4) & 0x0f;
+ int length = header[seek] & 0x0f;
+
+ seek++;
+
+ switch (id) {
+ case 1: {
+ seek+=length;
+ }
+
+ break;
+
+ case 4: {
+ parseId4(header, seek, length + 1);
+ seek+=length+1;
+ }
+
+ break;
+
+ case 5: {
+ parseId5(header, seek);
+ seek+=length+1;
+ }
+
+ break;
+
+ default: {
+ seek+=length+1;
+ }
+
+ break;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public final class AluRtpFeedbackConfigDiscoveryStarted implements RtpFeedbackEvent {
+ }
+
+ public final class AluRtpFeedbackConfigDiscoveryEnded implements RtpFeedbackEvent {
+ }
+
+ public final class AluDefaultRtpBurstServerEvent implements RtpFeedbackEvent {
+
+ private final String burstServer;
+
+ public AluDefaultRtpBurstServerEvent(String burstServer) {
+ this.burstServer = burstServer;
+ }
+
+ public String getBurstServer() {
+ return burstServer;
+ }
+ }
+
+ public final class AluDefaultRtpRetransmissionServerEvent implements RtpFeedbackEvent {
+
+ private final String retransmissionServer;
+
+ public AluDefaultRtpRetransmissionServerEvent(String retransmissionServer) {
+ this.retransmissionServer = retransmissionServer;
+ }
+
+ public String getRetransmissionServer() {
+ return retransmissionServer;
+ }
+ }
+
+ public final class AluRtpMulticastGroupInfoEvent implements RtpFeedbackEvent {
+
+ private final String multicastGroup;
+
+ private final String firstBurstServer;
+ private final String secondBurstServer;
+
+ private final String firstRetransmissionServer;
+ private final String secondRetransmissionServer;
+
+ public AluRtpMulticastGroupInfoEvent(String multicastGroup,
+ String firstBurstServer, String secondBurstServer,
+ String firstRetransmissionServer,
+ String secondRetransmissionServer)
+ {
+ this.multicastGroup = multicastGroup;
+ this.firstBurstServer = firstBurstServer;
+ this.secondBurstServer = secondBurstServer;
+ this.firstRetransmissionServer = firstRetransmissionServer;
+ this.secondRetransmissionServer = secondRetransmissionServer;
+ }
+
+ public String getMulticastGroup() {
+ return multicastGroup;
+ }
+
+ public String getFirstBurstServer() {
+ return firstBurstServer;
+ }
+
+ public String getSecondBurstServer() {
+ return secondBurstServer;
+ }
+
+ public String getFirstRetransmissionServer() {
+ return firstRetransmissionServer;
+ }
+
+ public String getSecondRetransmissionServer() {
+ return secondRetransmissionServer;
+ }
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedbackFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedbackFactory.java
new file mode 100644
index 00000000000..d2ea6e25ff6
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/AluRtpDistributionFeedbackFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+public class AluRtpDistributionFeedbackFactory implements RtpDistributionFeedback.Factory {
+
+ private final long ssrc;
+ private final String cname;
+
+ public AluRtpDistributionFeedbackFactory(long ssrc, String cname) {
+ this.ssrc = ssrc;
+ this.cname = cname;
+ }
+
+ @Override
+ public RtpDistributionFeedback createDistributionFeedback() {
+ return new AluRtpDistributionFeedback(ssrc, cname);
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedback.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedback.java
new file mode 100644
index 00000000000..b82ead96be4
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedback.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+import android.net.Uri;
+
+import com.google.android.exoplayer2.upstream.Loader;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpFeedbackPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpPacketBuilder;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpTokenPacket;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The Default RTP Distribution and Feedback Model implementation based on IETF Standards
+ */
+public final class DefaultRtpDistributionFeedback implements RtpDistributionFeedback {
+
+ private final long ssrc;
+ private final String cname;
+
+ private long ssrcSender;
+ private boolean ssrcSenderReceived;
+
+ // Default socket time-out in milliseconds
+ private static final int BURST_SOURCE_TIMEOUT = 50;
+ private static final int DISTRIBUTION_SOURCE_TIMEOUT = 2000;
+
+ public DefaultRtpDistributionFeedback(long ssrc, String cname) {
+ this.ssrc = ssrc;
+ this.cname = cname;
+ }
+
+ @Override
+ public RtpAuthTokenSource createAuthTokenSource(
+ RtpFeedbackTargetSource.AuthTokenEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new DefaultRtpAuthTokenSource(0, eventListener);
+ }
+
+ @Override
+ public RtpBurstSource createBurstSource(
+ RtpFeedbackTargetSource.BurstEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new DefaultRtpBurstSource(BURST_SOURCE_TIMEOUT, eventListener);
+ }
+
+ @Override
+ public RtpRetransmissionSource createRetransmissionSource(
+ RtpFeedbackTargetSource.RetransmissionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new DefaultRtpRetransmissionSource(eventListener);
+ }
+
+ @Override
+ public RtpDistributionSource createDistributionSource(
+ RtpDistributionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException {
+ return new DefaultRtpDistributionSource(DISTRIBUTION_SOURCE_TIMEOUT, eventListener);
+ }
+
+ @Override
+ public void setFeedbackEventListener(
+ RtpFeedbackEventListener feedbackListener) {
+ // TODO default implementation
+ }
+
+ private final class DefaultRtpAuthTokenSource extends RtpAuthTokenSource {
+ private final byte[] nonce;
+
+ public DefaultRtpAuthTokenSource(int socketTimeoutMillis,
+ RtpFeedbackTargetSource.AuthTokenEventListener
+ eventListener) {
+ super(socketTimeoutMillis, eventListener);
+
+ nonce = new byte[32];
+ new SecureRandom().nextBytes(nonce);
+ }
+
+ @Override
+ protected byte[] getRandomNonce() {
+ return nonce;
+ }
+
+ // AuthTokenMessages standard implementation (IETF RFC 6284)
+ @Override
+ public synchronized void sendAuthTokenRequest() throws IOException {
+ byte[] bytes = RtcpPacketBuilder.buildPortMappingRequestPacket(ssrc, cname,
+ getRandomNonce());
+
+ sendMessageFromBytes(bytes, bytes.length);
+
+ setAuthTokenResponsePending(true);
+ }
+ }
+
+ private final class DefaultRtpBurstSource extends RtpBurstSource {
+
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 1024;
+
+ public DefaultRtpBurstSource(int socketTimeoutMillis,
+ RtpFeedbackTargetSource.BurstEventListener eventListener) {
+ super(socketTimeoutMillis, eventListener);
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ protected boolean isRapidAcquisitionResponse(RtcpFeedbackPacket packet) {
+ // Rapid Acquisition response based in standard implementation (IETF RFC 6285)
+ if ((packet.getPayloadType() == RtcpPacketBuilder.RTCP_RTPFB)
+ && (packet.getFmt() == RtcpPacketBuilder.RTCP_SFMT_RAMS_INFO)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected boolean isRapidAcquisitionAccepted(RtcpFeedbackPacket packet) {
+ // TODO payload decoding based in standard implementation (IETF RFC 6285)
+ // ...
+ // decode the feedback control information (from payload) and
+ // evaluate the previous request
+
+ return true;
+ }
+
+ @Override
+ protected boolean isAuthTokenRejected(RtcpTokenPacket packet) {
+ if ((packet.getPayloadType() == RtcpPacketBuilder.RTCP_TOKEN)
+ && (packet.getSmt() == RtcpPacketBuilder.RTCP_SMT_TOKEN_VERIFY_FAIL)) {
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+ // TODO
+
+ if (!ssrcSenderReceived) {
+ ssrcSender = packet.getSsrc();
+ ssrcSenderReceived = true;
+ }
+
+ return true;
+ }
+
+ // BurstMessages standard implementation (IETF RFC 6285)
+ @Override
+ public void sendBurstRapidAcquisitionRequest(Uri uri) throws IOException {
+ byte[] bytes = RtcpPacketBuilder.buildRamsRequestPacket(ssrc, cname,
+ ssrcSender, new ArrayList(),
+ new ArrayList());
+
+ sendMessageFromBytes(bytes, bytes.length);
+ }
+
+ @Override
+ public void sendBurstTerminationRequest() throws IOException {
+ byte[] bytes = RtcpPacketBuilder.buildRamsTerminationPacket(ssrc, cname,
+ ssrcSender, new ArrayList(),
+ new ArrayList());
+
+ sendMessageFromBytes(bytes, bytes.length);
+ }
+ }
+
+
+ private final class DefaultRtpRetransmissionSource extends RtpRetransmissionSource {
+
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 512;
+
+ private final RtpFeedbackTargetSource.RetransmissionEventListener eventListener;
+
+ public DefaultRtpRetransmissionSource(RtpFeedbackTargetSource.RetransmissionEventListener
+ eventListener) {
+ super(eventListener);
+
+ this.eventListener = eventListener;
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ public void resetAllPacketsRecoveryPending(long timestamp) {
+
+ }
+
+ @Override
+ public int getPacketsRecoveryPending() {
+ return 0;
+ }
+
+ @Override
+ public int getMaxPacketsRecoveryPending() {
+ return 0;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+ // TODO
+ return true;
+ }
+
+ @Override
+ public long getMaxDelayTimeForPending() {
+ return 0;
+ }
+
+ // RetransmissionMessages implementation (IETF RFC 5760, IETF RFC 4585, IETF RFC 1889)
+ @Override
+ public void sendRetransmissionPacketRequest(int lastSequenceReceived, int numLostPackets)
+ throws IOException {
+ // TODO add token into Nack packet when an authentication token is used
+ List fbInformation = new ArrayList();
+
+ int bitmaskNextSequences, bitmaskShift;
+ int firstSequence, numPackets = 0;
+
+ long currentTime = System.currentTimeMillis();
+
+ while (numLostPackets > 0) {
+ numPackets++;
+
+ firstSequence = ((lastSequenceReceived + numPackets) < MAX_PACKET_SEQ) ?
+ (lastSequenceReceived + numPackets) :
+ ((lastSequenceReceived + numPackets) - MAX_PACKET_SEQ);
+
+ --numLostPackets;
+
+ for (bitmaskShift = 0, bitmaskNextSequences = 0;
+ (bitmaskShift < BITMASK_LENGTH) && (numLostPackets > 0);
+ ++bitmaskShift, ++numPackets, --numLostPackets) {
+
+ bitmaskNextSequences |= ((0xffff) & (1 << bitmaskShift));
+
+ int sequence = ((firstSequence + bitmaskShift + 1) < MAX_PACKET_SEQ) ?
+ (firstSequence + bitmaskShift + 1) :
+ ((firstSequence + bitmaskShift + 1) - MAX_PACKET_SEQ);
+ }
+
+ fbInformation.add(
+ new RtcpPacketBuilder.NackFbElement(firstSequence, bitmaskNextSequences));
+ }
+
+ if (fbInformation.size() > 0) {
+ byte[] bytes = RtcpPacketBuilder.buildNackPacket(ssrc, cname,
+ ssrcSender, fbInformation);
+
+ sendMessageFromBytes(bytes, bytes.length);
+ }
+ }
+
+ @Override
+ public void sendRetransmissionTerminationRequest() throws IOException {
+ // TODO add token into Nack packet when an authentication token is used
+ byte[] bytes = RtcpPacketBuilder.buildByePacket(ssrc, cname);
+
+ sendMessageFromBytes(bytes, bytes.length);
+
+ cancelLoad();
+ }
+
+ @Override
+ protected boolean isAuthTokenRejected(RtcpTokenPacket packet) {
+ if ((packet.getPayloadType() == RtcpPacketBuilder.RTCP_TOKEN)
+ && (packet.getSmt() == RtcpPacketBuilder.RTCP_SMT_TOKEN_VERIFY_FAIL)) {
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventListener.onRtpRetransmissionSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ eventListener.onRtpRetransmissionSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+
+ private final class DefaultRtpDistributionSource extends RtpDistributionSource {
+
+ // The maximum buffer capacity, in packets.
+ private static final int MAX_BUFFER_CAPACITY = 2048;
+
+ private final RtpDistributionEventListener eventListener;
+
+ public DefaultRtpDistributionSource(int socketTimeoutMillis,
+ RtpDistributionEventListener eventListener) {
+ super(socketTimeoutMillis, eventListener);
+
+ this.eventListener = eventListener;
+ }
+
+ @Override
+ public int getMaxBufferCapacity() {
+ return MAX_BUFFER_CAPACITY;
+ }
+
+ @Override
+ protected boolean processRtpPacket(RtpPacket packet) {
+ // TODO
+
+ if (!ssrcSenderReceived) {
+ ssrcSender = packet.getSsrc();
+ ssrcSenderReceived = true;
+ }
+
+ return true;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventListener.onRtpDistributionSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ eventListener.onRtpDistributionSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedbackFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedbackFactory.java
new file mode 100644
index 00000000000..437f7e98056
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/DefaultRtpDistributionFeedbackFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+public class DefaultRtpDistributionFeedbackFactory implements RtpDistributionFeedback.Factory {
+
+ private final long ssrc;
+ private final String cname;
+
+ public DefaultRtpDistributionFeedbackFactory(long ssrc, String cname) {
+ this.ssrc = ssrc;
+ this.cname = cname;
+ }
+
+ @Override
+ public RtpDistributionFeedback createDistributionFeedback() {
+ return new DefaultRtpDistributionFeedback(ssrc, cname);
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpDistributionFeedback.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpDistributionFeedback.java
new file mode 100644
index 00000000000..8877deb6faa
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpDistributionFeedback.java
@@ -0,0 +1,1234 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+import android.net.Uri;
+import android.os.ConditionVariable;
+
+import com.google.android.exoplayer2.upstream.Loader;
+import com.google.android.exoplayer2.upstream.RtpDataSource;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpCompoundPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpFeedbackPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpPacketBuilder;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpSdesPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpSrPacket;
+import com.google.android.exoplayer2.util.rtp.rtcp.RtcpTokenPacket;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A RTP Distribution and Feedback Abstract Model interface.
+ */
+public interface RtpDistributionFeedback {
+
+ int UNKNOWN_SEQ = -1; // Unknown sequence
+ int MAX_PACKET_SEQ = 65536;
+
+ /**
+ * The feedback properties
+ */
+ interface Properties {
+ int FB_SCHEME = 1;
+ int FB_VENDOR = 2;
+ int FB_RAMS_URI = 3;
+ int FB_CONGESTION_CONTROL_URI = 4;
+ int FB_PORT_MAPPING_URI = 5;
+ int FB_EVENTS_CALLBACK = 6;
+ }
+
+ /**
+ * The feedback schemes
+ */
+ interface Schemes {
+ int FB_REPORT = 0x01;
+ int FB_RAMS = 0x02;
+ int FB_CONGESTION_CONTROL = 0x04;
+ int FB_PORT_MAPPING = 0x08;
+ }
+
+ /**
+ * The middleware providers (feedback vendors)
+ */
+ interface Providers {
+ int ADTEC_DIGITAL = 1;
+ int ALTICAST = 2;
+ int ALU = 3;
+ int BCC = 4;
+ int BEE_MEDIASOFT = 5;
+ int BEENIUS = 6;
+ int CASCADE = 7;
+ int COMIGO = 8;
+ int CONKLIN_INTRACOM = 9;
+ int CUBIWARE = 10;
+ int DIGISOFT_TV = 11;
+ int EASY_TV = 12;
+ int ERICSSON = 13;
+ int ESPIAL = 14;
+ int HUAWEI = 15;
+ int IKON = 16;
+ int LEV_TV = 17;
+ int MICROSOFT = 18;
+ int MINERVA = 19;
+ int MIRADA = 20;
+ int NANGU_TV = 21;
+ int NETGEM = 22;
+ int NORDIJA = 23;
+ int NOKIA = 24;
+ int OCILION = 25;
+ int QUADRILLE = 26;
+ int SEACHANGE = 27;
+ int SIEMENS = 28;
+ int SMARTLABS = 29;
+ int THOMSON = 30;
+ int TIVO = 31;
+ int UTSTART = 32;
+ int VIANEOS = 33;
+ int ZAPPWARE = 34;
+ int ZENTERIO = 35;
+ int ZTE = 36;
+ }
+
+
+ /**
+ * A factory for {@link RtpDistributionFeedback} instances.
+ */
+ interface Factory {
+ /**
+ * Creates a {@link RtpDistributionFeedback} instance.
+ */
+ RtpDistributionFeedback createDistributionFeedback();
+ }
+
+
+ /**
+ * A distribution and feedback event abstract
+ */
+ interface RtpFeedbackEvent {
+ }
+
+ /**
+ * A event listener for {@link RtpFeedbackEvent} events.
+ */
+ interface RtpFeedbackEventListener {
+ /**
+ * Called when an event has been triggered from distribution and feedback architecture
+ */
+ void onRtpFeedbackEvent(RtpFeedbackEvent event);
+ }
+
+
+ /**
+ * Thrown when an error is encountered when trying to create a {@link RtpFeedbackTarget} or
+ * {@link RtpDistributionSource}.
+ */
+ final class UnsupportedRtpDistributionFeedbackSourceException extends Exception {
+
+ public UnsupportedRtpDistributionFeedbackSourceException(String message) {
+ super(message);
+ }
+
+ public UnsupportedRtpDistributionFeedbackSourceException(Throwable cause) {
+ super(cause);
+ }
+
+ public UnsupportedRtpDistributionFeedbackSourceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ RtpAuthTokenSource createAuthTokenSource(
+ RtpFeedbackTargetSource.AuthTokenEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException;
+
+ RtpBurstSource createBurstSource(RtpFeedbackTargetSource.BurstEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException;
+
+ RtpRetransmissionSource createRetransmissionSource(
+ RtpFeedbackTargetSource.RetransmissionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException;
+
+ RtpDistributionSource createDistributionSource(RtpDistributionEventListener eventListener)
+ throws UnsupportedRtpDistributionFeedbackSourceException;
+
+ void setFeedbackEventListener(RtpFeedbackEventListener eventListener);
+
+
+ final class RtpFeedbackProperties {
+
+ private final Map properties;
+ private Map propertiesSnapshot;
+
+ public RtpFeedbackProperties() {
+ properties = new HashMap<>();
+ }
+
+ /**
+ * Sets the specified property {@code value} for the specified {@code name}. If a property for
+ * this name previously existed, the old value is replaced by the specified value.
+ *
+ * @param id The identifier of the request property.
+ * @param value The value of the request property.
+ */
+ public synchronized void set(Integer id, Object value) {
+ propertiesSnapshot = null;
+ properties.put(id, value);
+ }
+
+ /**
+ * Sets the keys and values contained in the map. If a property previously existed, the old
+ * value is replaced by the specified value. If a property previously existed and is not in the
+ * map, the property is left unchanged.
+ *
+ * @param properties The request properties.
+ */
+ public synchronized void set(Map properties) {
+ propertiesSnapshot = null;
+ properties.putAll(properties);
+ }
+
+ /**
+ * Removes all properties previously existing and sets the keys and values of the map.
+ *
+ * @param properties The request properties.
+ */
+ public synchronized void clearAndSet(Map properties) {
+ propertiesSnapshot = null;
+ properties.clear();
+ properties.putAll(properties);
+ }
+
+ /**
+ * Removes a request property by name.
+ *
+ * @param identifier The identifier of the request property to remove.
+ */
+ public synchronized void remove(Integer identifier) {
+ propertiesSnapshot = null;
+ properties.remove(identifier);
+ }
+
+ /**
+ * Clears all request properties.
+ */
+ public synchronized void clear() {
+ propertiesSnapshot = null;
+ properties.clear();
+ }
+
+ /**
+ * Gets a snapshot of the request properties.
+ *
+ * @return A snapshot of the request properties.
+ */
+ public synchronized Map getSnapshot() {
+ if (propertiesSnapshot == null) {
+ propertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(properties));
+ }
+
+ return propertiesSnapshot;
+ }
+
+ }
+
+
+ interface RtpFeedbackTarget {
+ /**
+ * Thrown when an error is encountered when trying to open/read/write from/to a
+ * {@link RtpFeedbackTarget}.
+ */
+ final class RtpFeedbackTargetException extends IOException {
+
+ public RtpFeedbackTargetException(IOException cause) {
+ super(cause);
+ }
+
+ }
+
+ void open(Uri uri) throws RtpFeedbackTargetException;
+
+ void close();
+
+ Uri getUri();
+
+ /**
+ * Interface for messages to be sent into authentication source.
+ */
+ interface AuthTokenMessages {
+ /**
+ * send a Port Mapping Request packet to Retransmission Server to initialize
+ * the port mapping setup procedure. (sendPortMappingRequest)
+ */
+ void sendAuthTokenRequest() throws IOException;
+ }
+
+ /**
+ * Interface for messages to be sent into burst source.
+ */
+ interface BurstMessages {
+ /**
+ * send a RAMS Request packet to Retransmission Server to initialize
+ * the unicast-based rapid acquisition of multicast procedure. (sendRamsRequest)
+ */
+ void sendBurstRapidAcquisitionRequest(Uri uri) throws IOException;
+
+ /**
+ * send a RAMS Terminate packet to Retransmission Server to finalize the unicast-based
+ * rapid acquisition of multicast procedure. (sendRamsTerminate)
+ *
+ * or send a BYE packet to Retransmission Server to finalize the rtcp feedback.
+ */
+ void sendBurstTerminationRequest() throws IOException;
+ }
+
+ /**
+ * Interface for messages to be sent into retransmission source.
+ */
+ interface RetransmissionMessages {
+ /**
+ * send a Generic Negative Acknowledgement packet to Retransmission Server to notify a
+ * packet loss was detected. (sendNack)
+ */
+ void sendRetransmissionPacketRequest(int lastSequenceReceived,
+ int numLostPackets) throws IOException;
+
+ /**
+ * send a BYE packet to Retransmission Server to finalize the rtcp feedback.
+ */
+ void sendRetransmissionTerminationRequest() throws IOException;
+ }
+ }
+
+ interface RtpFeedbackTargetSource {
+
+ /**
+ * Interface for callbacks to be notified from Authentication Token Source.
+ */
+ interface AuthTokenEventListener {
+ /**
+ * Called when a Port Mapping Response packet has been received from Retransmission
+ * Server. (onPortMappingResponse)
+ */
+ void onAuthTokenResponse();
+
+ /**
+ * Called when a Port Mapping response packet has not received from Retransmission
+ * Server within a specified timeframe. (onPortMappingResponseBeforeTimeout)
+ */
+ void onAuthTokenResponseBeforeTimeout();
+
+ /**
+ * Called when an error occurs decoding Port Mapping response packet received from
+ * Retransmission Server (onPortMappingResponseBeforeError)
+ */
+ void onAuthTokenResponseBeforeError();
+
+ /**
+ * Called when the Port Mapping response packet received from
+ * Retransmission Server (onPortMappingResponseBeforeError) is unexpected
+ */
+ void onAuthTokenResponseUnexpected();
+
+ /**
+ * Called when a RTP Authentication Token source encounters an error.
+ */
+ void onRtpAuthTokenSourceError();
+
+ /**
+ * Called when a RTP Authentication Token source has been canceled.
+ */
+ void onRtpAuthTokenSourceCanceled();
+ }
+
+ /**
+ * Interface for callbacks to be notified from Burst Source.
+ */
+ interface BurstEventListener {
+
+ /**
+ * Called when a RAMS Information packet has been accepted by Retransmission Server.
+ */
+ void onBurstRapidAcquisitionAccepted();
+
+ /**
+ * Called when a RAMS Information packet has been rejected by Retransmission Server.
+ */
+ void onBurstRapidAcquisitionRejected();
+
+
+ /**
+ * Called when an multicast join signaling is detected that requires the RTP_Rx
+ * to be notified to send SFGMP Join message.
+ */
+ void onMulticastJoinSignal();
+
+ /**
+ * Called when the rapid acquisition has been completed.
+ */
+ void onBurstRapidAcquisitionCompleted();
+
+ /**
+ * Called when a Rams Information packet has not received from Retransmission Server
+ * within a specified timeframe.
+ */
+ void oBurstRapidAcquisitionResponseBeforeTimeout();
+
+ /**
+ * Called when the Retransmission Server has detected that token is invalid or has expired.
+ * It is only applied when the por mapping mechanism is supported
+ */
+ void onInvalidToken();
+
+ /**
+ * Called when a RTP packet burst has been received.
+ */
+ void onRtpPacketBurstReceived(RtpPacket packet);
+
+ /**
+ * Called when a RTP Burst Source encounters an error.
+ */
+ void onRtpBurstSourceError();
+
+ /**
+ * Called when a RTP Burst source has been canceled.
+ */
+ void onRtpBurstSourceCanceled();
+ }
+
+ /**
+ * Interface for callbacks to be notified from Retransmission Source.
+ */
+ interface RetransmissionEventListener {
+
+ /**
+ * Called when the Retransmission Server has detected that token is invalid or has expired.
+ * It is only applied when the por mapping mechanism is supported
+ */
+ void onInvalidToken();
+
+ /**
+ * Called when a RTP packet loss has been received.
+ */
+ void onRtpPacketLossReceived(RtpPacket packet);
+
+ /**
+ * Called when a RTP Retransmission Source encounters an error.
+ */
+ void onRtpRetransmissionSourceError();
+
+ /**
+ * Called when a RTP Retransmission source has been canceled.
+ */
+ void onRtpRetransmissionSourceCanceled();
+ }
+ }
+
+ /**
+ * Interface for callbacks to be notified from Distribution Source.
+ */
+ interface RtpDistributionEventListener {
+ /**
+ * Called when a RTP packet has been received.
+ */
+ void onRtpPacketReceived(RtpPacket packet);
+
+ /**
+ * Called when a RTP lost packet has been detected.
+ */
+ void onRtpLostPacketDetected(int lastSequenceReceived, int numLostPackets);
+
+ /**
+ * Called when a RTP Distribution source encounters an error.
+ */
+ void onRtpDistributionSourceError();
+
+ /**
+ * Called when a RTP Distribution source has been canceled.
+ */
+ void onRtpDistributionSourceCanceled();
+ }
+
+ abstract class RtpAuthTokenSource implements RtpFeedbackTarget,
+ RtpFeedbackTarget.AuthTokenMessages,
+ RtcpCompoundPacket.RtcpCompoundPacketEventListener,
+ Loader.Loadable,
+ Loader.Callback {
+
+ private Uri uri;
+
+ private byte[] inBuffer;
+ private byte[] outBuffer;
+
+ protected DatagramSocket socket;
+ private final DatagramPacket inPacket;
+ protected final DatagramPacket outPacket;
+
+ private InetAddress address;
+ private InetSocketAddress socketAddress;
+
+ private final int socketTimeoutMillis;
+
+ private final ConditionVariable loadCondition;
+ private volatile boolean loadCanceled = false;
+
+ private RtpFeedbackTargetSource.AuthTokenEventListener eventListener;
+
+ private byte[] token;
+ private long expirationTime;
+
+ private boolean authReponsePending = false;
+
+ public RtpAuthTokenSource(int socketTimeoutMillis,
+ RtpFeedbackTargetSource.AuthTokenEventListener eventListener) {
+
+ this.eventListener = eventListener;
+ this.socketTimeoutMillis = socketTimeoutMillis;
+
+ this.loadCondition = new ConditionVariable(false);
+
+ this.inBuffer = new byte[RtpDataSource.MTU_SIZE];
+ this.outBuffer = new byte[RtpDataSource.MTU_SIZE];
+
+ this.inPacket = new DatagramPacket(this.inBuffer, 0, RtpDataSource.MTU_SIZE);
+ this.outPacket = new DatagramPacket(this.outBuffer, 0, RtpDataSource.MTU_SIZE);
+ }
+
+ public final byte[] getToken() { return token; }
+
+ public final long getExpirationTime() { return expirationTime; }
+
+ abstract byte[] getRandomNonce();
+
+ protected void sendMessageFromBytes(byte[] bytes, int length) throws IOException {
+ outPacket.setData(bytes, 0, length);
+ socket.send(outPacket);
+
+ loadCondition.open();
+ }
+
+ public final void setAuthTokenResponsePending(boolean state) {
+ authReponsePending = state;
+ }
+
+
+ // RtpFeedbackTarget implementation
+ @Override
+ public void open(Uri uri) throws RtpFeedbackTargetException {
+ this.uri = Assertions.checkNotNull(uri);
+
+ String host = uri.getHost();
+ int port = uri.getPort();
+
+ try {
+
+ address = InetAddress.getByName(host);
+ socketAddress = new InetSocketAddress(address, port);
+
+ socket = new DatagramSocket();
+ socket.connect(socketAddress);
+
+ } catch (IOException e) {
+ throw new RtpFeedbackTargetException(e);
+ }
+
+ try {
+
+ socket.setSoTimeout(socketTimeoutMillis);
+
+ } catch (SocketException e) {
+ throw new RtpFeedbackTargetException(e);
+ }
+ }
+
+ @Override
+ public Uri getUri() {
+ return uri;
+ }
+
+
+ // RtcpCompoundPacketEventListener implementation
+ @Override
+ public void onSenderReportPacket(RtcpSrPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onSourceDescriptionPacket(RtcpSdesPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onRtpFeedbackPacket(RtcpFeedbackPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTokenPacket(RtcpTokenPacket packet) {
+ if ((packet.getPayloadType() == RtcpPacketBuilder.RTCP_TOKEN)
+ && (packet.getSmt() == RtcpPacketBuilder.RTCP_SMT_PORT_MAPPING_RESP)) {
+ this.token = packet.getTokenElement();
+ this.expirationTime = packet.getRelativeExpirationTime();
+
+ authReponsePending = false;
+ eventListener.onAuthTokenResponse();
+
+ } else {
+ eventListener.onAuthTokenResponseUnexpected();
+ }
+ }
+
+ private void handlePacket(byte[] buffer, int length)
+ throws RtcpCompoundPacket.RtcpCompoundPacketException {
+ RtcpCompoundPacket compoundPacket = new RtcpCompoundPacket(this);
+ compoundPacket.fromBytes(buffer, length);
+ }
+
+
+ // Loader.Loadable implementation
+ @Override
+ public void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @Override
+ public boolean isLoadCanceled() {
+ return loadCanceled;
+ }
+
+ @Override
+ public void load() throws IOException, InterruptedException {
+ while (!loadCanceled) {
+
+ try {
+
+ loadCondition.block();
+
+ if (authReponsePending) {
+ socket.receive(inPacket);
+ handlePacket(inPacket.getData(), inPacket.getLength());
+ }
+
+ } catch (RtcpCompoundPacket.RtcpCompoundPacketException e) {
+
+ if (authReponsePending) {
+ authReponsePending = false;
+ eventListener.onAuthTokenResponseBeforeError();
+ }
+
+ } catch (SocketTimeoutException e) {
+ authReponsePending = false;
+ eventListener.onAuthTokenResponseBeforeTimeout();
+ }
+
+ if (authReponsePending) {
+ authReponsePending = false;
+ eventListener.onAuthTokenResponseBeforeError();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+
+ if (socket != null) {
+ socket.close();
+ socket = null;
+ }
+
+ loadCondition.close();
+
+ } catch (Exception e) { }
+
+ address = null;
+ socketAddress = null;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpAuthTokenSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpAuthTokenSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventListener.onRtpAuthTokenSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpAuthTokenSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ eventListener.onRtpAuthTokenSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+
+ abstract class RtpBurstSource implements RtpFeedbackTarget,
+ RtpFeedbackTarget.BurstMessages,
+ RtcpCompoundPacket.RtcpCompoundPacketEventListener,
+ Loader.Loadable,
+ Loader.Callback {
+
+ private Uri uri;
+
+ private byte[] inBuffer;
+ private byte[] outBuffer;
+
+ private DatagramSocket socket;
+ private final DatagramPacket inPacket;
+ private final DatagramPacket outPacket;
+
+ private InetAddress address;
+ private InetSocketAddress socketAddress;
+
+ private final int socketTimeoutMillis;
+
+ private volatile boolean loadCanceled = false;
+
+ private final RtpFeedbackTargetSource.BurstEventListener eventListener;
+
+ public RtpBurstSource(int socketTimeoutMillis,
+ RtpFeedbackTargetSource.BurstEventListener eventListener) {
+
+ this.eventListener = eventListener;
+ this.socketTimeoutMillis = socketTimeoutMillis;
+
+ this.inBuffer = new byte[RtpDataSource.MTU_SIZE];
+ this.outBuffer = new byte[RtpDataSource.MTU_SIZE];
+
+ this.inPacket = new DatagramPacket(this.inBuffer, 0, RtpDataSource.MTU_SIZE);
+ this.outPacket = new DatagramPacket(this.outBuffer, 0, RtpDataSource.MTU_SIZE);
+ }
+
+ protected void sendMessageFromBytes(byte[] bytes, int length) throws IOException {
+ outPacket.setData(bytes, 0, length);
+ socket.send(outPacket);
+ }
+
+ abstract public int getMaxBufferCapacity();
+
+ abstract boolean isRapidAcquisitionResponse(RtcpFeedbackPacket packet);
+
+ abstract boolean isRapidAcquisitionAccepted(RtcpFeedbackPacket packet);
+
+ abstract boolean isAuthTokenRejected(RtcpTokenPacket packet);
+
+ abstract protected boolean processRtpPacket(RtpPacket packet);
+
+ @Override
+ public void open(Uri uri) throws RtpFeedbackTargetException {
+ this.uri = Assertions.checkNotNull(uri);
+
+ String host = uri.getHost();
+ int port = uri.getPort();
+
+ try {
+
+ address = InetAddress.getByName(host);
+ socketAddress = new InetSocketAddress(address, port);
+
+ socket = new DatagramSocket();
+ socket.connect(socketAddress);
+
+ } catch (IOException e) {
+ throw new RtpFeedbackTargetException(e);
+ }
+
+ try {
+
+ socket.setSoTimeout(socketTimeoutMillis);
+
+ } catch (SocketException e) {
+ throw new RtpFeedbackTargetException(e);
+ }
+ }
+
+ @Override
+ public Uri getUri() {
+ return uri;
+ }
+
+
+ // RtcpCompoundPacketEventListener implementation
+ @Override
+ public void onSenderReportPacket(RtcpSrPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onSourceDescriptionPacket(RtcpSdesPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onRtpFeedbackPacket(RtcpFeedbackPacket packet) {
+ if (isRapidAcquisitionResponse(packet)) {
+ if (isRapidAcquisitionAccepted(packet)) {
+ eventListener.onBurstRapidAcquisitionAccepted();
+ } else {
+ eventListener.onBurstRapidAcquisitionRejected();
+ }
+ }
+ }
+
+ @Override
+ public void onTokenPacket(RtcpTokenPacket packet) {
+ if (isAuthTokenRejected(packet)) {
+ eventListener.onInvalidToken();
+ }
+ }
+
+ private void handlePacket(byte[] buffer, int length)
+ throws RtcpCompoundPacket.RtcpCompoundPacketException {
+ try {
+
+ RtpPacket rtpPacket = new RtpPacket();
+ rtpPacket.fromBytes(buffer, length);
+
+ if (processRtpPacket(rtpPacket)) {
+ eventListener.onRtpPacketBurstReceived(rtpPacket);
+ }
+
+ } catch (RtpPacket.RtpPacketException ex) {
+
+ RtcpCompoundPacket packet = new RtcpCompoundPacket(this);
+ packet.fromBytes(buffer, length);
+ }
+ }
+
+ // Loader.Loadable implementation
+ @Override
+ public void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @Override
+ public boolean isLoadCanceled() {
+ return loadCanceled;
+ }
+
+ @Override
+ public void load() throws IOException, InterruptedException {
+ while (!loadCanceled) {
+
+ try {
+
+ if (!loadCanceled) {
+ socket.receive(inPacket);
+ handlePacket(inPacket.getData(), inPacket.getLength());
+ }
+
+ } catch (SocketTimeoutException se) {
+ throw new SocketTimeoutException(se.getMessage());
+
+ } catch (RtcpCompoundPacket.RtcpCompoundPacketException pe) {
+ // Do nothing
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+
+ if (socket != null) {
+ socket.close();
+ socket = null;
+ }
+
+ } catch (Exception e) { }
+
+ address = null;
+ socketAddress = null;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpBurstSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpBurstSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+
+ eventListener.onRtpBurstSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpBurstSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+
+ eventListener.onRtpBurstSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+
+ abstract class RtpRetransmissionSource implements RtpFeedbackTarget,
+ RtpFeedbackTarget.RetransmissionMessages,
+ RtcpCompoundPacket.RtcpCompoundPacketEventListener,
+ Loader.Loadable,
+ Loader.Callback {
+
+ private Uri uri;
+
+ private byte[] inBuffer;
+ private byte[] outBuffer;
+
+ private DatagramSocket socket;
+ private final DatagramPacket inPacket;
+ private final DatagramPacket outPacket;
+
+ private InetAddress address;
+ private InetSocketAddress socketAddress;
+
+ private volatile boolean loadCanceled = false;
+
+ protected static final int BITMASK_LENGTH = 16;
+
+ private final RtpFeedbackTargetSource.RetransmissionEventListener eventListener;
+
+ public RtpRetransmissionSource(RtpFeedbackTargetSource.RetransmissionEventListener
+ eventListener) {
+ this.eventListener = eventListener;
+
+ this.inBuffer = new byte[RtpDataSource.MTU_SIZE];
+ this.outBuffer = new byte[RtpDataSource.MTU_SIZE];
+
+ this.inPacket = new DatagramPacket(this.inBuffer, 0, RtpDataSource.MTU_SIZE);
+ this.outPacket = new DatagramPacket(this.outBuffer, 0, RtpDataSource.MTU_SIZE);
+ }
+
+ @Override
+ public void open(Uri uri) throws RtpFeedbackTargetException {
+ this.uri = Assertions.checkNotNull(uri);
+
+ String host = uri.getHost();
+ int port = uri.getPort();
+
+ try {
+
+ address = InetAddress.getByName(host);
+ socketAddress = new InetSocketAddress(address, port);
+
+ socket = new DatagramSocket();
+ socket.connect(socketAddress);
+
+ } catch (IOException e) {
+ throw new RtpFeedbackTargetException(e);
+ }
+ }
+
+ @Override
+ public Uri getUri() {
+ return uri;
+ }
+
+ abstract public int getMaxBufferCapacity();
+
+ abstract public void resetAllPacketsRecoveryPending(long timestamp);
+
+ abstract public int getPacketsRecoveryPending();
+
+ abstract public int getMaxPacketsRecoveryPending();
+
+ abstract protected boolean processRtpPacket(RtpPacket packet);
+
+ abstract boolean isAuthTokenRejected(RtcpTokenPacket packet);
+
+ abstract public long getMaxDelayTimeForPending();
+
+ protected void sendMessageFromBytes(byte[] bytes, int length) throws IOException {
+ outPacket.setData(bytes, 0, length);
+ socket.send(outPacket);
+ }
+
+ // RtcpCompoundPacketEventListener implementation
+ @Override
+ public void onSenderReportPacket(RtcpSrPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onSourceDescriptionPacket(RtcpSdesPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onRtpFeedbackPacket(RtcpFeedbackPacket packet) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTokenPacket(RtcpTokenPacket packet) {
+ if (isAuthTokenRejected(packet)) {
+ eventListener.onInvalidToken();
+ }
+ }
+
+ private void handlePacket(byte[] buffer, int length)
+ throws RtcpCompoundPacket.RtcpCompoundPacketException {
+ try {
+
+ RtpPacket rtpPacket = new RtpPacket();
+ rtpPacket.fromBytes(buffer, length);
+
+ if (processRtpPacket(rtpPacket)) {
+ eventListener.onRtpPacketLossReceived(rtpPacket);
+ }
+
+ } catch (RtpPacket.RtpPacketException ex) {
+
+ RtcpCompoundPacket packet = new RtcpCompoundPacket(this);
+ packet.fromBytes(buffer, length);
+ }
+ }
+
+ // Loader.Loadable implementation
+ @Override
+ public void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @Override
+ public boolean isLoadCanceled() {
+ return loadCanceled;
+ }
+
+ @Override
+ public void load() throws IOException, InterruptedException {
+ while (!loadCanceled) {
+
+ try {
+
+ if (!loadCanceled) {
+ socket.receive(inPacket);
+ handlePacket(inPacket.getData(), inPacket.getLength());
+ }
+
+ } catch (RtcpCompoundPacket.RtcpCompoundPacketException e) {
+ // Do nothing
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+
+ if (socket != null) {
+ socket.close();
+ socket = null;
+ }
+
+ } catch (Exception e) { }
+
+ address = null;
+ socketAddress = null;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventListener.onRtpRetransmissionSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpRetransmissionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ eventListener.onRtpRetransmissionSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+
+ abstract class RtpDistributionSource implements Loader.Loadable,
+ Loader.Callback {
+ private Uri uri;
+
+ private byte[] inBuffer;
+
+ private DatagramSocket socket;
+ private MulticastSocket mcastSocket;
+ private final DatagramPacket inPacket;
+
+ private InetAddress address;
+ private InetSocketAddress socketAddress;
+
+ private volatile boolean loadCanceled = false;
+
+ private final int socketTimeoutMillis;
+ private final RtpDistributionEventListener eventListener;
+
+ /**
+ * Thrown when an error is encountered when trying to open/read from a
+ * {@link RtpDistributionSource}.
+ */
+ final class RtpDistributionSourceException extends IOException {
+
+ public RtpDistributionSourceException(IOException cause) {
+ super(cause);
+ }
+
+ }
+
+ public RtpDistributionSource(int socketTimeoutMillis,
+ RtpDistributionEventListener eventListener) {
+ this.eventListener = eventListener;
+ this.socketTimeoutMillis = socketTimeoutMillis;
+
+ this.inBuffer = new byte[RtpDataSource.MTU_SIZE];
+ this.inPacket = new DatagramPacket(this.inBuffer, 0, RtpDataSource.MTU_SIZE);
+ }
+
+ public void open(Uri uri) throws RtpDistributionSourceException {
+ this.uri = Assertions.checkNotNull(uri);
+
+ String host = uri.getHost();
+ int port = uri.getPort();
+
+ try {
+
+ address = InetAddress.getByName(host);
+ socketAddress = new InetSocketAddress(address, port);
+
+ if (address.isMulticastAddress()) {
+ mcastSocket = new MulticastSocket(socketAddress);
+ mcastSocket.joinGroup(address);
+ socket = mcastSocket;
+ } else {
+ socket = new DatagramSocket();
+ socket.connect(socketAddress);
+ }
+
+ } catch (IOException e) {
+ throw new RtpDistributionSourceException(e);
+ }
+
+ try {
+
+ socket.setSoTimeout(socketTimeoutMillis);
+
+ } catch (SocketException e) {
+ throw new RtpDistributionSourceException(e);
+ }
+ }
+
+ abstract public int getMaxBufferCapacity();
+
+ abstract protected boolean processRtpPacket(RtpPacket packet);
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ private void handlePacket(byte[] buffer, int length) throws RtpPacket.RtpPacketException {
+ RtpPacket rtpPacket = new RtpPacket();
+ rtpPacket.fromBytes(buffer, length);
+
+ if (processRtpPacket(rtpPacket)) {
+ eventListener.onRtpPacketReceived(rtpPacket);
+ }
+ }
+
+ // Loader.Loadable implementation
+ @Override
+ public final void cancelLoad() {
+ loadCanceled = true;
+ }
+
+ @Override
+ public final boolean isLoadCanceled() {
+ return loadCanceled;
+ }
+
+ @Override
+ public final void load() throws IOException, InterruptedException {
+ while (!loadCanceled) {
+
+ try {
+
+ if (!loadCanceled) {
+ socket.receive(inPacket);
+ handlePacket(inPacket.getData(), inPacket.getLength());
+ }
+
+ } catch (SocketTimeoutException se) {
+ throw new SocketTimeoutException(se.getMessage());
+
+ } catch (RtpPacket.RtpPacketException e) {
+ // Do nothing
+ }
+ }
+ }
+
+ public void close() {
+ try {
+
+ if (socket != null) {
+ socket.close();
+ socket = null;
+ }
+
+ } catch (Exception e) { }
+
+ address = null;
+ socketAddress = null;
+ }
+
+ // Loader.Callback implementation
+ @Override
+ public void onLoadCompleted(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLoadCanceled(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, boolean released) {
+ eventListener.onRtpDistributionSourceCanceled();
+ }
+
+ @Override
+ public int onLoadError(RtpDistributionSource loadable, long elapsedRealtimeMs,
+ long loadDurationMs, IOException error) {
+ eventListener.onRtpDistributionSourceError();
+ return Loader.DONT_RETRY;
+ }
+ }
+}
\ No newline at end of file
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpExtractorsFactory.java
new file mode 100644
index 00000000000..c39f8a31cd6
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpExtractorsFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+
+import com.google.android.exoplayer2.extractor.Extractor;
+import com.google.android.exoplayer2.extractor.ExtractorsFactory;
+import com.google.android.exoplayer2.extractor.ts.TsExtractor;
+
+import static com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES;
+
+/**
+ * An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
+ *
+ *
+ * - MPEG TS ({@link TsExtractor})
+ *
+ */
+public final class RtpExtractorsFactory implements ExtractorsFactory {
+
+ @Override
+ public synchronized Extractor[] createExtractors() {
+ Extractor[] extractors = new Extractor[1];
+ extractors[0] = new TsExtractor(FLAG_ALLOW_NON_IDR_KEYFRAMES);
+ return extractors;
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacket.java
new file mode 100644
index 00000000000..37b8d997464
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacket.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+
+/**
+ * This class wraps a RTP packet providing method to convert from a byte array or individual
+ * setter methods.
+ *
+ * A RTP packet is composed of an header and the subsequent payload. It has the following format:
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |V=2|P|X| CC |M| PT | sequence number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | timestamp |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | synchronization source (SSRC) identifier |
+ * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ * | contributing source (CSRC) identifiers |
+ * | .... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * The first twelve octets are present in every RTP packet, while the list of
+ * CSRC identifiers is present only when inserted by a mixer.
+ *
+ */
+public class RtpPacket {
+
+ private static final String LOG_TAG = RtpPacket.class.getSimpleName();
+
+ private static final int MPEG_TS_SIG = 0x47;
+ private static final int RTP_MIN_SIZE = 4;
+ private static final int RTP_HDR_SIZE = 12; /* RFC 3550 */
+ private static final int RTP_VERSION = 0x02;
+
+ /* offset to header extension and extension length,
+ * as per RFC 3550 5.3.1 */
+ private static final int XTLEN_OFFSET = 14;
+ private static final int XTSIZE = 4;
+
+ private static final int RTP_XTHDRLEN = XTLEN_OFFSET + XTSIZE;
+
+ private static final int CSRC_SIZE = 4;
+
+ /* MPEG payload-type constants */
+ public static final int RTP_MPA_TYPE = 0x0E; // MPEG-1 and MPEG-2 audio
+ public static final int RTP_MPV_TYPE = 0x20; // MPEG-1 and MPEG-2 video
+ public static final int RTP_MP2TS_TYPE = 0x21; // MPEG TS
+ public static final int RTP_DYN_TYPE = 0x63; // MPEG TS
+
+ //Fields that compose the RTP header
+ private int version;
+ private boolean padding;
+ private boolean extension;
+ private int csrcCount;
+
+ private boolean marker;
+ private int sequenceNumber;
+ private int payloadType;
+
+ private long timestamp;
+ private long ssrc;
+
+ private long[] csrc;
+
+ //bitstream of the RTP header extension
+ private byte[] headerExtension;
+
+ //bitstream of the RTP payload
+ private byte[] payload;
+
+ private int packetSize;
+
+ /**
+ * Thrown when an error is encountered when trying to parse from a {@link RtpPacket}.
+ */
+ public static final class RtpPacketException extends Exception {
+
+ public RtpPacketException(String message) {
+ super(message);
+ }
+
+ public RtpPacketException(Throwable cause) {
+ super(cause);
+ }
+
+ public RtpPacketException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+
+ public RtpPacket() {
+ // Fill default fields
+ version = 2;
+ padding = true;
+ marker = true;
+ ssrc = 0;
+ csrcCount = 0;
+ timestamp = 0;
+ }
+
+ public RtpPacket(int payloadType, int sequenceNumber, long timestamp, long ssrc,
+ byte[] payload, @Nullable byte[] headerExtension) {
+ this();
+
+ this.payloadType = payloadType;
+ this.sequenceNumber = sequenceNumber;
+ this.timestamp = timestamp;
+ this.ssrc = ssrc;
+ this.payload = payload;
+
+ this.extension = !(headerExtension == null);
+ this.headerExtension = headerExtension;
+ }
+
+ public RtpPacket(byte[] buffer, int length) throws RtpPacketException {
+ fromBytes(buffer, length);
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public boolean isPadding() {
+ return padding;
+ }
+
+ public void setPadding(boolean padding) {
+ this.padding = padding;
+ }
+
+ public boolean isExtension() {
+ return extension;
+ }
+
+ public void setExtension(boolean extension) {
+ this.extension = extension;
+ }
+
+ public int getCsrcCount() {
+ return csrcCount;
+ }
+
+ public void setCsrcCount(int csrcCount) {
+ this.csrcCount = csrcCount;
+ }
+
+ public boolean isMarker() {
+ return marker;
+ }
+
+ public void setMarker(boolean marker) {
+ this.marker = marker;
+ }
+
+ public int getPayloadType() {
+ return payloadType;
+ }
+
+ public void setPayloadType(int payloadType) {
+ this.payloadType = payloadType;
+ }
+
+ public int getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ public void setSequenceNumber(int sequenceNumber) {
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public long getTimeStamp() {return timestamp; }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public long getSsrc() {return ssrc; }
+
+ public void setSsrc(long ssrc) {
+ this.ssrc = ssrc;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public byte[] getHeaderExtension() {
+ return headerExtension;
+ }
+
+ public void setHeaderExtension(byte[] headerExtension) {
+ this.headerExtension = headerExtension;
+ }
+
+ public byte[] getPayload() { return payload; }
+
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+
+ public int getPacketSize() {
+ return packetSize;
+ }
+
+ public void setPacketSize(int packetSize) {
+ this.packetSize = packetSize;
+ }
+
+
+ // Decode a RTP packet from bytes
+ public void fromBytes(byte[] buffer, int length) throws RtpPacketException {
+ int padLen = 0, headLen = 0, extLen = 0;
+ int frontSkip = 0, backSkip = 0;
+
+ if( (buffer.length < RTP_MIN_SIZE) || (buffer.length < RTP_HDR_SIZE) ) {
+ throw new RtpPacketException("Inappropriate length=[" + buffer.length + "] of RTP packet");
+ }
+
+ // Read the packet header
+ version = (buffer[0] & 0xC0) >> 6;
+
+ if (RTP_VERSION != version) {
+ throw new RtpPacketException("Wrong RTP version " + version + ", must be " +
+ RTP_VERSION);
+ }
+
+ padding = ((buffer[0] & 0x20) >> 5) == 1;
+ extension = ((buffer[0] & 0x10) >> 4) == 1;
+ csrcCount = buffer[0] & 0x0F;
+
+ headLen += RTP_HDR_SIZE + (CSRC_SIZE * csrcCount);
+
+ marker = ((buffer[1] & 0x80) >> 7) == 1;
+ payloadType = buffer[1] & 0x7F;
+
+ /* profile-based skip: adopted from vlc 0.8.6 code */
+ if ((RtpPacket.RTP_MPA_TYPE == payloadType) || (RtpPacket.RTP_MPV_TYPE == payloadType)) {
+ headLen += 4;
+ } else if ((RtpPacket.RTP_MP2TS_TYPE != payloadType) && (RtpPacket.RTP_DYN_TYPE != payloadType)) {
+ throw new RtpPacketException("Unsupported payload type " + payloadType);
+ }
+
+ frontSkip += headLen;
+
+ if (padding) {
+ padLen = buffer[length - 1];
+ backSkip += padLen;
+ }
+
+ if (length < (frontSkip + backSkip)) {
+ throw new RtpPacketException("Invalid header (skip "
+ + (frontSkip + backSkip) + " exceeds packet length " + length);
+ }
+
+ if (extension) {
+ if (buffer.length < RTP_XTHDRLEN) {
+ throw new RtpPacketException("RTP x-header requires " + (XTLEN_OFFSET + 1) +
+ " bytes, only " + buffer.length + " provided");
+ }
+
+ extLen = XTSIZE +
+ (Integer.SIZE/Byte.SIZE) * ((buffer[XTLEN_OFFSET] << 8) + buffer[XTLEN_OFFSET + 1]);
+
+ frontSkip += extLen;
+ }
+
+ //Payload Type: 33 MPEG 2 TS
+ if (payloadType == RtpPacket.RTP_MP2TS_TYPE) {
+ sequenceNumber = (buffer[2] & 0xff) * 256 + (buffer[3] & 0xff);
+ } else {
+ //Payload Type: DynamicRTP-Type 99 MPEG2TS with sequence number preceded.
+ sequenceNumber = (buffer[frontSkip]&0xff)*256+(buffer[frontSkip+1]&0xff);
+ frontSkip += 2;
+ }
+
+ timestamp = ((buffer[4] & 0xff) << 24) | ((buffer[5] & 0xff) << 16) |
+ ((buffer[6] & 0xff) << 8) | (buffer[7] & 0xff);
+
+ ssrc = ((buffer[8] & 0xff) << 24) | ((buffer[9] & 0xff) << 16) |
+ ((buffer[10] & 0xff) << 8) | (buffer[11] & 0xff);
+
+ // CSRC list
+ if (csrcCount > 0) {
+ csrc = new long[csrcCount];
+
+ for (int i=0, pos=12; i < csrcCount; i++, pos+=4) {
+ csrc[i] = ((buffer[pos] & 0xff) << 24) | ((buffer[pos+1] & 0xff) << 16) |
+ ((buffer[pos+2] & 0xff) << 8) | (buffer[pos+3] & 0xff);
+ }
+ }
+
+ // Read the extension header if present
+ if (extension) {
+ headerExtension = new byte[extLen];
+ System.arraycopy(buffer, headLen, headerExtension, 0, extLen);
+ }
+
+ // Read the payload
+ payload = new byte[length-frontSkip];
+ System.arraycopy(buffer, frontSkip, payload, 0, length-frontSkip);
+
+ packetSize = length;
+ }
+}
\ No newline at end of file
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacketQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacketQueue.java
new file mode 100644
index 00000000000..3577681b541
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/RtpPacketQueue.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 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.util.rtp;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * A circular buffer queue for RTP packets
+ */
+public class RtpPacketQueue {
+
+ private final static int MAX_SEGMENT_SIZE = 1316;
+
+ private final ByteBuffer buffer;
+ private final RtpPacketQueueItem[] packets;
+
+ private final int capacity;
+
+ private int front;
+ private int back;
+
+ private long total;
+
+ private class RtpPacketQueueItem {
+
+ public RtpPacket packet;
+ public int length;
+ public int offset;
+
+ public RtpPacketQueueItem() {
+ this.packet = null;
+ this.length = 0;
+ this.offset = 0;
+ }
+
+ public void reset() {
+ this.packet = null;
+ length = 0;
+ offset = 0;
+ }
+ }
+
+ public RtpPacketQueue(int capacity) {
+
+ this.capacity = capacity;
+
+ total = front = back = 0;
+
+ buffer = ByteBuffer.allocate(MAX_SEGMENT_SIZE * capacity);
+
+ packets = new RtpPacketQueueItem[capacity];
+
+ for (int i = 0; i < capacity; i++)
+ packets[i] = new RtpPacketQueueItem();
+ }
+
+ public synchronized void reset() {
+ front = back = 0;
+ total = 0;
+
+ buffer.rewind();
+
+ for (int i = 0; i < capacity; i++)
+ packets[i].reset();
+ }
+
+ public synchronized int push(RtpPacket packet) throws BufferUnderflowException {
+ if ((packet != null) && (packets[back].length == 0)) {
+
+ int length = packet.getPayload().length;
+
+ packets[back].packet = packet;
+ packets[back].length = length;
+ packets[back].offset = 0;
+
+ buffer.position(back * MAX_SEGMENT_SIZE);
+ buffer.put(packet.getPayload());
+
+ total+=length;
+
+ back = (back + 1) % capacity;
+
+ return length;
+ }
+
+ return -1;
+ }
+
+ public synchronized int get(byte[] data, int offset, int length) throws BufferUnderflowException {
+ int size = 0;
+
+ if ((length > 0) && (packets[front].length > 0)) {
+
+ size = Math.min(length, packets[front].length);
+
+ if (packets[front].length >= size) {
+
+ buffer.position((front * MAX_SEGMENT_SIZE) + packets[front].offset);
+ buffer.get(data, offset, size);
+
+ packets[front].length -= size;
+
+ if (packets[front].length == 0) {
+ packets[front].reset();
+ front = (front + 1) % capacity;
+
+ } else {
+ packets[front].offset += size;
+ }
+
+ total-=size;
+ }
+ }
+
+ return size;
+ }
+
+ public synchronized RtpPacket front() {
+ return packets[front].packet;
+ }
+
+ public synchronized boolean isDataAvailable() { return (total > 0); }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpCompoundPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpCompoundPacket.java
new file mode 100644
index 00000000000..ec965e63d24
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpCompoundPacket.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+/**
+ * This class wraps a RTCP compound packet providing method to convert from a byte array and obtain
+ * the RTCP packets are composed
+ *
+ */
+public class RtcpCompoundPacket {
+
+ public interface RtcpCompoundPacketEventListener {
+ /**
+ * Called when a Sender Report packet has been found while parsing.
+ */
+ void onSenderReportPacket(RtcpSrPacket packet);
+
+ /**
+ * Called when a Source Description packet has been found while parsing.
+ */
+ void onSourceDescriptionPacket(RtcpSdesPacket packet);
+
+ /**
+ * Called when a Generic RTP Feedback packet has been found while parsing.
+ */
+ void onRtpFeedbackPacket(RtcpFeedbackPacket packet);
+
+ /**
+ * Called when a TOKEN packet has been found while parsing.
+ */
+ void onTokenPacket(RtcpTokenPacket packet);
+ }
+
+ private RtcpPacket packets[];
+
+ private final RtcpCompoundPacketEventListener eventListener;
+
+ /**
+ * Thrown when an error is encountered when trying to decode a {@link RtcpCompoundPacket}.
+ */
+ public static final class RtcpCompoundPacketException extends Exception {
+
+ public RtcpCompoundPacketException(String message) {
+ super(message);
+ }
+
+ public RtcpCompoundPacketException(Throwable cause) {
+ super(cause);
+ }
+
+ public RtcpCompoundPacketException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public RtcpCompoundPacket(RtcpCompoundPacketEventListener eventListener) {
+ this.eventListener = eventListener;
+ }
+
+ public RtcpPacket[] getPackets() {
+ return packets;
+ }
+
+ // Decode a RTCP compound packet from bytes
+ public void fromBytes(byte[] buffer, int length) throws RtcpCompoundPacketException {
+ // TODO the implementation (invoking listener for each rtcp compound event
+ }
+}
+
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpFeedbackPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpFeedbackPacket.java
new file mode 100644
index 00000000000..d5fdb3d5cac
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpFeedbackPacket.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+public class RtcpFeedbackPacket extends RtcpPacket {
+
+ protected void decodePayload(byte[] payload, int length) {
+ // TODO the implementation
+ }
+
+ public int getFmt() {
+ return 0;
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacket.java
new file mode 100644
index 00000000000..939f31be2e6
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacket.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+/**
+ * This class wraps a RTCP packet providing method to convert from and to a byte array.
+ *
+ * A RCTP packet is composed of an header and the subsequent payload. It has the following format:
+ *
+ /*
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |V=2|P| RC | PT | length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | SSRC of sender |
+ * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ * : :
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * The first eight octets are present in every RTCP packet.
+ *
+ */
+abstract public class RtcpPacket {
+
+ private static final int RTCP_VERSION = 0x02;
+
+ private static final int RTCP_HDR_SIZE = 8; /* RFC 3550 */
+
+ // fields that compose the RTCP header
+ private int version;
+ private boolean padding;
+ private int receptionCount;
+ private int payloadType;
+ private int length;
+ private long ssrc;
+
+ private byte[] payload;
+
+ private int packetSize;
+
+ /**
+ * Thrown when an error is encountered when trying to parse a {@link RtcpPacket}.
+ */
+ public static final class RtcpPacketException extends Exception {
+
+ public RtcpPacketException(String message) {
+ super(message);
+ }
+
+ public RtcpPacketException(Throwable cause) {
+ super(cause);
+ }
+
+ public RtcpPacketException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+
+ public RtcpPacket() {
+ // Fill default fields
+ version = 2;
+ padding = true;
+ receptionCount = 0;
+ ssrc = 0;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public boolean isPadding() {
+ return padding;
+ }
+
+ public int getReceptionCount() {
+ return receptionCount;
+ }
+
+ public int getPayloadType() {
+ return payloadType;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public long getSsrc() {
+ return ssrc;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public int getPacketSize() {
+ return packetSize;
+ }
+
+ // Decode a RTCP packet from bytes
+ public void fromBytes(byte[] buffer, int length) throws RtcpPacketException {
+ int padLen = 0, headLen = RTCP_HDR_SIZE;
+ int frontSkip = 0, backSkip = 0;
+
+ if (buffer.length < RTCP_HDR_SIZE) {
+ throw new RtcpPacketException("Inappropriate length=[" + buffer.length + "] of RTCP packet");
+ }
+
+ // Read the packet header
+ this.version = (buffer[0] & 0xC0) >> 6;
+
+ if (RTCP_VERSION != this.version) {
+ throw new RtcpPacketException("Wrong RTCP version " + this.version + ", must be " +
+ RTCP_VERSION);
+ }
+
+ this.padding = ((buffer[0] & 0x20) >> 5) == 1;
+ this.receptionCount = buffer[0] & 0x1F;
+ this.payloadType = buffer[1] & 0xFF;
+ this.length = (buffer[2] & 0xff) * 256 + (buffer[3] & 0xff);
+
+ this.ssrc = ((buffer[4] & 0xff) << 24) | ((buffer[5] & 0xff) << 16) |
+ ((buffer[6] & 0xff) << 8) | (buffer[7] & 0xff);
+
+ frontSkip += headLen;
+
+ if (padding) {
+ padLen = buffer[length - 1];
+ backSkip += padLen;
+ }
+
+ if (length < (frontSkip + backSkip)) {
+ throw new RtcpPacketException("Invalid header (skip "
+ + (frontSkip + backSkip) + " exceeds packet length " + length);
+ }
+
+ // we have already read 2 * 4 = 8 bytes
+ // out of ( length + 1 ) * 4 totals
+ int size = Math.min(((this.length + 1) * 4 - RTCP_HDR_SIZE), length-frontSkip);
+
+ // Read the payload
+ this.payload = new byte[size];
+ System.arraycopy(buffer, frontSkip, payload, 0, size);
+
+ decodePayload(payload, size);
+
+ this.packetSize = length;
+ }
+
+ abstract void decodePayload(byte[] payload, int length);
+
+ public byte[] toBytes() { return null; }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketBuilder.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketBuilder.java
new file mode 100644
index 00000000000..97f33181e3a
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketBuilder.java
@@ -0,0 +1,1323 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+import android.support.annotation.Nullable;
+import android.util.SparseIntArray;
+
+import java.util.List;
+
+/**
+ * This class provides generic packet assembly and building functions for
+ * RTCP packets
+ */
+public class RtcpPacketBuilder {
+
+ private static final byte VERSION = 2;
+ private static final byte PADDING = 0;
+
+ // Payload Types
+ public static final int RTCP_SR = (int) 200;
+ public static final int RTCP_RR = (int) 201;
+ public static final int RTCP_SDES = (int) 202;
+ public static final int RTCP_BYE = (int) 203;
+ public static final int RTCP_APP = (int) 204;
+
+ // Extended RTP for Real-time Transport Control Protocol Based Feedback
+ public static final int RTCP_RTPFB = (int) 205;
+ public static final int RTCP_PSFB = (int) 206;
+
+ // Extended RTP for Real-time Transport Control Protocol Based Port Mapping between Unicast
+ // and Multicast RTP Sessions
+ public static final int RTCP_TOKEN = (int) 210;
+
+ public static final int RTCP_SMT_PORT_MAPPING_REQ = (int) 1;
+ public static final int RTCP_SMT_PORT_MAPPING_RESP = (int) 2;
+ public static final int RTCP_SMT_TOKEN_VERIFY_REQ = (int) 3;
+ public static final int RTCP_SMT_TOKEN_VERIFY_FAIL = (int) 4;
+
+ public static final int RTCP_SFMT_RAMS_REQ = (int) 1;
+ public static final int RTCP_SFMT_RAMS_INFO = (int) 2;
+ public static final int RTCP_SFMT_RAMS_TERM = (int) 3;
+
+ private static final int RTCP_RAMS_TLV_SSRC = (int) 1;
+
+ private static final byte RTCP_SDES_END = (byte) 0;
+ private static final byte RTCP_SDES_CNAME = (byte) 1;
+ private static final byte RTCP_SDES_NAME = (byte) 2;
+ private static final byte RTCP_SDES_EMAIL = (byte) 3;
+ private static final byte RTCP_SDES_PHONE = (byte) 4;
+ private static final byte RTCP_SDES_LOC = (byte) 5;
+ private static final byte RTCP_SDES_TOOL = (byte) 6;
+ private static final byte RTCP_SDES_NOTE = (byte) 7;
+ private static final byte RTCP_SDES_PRIV = (byte) 8;
+
+
+ public static class NackFbElement {
+ private int pid;
+ private int blp;
+
+ public NackFbElement(int pid, int blp) {
+ this.pid = pid;
+ this.blp = blp;
+ }
+
+ public int getPid() { return pid; }
+
+ public int getBlp() { return blp; }
+ }
+
+ /**
+ * A {@link TlvElement} represents each entry in a TLV formatted byte-array.
+ *
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Reserved | Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Value :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ public static class TlvElement {
+ /**
+ * The Type (T) field of the current TLV element. Note that for LV
+ * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
+ * this field is undefined.
+ */
+ private long type;
+ /**
+ * The Length (L) field of the current TLV element.
+ */
+ private long length;
+ /**
+ * The Value (V) field - a raw byte array representing the current TLV
+ * element
+ */
+ private byte[] value;
+
+ public TlvElement(long type, long length, @Nullable byte[] value) {
+ this.type = type;
+ this.length = length;
+ this.value = value;
+ }
+
+ public long getType() { return type; }
+
+ public long getLength() { return length; }
+
+ public byte[] getValue() { return value; }
+ }
+
+
+ /**
+ * A {@link PrivateExtension} represents each entry in a Private Extension on TLV formatted
+ * byte-array.
+ *
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Reserved | Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Enterprise Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Value :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ public static class PrivateExtension extends TlvElement {
+ /**
+ * The Enterprise Number (V) field of the current TLV element.
+ */
+ private long enterpriseNumber;
+
+ public PrivateExtension(long type, long length, @Nullable byte[] value,
+ long enterpriseNumber) {
+ super(type, length, value);
+
+ this.enterpriseNumber = enterpriseNumber;
+ }
+
+ public long getEnterpriseNumber() { return enterpriseNumber; }
+ }
+
+ /**
+ * Assembly a Receiver Report RTCP Packet.
+ *
+ * @param ssrc
+ * @return byte[] The Receiver Report Packet
+ */
+ private static byte[] assembleRTCPReceiverReportPacket(long ssrc) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| RC | PT=RR=201 | length | header
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of sender |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SSRC_1 (SSRC of first source) | report
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
+ | fraction lost | cumulative number of packets lost | 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | extended highest sequence number received |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | interarrival jitter |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | last SR (LSR) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | delay since last SR (DLSR) |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SSRC_2 (SSRC of second source) | report
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
+ : ... : 2
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | profile-specific extensions |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+
+ // construct the first byte containing V, P and RC
+ byte V_P_RC;
+ V_P_RC = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x00)
+ // take only the right most 5 bytes i.e.
+ // 00011111 = 0x1F
+ );
+
+ // SSRC of sender
+ byte[] ss = RtcpPacketUtils.longToBytes(ssrc, 4);
+
+ // Payload Type = RR
+ byte[] pt =
+ RtcpPacketUtils.longToBytes((long) RTCP_RR, 1);
+
+ byte[] receptionReportBlocks =
+ new byte [0];
+
+ /* TODO
+ receptionReportBlocks =
+ RtcpPacketUtils.append(receptionReportBlocks,
+ assembleRTCPReceptionReport());*/
+
+ // Each reception report is 24 bytes, so calculate the number of
+ // sources in the reception report block and update the reception
+ // block count in the header
+ byte receptionReports = (byte) (receptionReportBlocks.length / 24);
+
+ // Reset the RC to reflect the number of reception report blocks
+ V_P_RC = (byte) (V_P_RC | (byte) (receptionReports & 0x1F));
+
+ byte[] length =
+ RtcpPacketUtils.longToBytes(((FIXED_HEADER_SIZE + ss.length +
+ receptionReportBlocks.length)/4)-1, 2);
+
+ byte[] packet = new byte [1];
+ packet[0] = V_P_RC;
+ packet = RtcpPacketUtils.append(packet, pt);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ss);
+
+ /*
+ TODO
+ packet = RtcpPacketUtils.append(packet, receptionReportBlocks);
+ */
+
+ return packet;
+ }
+
+ /**
+ * Assembly an Source Description SDES RTCP Packet.
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @return The SDES Packet
+ */
+ private static byte[] assembleRTCPSourceDescriptionPacket(long ssrc, String cname) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| SC | PT=SDES=202 | length |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SSRC/CSRC_1 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SDES items |
+ | ... |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SSRC/CSRC_2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SDES items |
+ | ... |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and SC
+ byte v_p_sc;
+ v_p_sc = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x01));
+
+ byte[] pt =
+ RtcpPacketUtils.longToBytes ((long) RTCP_SDES, 1);
+
+ /////////////////////// Chunk 1 ///////////////////////////////
+ byte[] ss =
+ RtcpPacketUtils.longToBytes ((long) ssrc, 4);
+
+
+ ////////////////////////////////////////////////
+ // SDES Item #1 :CNAME
+ /* 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | CNAME=1 | length | user and domain name ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ byte item = RTCP_SDES_CNAME;
+ byte[] user_and_domain = new byte[cname.length()];
+ user_and_domain = cname.getBytes();
+
+
+ // Copy the CName item related fields
+ byte[] cnameHeader = { item, (byte) user_and_domain.length };
+
+ // Append the header and CName Information in the SDES Item Array
+ byte[] sdesItem = new byte[0] ;
+ sdesItem = RtcpPacketUtils.append (sdesItem, cnameHeader);
+ sdesItem = RtcpPacketUtils.append (sdesItem, user_and_domain);
+
+ int padLen = RtcpPacketUtils.calculatePadLength(sdesItem.length);
+
+ // Determine the length of the packet (section 6.4.1 "The length of
+ // the RTCP packet in 32 bit words minus one, including the header and
+ // any padding")
+ byte[] sdesLength = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ss.length + sdesItem.length + padLen + 4)/4)-1, 2);
+
+ // Assemble all the info into a packet
+ byte[] packet = new byte[2];
+
+ packet[0] = v_p_sc;
+ packet[1] = pt[0];
+ packet = RtcpPacketUtils.append(packet, sdesLength);
+ packet = RtcpPacketUtils.append(packet, ss);
+ packet = RtcpPacketUtils.append(packet, sdesItem);
+
+ if (padLen > 0) {
+ // Append necessary padding fields
+ byte[] padBytes = new byte[padLen];
+ packet = RtcpPacketUtils.append(packet, padBytes);
+ }
+
+ // Append SDES Item end field (32 bit boundary)
+ byte[] sdesItemEnd = new byte [4];
+ packet = RtcpPacketUtils.append (packet, sdesItemEnd);
+
+ return packet;
+ }
+
+ /**
+ *
+ * Assembly a "BYE" packet (PT=BYE=203)
+ *
+ * @param ssrc The sincronization source
+ * @return The BYE Packet
+ *
+ */
+ private static byte[] assembleRTCPByePacket(long ssrc) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| SC | PT=BYE=203 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC/CSRC |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : ... :
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | length | reason for leaving ... (opt)
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and SC
+ byte V_P_SC;
+ V_P_SC = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x01)
+ );
+
+ // Generate the payload type byte
+ byte PT[] = RtcpPacketUtils.longToBytes((long) RTCP_BYE, 1);
+
+ // Generate the SSRC
+ byte ss[] = RtcpPacketUtils.longToBytes((long) ssrc, 4);
+
+ byte textLength [] = RtcpPacketUtils.longToBytes(0 , 1);
+
+ // Length of the packet is number of 32 byte words - 1
+ byte[] length =
+ RtcpPacketUtils.longToBytes(((FIXED_HEADER_SIZE + ss.length)/4)-1, 2);
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_SC;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ss);
+ packet = RtcpPacketUtils.append(packet, textLength);
+
+ return packet;
+ }
+
+ /**
+ *
+ * Assembly a "APP" packet (PT=APP=204)
+ *
+ * @param ssrc The sincronization source
+ * @param name The application name
+ * @param appData The application-dependent data
+ * @return The APP Packet
+ *
+ */
+ private static byte[] assembleRTCPAppPacket(long ssrc, String name,
+ byte[] appData) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| subtype | PT=APP=204 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC/CSRC |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | name (ASCII) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | application-dependent data ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and SC
+ byte V_P_SC;
+ V_P_SC = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x00));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_APP, 1);
+
+ // Generate the SSRC
+ byte[] ss = RtcpPacketUtils.longToBytes((long) ssrc, 4);
+
+ // Generate the application name
+ byte[] appName = name.getBytes();
+
+ int dataLen = appName.length + appData.length;
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ss.length + dataLen + 2)/4)-1, 2);
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_SC;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ss);
+
+ packet = RtcpPacketUtils.append(packet, appName);
+ packet = RtcpPacketUtils.append(packet, appData);
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Transport layer Feedback (Generic NACK) "RTPFB" packet (PT=RTPFB=205)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param ssrcSource The sincronization source
+ * @param fbInformation The RTP sequence number array of the lost packets and the bitmask
+ * of the lost packets immediately following the RTP packet indicated
+ * by the pid
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPNackPacket(long ssrcSender, long ssrcSource,
+ List fbInformation) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| FMT=1 | PT=205 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of media source |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | PID | BLP |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and FMT
+ byte V_P_FMT;
+ V_P_FMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x01));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_RTPFB, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ // Generate the SSRC media source
+ byte[] ssms = RtcpPacketUtils.longToBytes((long) ssrcSource, 4);
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + ssms.length + (fbInformation.size()*4) + 2)/4)-1, 2);
+
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_FMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+ packet = RtcpPacketUtils.append(packet, ssms);
+
+ // Generate the feedback control information (FCI)
+ for (int index = 0; index < fbInformation.size(); index++) {
+
+ NackFbElement fbElement = fbInformation.get(index);
+
+ // Generate the PID
+ byte[] pid = RtcpPacketUtils.longToBytes((long) fbElement.getPid(), 2);
+
+ // Generate the BLP
+ byte[] blp = RtcpPacketUtils.longToBytes((long) fbElement.getPid(), 2);
+
+ packet = RtcpPacketUtils.append(packet, pid);
+ packet = RtcpPacketUtils.append(packet, blp);
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Transport layer Feedback (RAMS Request) "RTPFB" packet (PT=RTPFB=205)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param ssrcSource The sincronization source
+ * @param extensions The optional TLV elements
+ * @param privateExtensions The optional private extensions
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPRamsRequestPacket(long ssrcSender, long ssrcSource,
+ List extensions,
+ List privateExtensions) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| FMT=6 | PT=205 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of media source |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SFMT=1 | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Requested Media Sender SSRC(s) :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Optional TLV-encoded Fields (and Padding, if needed) :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_FCI_SIZE = 8; // 8 bytes (4 + 4 bytes mandatory TLV element fixed to 0 length)
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and FMT
+ byte V_P_FMT;
+ V_P_FMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x06));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_RTPFB, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ // Generate the SSRC media source
+ byte[] ssms = RtcpPacketUtils.longToBytes((long) ssrcSource, 4);
+
+ // Length of the feedback control information (FCI) is composed of fixed and variable values
+ int var_fci_size = 0;
+
+ for (int index = 0; index < extensions.size(); index++) {
+ var_fci_size += extensions.get(index).getLength();
+ }
+
+ for (int index = 0; index < privateExtensions.size(); index++) {
+ var_fci_size += privateExtensions.get(index).getLength();
+ }
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + ssms.length + (FIXED_FCI_SIZE + var_fci_size) + 2)/4)-1, 2);
+
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_FMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+ packet = RtcpPacketUtils.append(packet, ssms);
+
+ // Generate the feedback control information (FCI)
+
+ // Generate the sub fmt type byte
+ byte[] SFMT = RtcpPacketUtils.longToBytes((long) RTCP_SFMT_RAMS_REQ, 1);
+
+ // Generate the reserved byte
+ byte[] reserved = RtcpPacketUtils.longToBytes((long) 0, 3);
+
+ // Generate the requested media senders byte
+ // (Mandatory TLV element: Length field set to 0 bytes means requesting to rapidly acquire channel)
+ byte[] tlv_type = RtcpPacketUtils.longToBytes((long) RTCP_RAMS_TLV_SSRC, 1);
+ byte[] tlv_reserved = RtcpPacketUtils.longToBytes((long) 0, 1);
+ byte[] tlv_length = RtcpPacketUtils.longToBytes((long) 0, 2);
+
+ packet = RtcpPacketUtils.append(packet, SFMT);
+ packet = RtcpPacketUtils.append(packet, reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_length);
+
+ for (int index = 0; index < extensions.size(); index++) {
+ TlvElement tlvElement = extensions.get(index);
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes((long) tlvElement.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes((long) tlvElement.getLength(), 2);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlvElement.getValue());
+ }
+
+ for (int index = 0; index < privateExtensions.size(); index++) {
+ PrivateExtension privateExtension = privateExtensions.get(index);
+
+ /**
+ * Represents a TLV formatted byte-array with Private Extensions.
+ *
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Reserved | Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Enterprise Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Value :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes(privateExtension.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes(privateExtension.getLength(), 2);
+ byte[] enterpriseCode = RtcpPacketUtils.longToBytes(privateExtension.getEnterpriseNumber(), 4);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, enterpriseCode);
+ packet = RtcpPacketUtils.append(packet, privateExtension.getValue());
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Transport layer Feedback (RAMS Termination) "RTPFB" packet (PT=BYE=205)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param ssrcSource The sincronization source
+ * @param extensions The optional TLV elements
+ * @param privateExtensions The optional private extensions
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPRamsTerminationPacket(long ssrcSender, long ssrcSource,
+ List extensions,
+ List privateExtensions) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| FMT=6 | PT=205 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of media source |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | SFMT=3 | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Optional TLV-encoded Fields (and Padding, if needed) :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and FMT
+ byte V_P_FMT;
+ V_P_FMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x06));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_RTPFB, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ // Generate the SSRC media source
+ byte[] ssms = RtcpPacketUtils.longToBytes((long) ssrcSource, 4);
+
+
+ // Length of the feedback control information (FCI) is composed of fixed and variable values
+ int var_fci_size = 0;
+
+ for (int index = 0; index < extensions.size(); index++) {
+ var_fci_size += extensions.get(index).getLength();
+ }
+
+ for (int index = 0; index < privateExtensions.size(); index++) {
+ var_fci_size += privateExtensions.get(index).getLength();
+ }
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + ssms.length + (var_fci_size) + 2)/4)-1, 2);
+
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_FMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+ packet = RtcpPacketUtils.append(packet, ssms);
+
+ // Generate the feedback control information (FCI)
+
+ // Generate the sub fmt type byte
+ byte[] SFMT = RtcpPacketUtils.longToBytes((long) RTCP_SFMT_RAMS_TERM, 1);
+
+ // Generate the reserved byte
+ byte[] reserved = RtcpPacketUtils.longToBytes((long) 0, 3);
+
+ // Generate the optional tlv elements
+ byte[] tlv_reserved = RtcpPacketUtils.longToBytes((long) 0, 1);
+
+ packet = RtcpPacketUtils.append(packet, SFMT);
+ packet = RtcpPacketUtils.append(packet, reserved);
+
+ for (int index = 0; index < extensions.size(); index++) {
+ TlvElement tlvElement = extensions.get(index);
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes((long) tlvElement.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes((long) tlvElement.getLength(), 2);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlvElement.getValue());
+ }
+
+ for (int index = 0; index < privateExtensions.size(); index++) {
+ PrivateExtension privateExtension = privateExtensions.get(index);
+
+ /**
+ * Represents a TLV formatted byte-array with Private Extensions.
+ *
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Reserved | Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Enterprise Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Value :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes(privateExtension.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes(privateExtension.getLength(), 2);
+ byte[] enterpriseCode = RtcpPacketUtils.longToBytes(privateExtension.getEnterpriseNumber(), 4);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, enterpriseCode);
+ packet = RtcpPacketUtils.append(packet, privateExtension.getValue());
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Lack of Synch Indication (LSI) "RTPFB" packet (PT=205, FMT=2)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param ssrcSource The sincronization source
+ * @param bitrate The maximum bitrate of RTP stream it can accommodate
+ * @param extensions The optional extended parameters encoded using TLV elements
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPLackSynchIndicationPacket(long ssrcSender, long ssrcSource,
+ long bitrate,
+ List extensions) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| FMT=2 | PT=205 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of media source |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | Bitrate |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Extensions |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and FMT
+ byte V_P_FMT;
+ V_P_FMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x02));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_RTPFB, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ // Generate the SSRC media source
+ byte[] ssms = RtcpPacketUtils.longToBytes((long) ssrcSource, 4);
+
+ // Generate the SSRC media source
+ byte[] br = RtcpPacketUtils.longToBytes((long) bitrate, 8);
+
+ // Length of the feedback control information (FCI) is composed of fixed and variable values
+ int var_fci_size = 0;
+
+ for (int index = 0; index < extensions.size(); index++) {
+ var_fci_size += extensions.get(index).getLength();
+ }
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + ssms.length + (br.length + var_fci_size) + 2)/4)-1, 2);
+
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_FMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+ packet = RtcpPacketUtils.append(packet, ssms);
+
+ // Generate the feedback control information (FCI)
+
+ // Generate the sub fmt type byte
+ byte[] SFMT = RtcpPacketUtils.longToBytes((long) RTCP_SFMT_RAMS_REQ, 1);
+
+ // Generate the reserved byte
+ byte[] reserved = RtcpPacketUtils.longToBytes((long) 0, 3);
+
+ // Generate the requested media senders byte
+ // (Mandatory TLV element: Length field set to 0 bytes means requesting to rapidly acquire channel)
+
+ byte[] tlv_type = RtcpPacketUtils.longToBytes((long) RTCP_RAMS_TLV_SSRC, 1);
+ byte[] tlv_reserved = RtcpPacketUtils.longToBytes((long) 0, 1);
+ byte[] tlv_length = RtcpPacketUtils.longToBytes((long) 0, 2);
+ byte[] tlv_value = RtcpPacketUtils.longToBytes((long) 0, 4);
+
+ packet = RtcpPacketUtils.append(packet, SFMT);
+ packet = RtcpPacketUtils.append(packet, reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlv_value);
+
+ for (int index = 0; index < extensions.size(); index++) {
+ TlvElement tlvElement = extensions.get(index);
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes((long) tlvElement.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes((long) tlvElement.getLength(), 2);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlvElement.getValue());
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Synch Completed Indication (SCI) "RTPFB" packet (PT=205, FMT=4)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param ssrcSource The sincronization source
+ * @param extensions The optional extended parameters encoded using TLV elements
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPSynchCompletedIndicationPacket(long ssrcSender, long ssrcSource,
+ List extensions) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| FMT=4 | PT=205 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of media source |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | Extensions |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and FMT
+ byte V_P_FMT;
+ V_P_FMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x04));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_RTPFB, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ // Generate the SSRC media source
+ byte[] ssms = RtcpPacketUtils.longToBytes((long) ssrcSource, 4);
+
+ // Length of the feedback control information (FCI) is only composed of variable values
+ int var_fci_size = 0;
+
+ for (int index = 0; index < extensions.size(); index++) {
+ var_fci_size += extensions.get(index).getLength();
+ }
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + ssms.length + var_fci_size + 2)/4)-1, 2);
+
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_FMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+ packet = RtcpPacketUtils.append(packet, ssms);
+
+ // Generate the feedback control information (FCI)
+
+ // Generate the sub fmt type byte
+ byte[] SFMT = RtcpPacketUtils.longToBytes((long) RTCP_SFMT_RAMS_REQ, 1);
+
+ // Generate the reserved byte
+ byte[] reserved = RtcpPacketUtils.longToBytes((long) 0, 3);
+
+ // Generate the requested media senders byte
+ // (Mandatory TLV element: Length field set to 0 bytes means requesting to rapidly acquire channel)
+
+ byte[] tlv_type = RtcpPacketUtils.longToBytes((long) RTCP_RAMS_TLV_SSRC, 1);
+ byte[] tlv_reserved = RtcpPacketUtils.longToBytes((long) 0, 1);
+ byte[] tlv_length = RtcpPacketUtils.longToBytes((long) 0, 2);
+ byte[] tlv_value = RtcpPacketUtils.longToBytes((long) 0, 4);
+
+ packet = RtcpPacketUtils.append(packet, SFMT);
+ packet = RtcpPacketUtils.append(packet, reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlv_value);
+
+ for (int index = 0; index < extensions.size(); index++) {
+ TlvElement tlvElement = extensions.get(index);
+
+ byte[] opt_tlv_type = RtcpPacketUtils.longToBytes((long) tlvElement.getType(), 1);
+ byte[] opt_tlv_length = RtcpPacketUtils.longToBytes((long) tlvElement.getLength(), 2);
+
+ packet = RtcpPacketUtils.append(packet, opt_tlv_type);
+ packet = RtcpPacketUtils.append(packet, tlv_reserved);
+ packet = RtcpPacketUtils.append(packet, opt_tlv_length);
+ packet = RtcpPacketUtils.append(packet, tlvElement.getValue());
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Port Mapping Request packet (PT=TOKEN=210)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param nonce The random nonce
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPPortMappingRequestPacket(long ssrcSender, byte[] nonce) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| SMT=1 | PT=210 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Random |
+ | Nonce |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and SFMT
+ byte V_P_SMT;
+ V_P_SMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x01));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_TOKEN, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ int padLen = RtcpPacketUtils.calculate64PadLength(nonce.length);
+
+ byte[] length = RtcpPacketUtils.longToBytes (((FIXED_HEADER_SIZE +
+ ssps.length + nonce.length + padLen + 2)/4)-1, 2);
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte [1];
+
+ packet[0] = V_P_SMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+
+ packet = RtcpPacketUtils.append(packet, nonce);
+
+ if (padLen > 0) {
+ // Append necessary padding fields
+ byte[] padBytes = new byte[padLen];
+ packet = RtcpPacketUtils.append(packet, padBytes);
+ }
+
+ return packet;
+ }
+
+
+ /**
+ *
+ * Assembly a Token Verification Request packet (PT=TOKEN=210)
+ *
+ * @param ssrcSender The sincronization source of sender
+ * @param nonce The random nonce
+ * @param token The authentication token (received into Port Mapping Response packet)
+ * @param expirationTime The expiration time for authentication token
+ * @return The RTPFB Packet
+ *
+ */
+ private static byte[] assembleRTCPTokenVerificationRequestPacket(long ssrcSender, byte[] nonce,
+ byte[] token,
+ long expirationTime) {
+ /*
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| SMT=3 | PT=210 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Random |
+ | Nonce |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : Token Element :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Associated Absolute |
+ | Expiration Time |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final int FIXED_HEADER_SIZE = 4; // 4 bytes
+ // construct the first byte containing V, P and SFMT
+ byte V_P_SMT;
+ V_P_SMT = (byte) ((VERSION << 6) |
+ (PADDING << 5) |
+ (0x03));
+
+ // Generate the payload type byte
+ byte[] PT = RtcpPacketUtils.longToBytes((long) RTCP_TOKEN, 1);
+
+ // Generate the SSRC packet sender
+ byte[] ssps = RtcpPacketUtils.longToBytes((long) ssrcSender, 4);
+
+ int noncePadLen = RtcpPacketUtils.calculate64PadLength(nonce.length);
+
+ byte[] expiration = RtcpPacketUtils.longToBytes((long) expirationTime, 8);
+
+ int tokenPadLen = RtcpPacketUtils.calculatePadLength(token.length);
+
+ byte[] length = RtcpPacketUtils.longToBytes(((FIXED_HEADER_SIZE +
+ ssps.length + nonce.length + noncePadLen + token.length + tokenPadLen +
+ expiration.length + 2) / 4) - 1, 2);
+
+ ///////////////////////// Packet Construction ///////////////////////////////
+ byte packet[] = new byte[1];
+
+ packet[0] = V_P_SMT;
+ packet = RtcpPacketUtils.append(packet, PT);
+ packet = RtcpPacketUtils.append(packet, length);
+ packet = RtcpPacketUtils.append(packet, ssps);
+
+ packet = RtcpPacketUtils.append(packet, nonce);
+
+ if (noncePadLen > 0) {
+ // Append necessary padding fields
+ byte[] padBytes = new byte[noncePadLen];
+ packet = RtcpPacketUtils.append(packet, padBytes);
+ }
+
+ packet = RtcpPacketUtils.append(packet, token);
+
+ if (tokenPadLen > 0) {
+ // Append necessary padding fields
+ byte[] padBytes = new byte[tokenPadLen];
+ packet = RtcpPacketUtils.append(packet, padBytes);
+ }
+
+ packet = RtcpPacketUtils.append(packet, expiration);
+
+ return packet;
+ }
+
+
+ //************************************************************************
+ // IETF RFC 3350 - RTP: A Transport Protocol for Real-Time Applications
+
+ /**
+ *
+ * Constructs a "BYE" packet (PT=BYE=203)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param cname The canonical name
+ * @return The BYE Packet
+ *
+ */
+ public static byte[] buildByePacket(long ssrc, String cname) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPByePacket(ssrc));
+
+ return packet;
+ }
+
+ /**
+ *
+ * Constructs a "APP" packet (PT=APP=204)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @return The APP Packet
+ *
+ */
+ public static byte[] buildAppPacket(long ssrc, String cname,
+ String appName, byte[] appData) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPAppPacket(ssrc, appName, appData));
+
+ return packet;
+ }
+
+
+ //************************************************************************
+ // IETF RFC 4584 - Extended RTP Profile for Real-time Transport Control Protocol (RTCP) -
+ // Based Feedback (RTP/AVPF)
+
+ /**
+ *
+ * Constructs a Transport layer Feedback (Generic NACK) "RTPFB" packet (PT=RTPFB=205)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param ssrcSender The sincronization source of sender
+ * @param fbInformation The RTP sequence number array of the lost packets and the bitmask
+ * of the lost packets immediately following the RTP packet indicated
+ * by the pid
+ * @return The RTPFB Packet
+ *
+ */
+
+ public static byte[] buildNackPacket(long ssrc, String cname, long ssrcSender,
+ List fbInformation) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPNackPacket(ssrc, ssrcSender,
+ fbInformation));
+
+ return packet;
+ }
+
+
+ //**************************************************************************
+ // IETF RFC 6285 - Unicast-Based Rapid Acquisition of Multicast RTP Sessions
+
+ /**
+ *
+ * Constructs a Transport layer Feedback (RAMS request) "RTPFB" packet (PT=RTPFB=205)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param ssrcSender The sincronization source of sender
+ * @param extensions The optional TLV elements
+ * @param privateExtensions The optional private extensions
+ * @return The RTPFB Packet
+ *
+ */
+ public static byte[] buildRamsRequestPacket(long ssrc, String cname, long ssrcSender,
+ List extensions,
+ List privateExtensions) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPRamsRequestPacket(ssrc, ssrcSender,
+ extensions, privateExtensions));
+
+ return packet;
+ }
+
+ /**
+ *
+ * Constructs a Transport layer Feedback (RAMS Termination) "RTPFB" packet (PT=RTPFB=205)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param ssrcSender The sincronization source of sender
+ * @param extensions The optional TLV elements
+ * @param privateExtensions The optional private extensions
+ * @return The RTPFB Packet
+ *
+ */
+ public static byte[] buildRamsTerminationPacket(long ssrc, String cname, long ssrcSender,
+ List extensions,
+ List privateExtensions) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPRamsTerminationPacket(ssrc, ssrcSender,
+ extensions, privateExtensions));
+
+ return packet;
+ }
+
+
+ //************************************************************************
+ // IETF RFC 6284 - Port Mapping between Unicast and Multicast RTP Sessions
+
+ /**
+ *
+ * Constructs a Port Mapping Request packet (PT=TOKEN)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param nonce The random nonce
+ * @return The Packet
+ *
+ */
+ public static byte[] buildPortMappingRequestPacket(long ssrc, String cname, byte[] nonce) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPPortMappingRequestPacket(ssrc, nonce));
+
+ return packet;
+ }
+
+ /**
+ *
+ * Constructs a Token Verification Request packet (PT=TOKEN)
+ *
+ * @param ssrc The sincronization source
+ * @param cname The canonical name
+ * @param nonce The random nonce
+ * @param token The token element
+ * @param expirationTime The absoluete expiration time
+ * @return The Packet
+ *
+ */
+ public static byte[] buildTokenVerificationRequestPacket(long ssrc, String cname, byte[] nonce,
+ byte[] token, long expirationTime) {
+ byte packet[] = new byte [0];
+
+ packet = RtcpPacketUtils.append(packet, assembleRTCPReceiverReportPacket(ssrc));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPSourceDescriptionPacket(ssrc, cname));
+ packet = RtcpPacketUtils.append(packet, assembleRTCPTokenVerificationRequestPacket(
+ ssrc, nonce, token, expirationTime));
+
+ return packet;
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketUtils.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketUtils.java
new file mode 100644
index 00000000000..420588bf03b
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpPacketUtils.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+/**
+ * This class provides generic utility functions for
+ * RTCP packets
+ */
+public class RtcpPacketUtils {
+ /**
+ * Append two byte arrays.
+ * Appends packet B at the end of Packet A (Assuming Bytes as elements).
+ * Returns packet ( AB ).
+ *
+ * @param packetA The first packet.
+ * @param packetB The second packet.
+ * @return The desired packet which is A concatenated with B.
+ */
+ public static synchronized byte[] append(byte[] packetA,
+ byte[] packetB) {
+ // Create a new array whose size is equal to sum of packets
+ // being added
+ byte[] packetAB = new byte [ packetA.length + packetB.length ];
+
+ // First paste in packetA
+ for ( int i=0; i < packetA.length; i++ ) {
+ packetAB [i] = packetA [i];
+ }
+
+ // Now start pasting packetB
+ for ( int i=0; i < packetB.length; i++ ) {
+ packetAB [i+packetA.length] = packetB [i];
+ }
+
+ return packetAB;
+ }
+
+ /**
+ * Convert signed int to long by taking 2's complement if necessary.
+ *
+ * @param intToConvert The signed integer which will be converted to Long.
+ *
+ * @return The unsigned long representation of the signed int.
+ */
+ public static synchronized long convertSignedIntToLong(int intToConvert)
+ {
+ int in = intToConvert;
+ // IP: Removed
+ // Session.outprintln (String.valueOf(in));
+ in = ( in << 1 ) >> 1;
+ long lin = (long) in;
+ lin = lin + 0x7FFFFFFF;
+
+ return lin;
+ }
+
+ /**
+ * Convert 64 bit long to n bytes.
+ *
+ * @param ldata The long from which the n byte array will be constructed.
+ * @param n The desired number of bytes to convert the long to.
+ *
+ * @return The desired byte array which is populated with the long value.
+ */
+ public static synchronized byte[] longToBytes(long ldata, int n)
+ {
+ byte[] buff = new byte [ n ];
+ for ( int i=n-1; i>=0; i--) {
+ // Keep assigning the right most 8 bits to the
+ // byte arrays while shift 8 bits during each iteration
+ buff [ i ] = (byte) ldata;
+ ldata = ldata >> 8;
+ }
+
+ return buff;
+ }
+
+ /**
+ * Calculate number of octets required to fit the
+ * given number of octets into 32 bit boundary.
+ *
+ * @param lengthOfUnpaddedPacket The length of an unpadded packet
+ *
+ * @return The required number of octets which must be appended to this
+ * packet to make it fit into a 32 bit boundary.
+ */
+ public static synchronized int calculatePadLength(int lengthOfUnpaddedPacket) {
+ // Determine the number of 8 bit words required to fit the packet in
+ // 32 bit boundary
+ int remain =
+ (int) Math.IEEEremainder ( (double) lengthOfUnpaddedPacket ,
+ (double) 4 );
+ int padLen = 0;
+ // e.g. remainder -1, then we need to pad 1 extra
+ // byte to the end to make it to the 32 bit boundary
+ if ( remain < 0 )
+ padLen = Math.abs ( remain );
+ // e.g. remainder is +1 then we need to pad 3 extra bytes
+ else if ( remain > 0 )
+ padLen = 4-remain;
+
+ return ( padLen );
+ }
+
+ /**
+ * Calculate number of octets required to fit the
+ * given number of octets into 64 bit boundary.
+ *
+ * @param lengthOfUnpaddedPacket The length of an unpadded packet
+ *
+ * @return The required number of octets which must be appended to this
+ * packet to make it fit into a 64 bit boundary.
+ */
+ public static synchronized int calculate64PadLength(int lengthOfUnpaddedPacket) {
+ // Determine the number of 8 bit words required to fit the packet in
+ // 64 bit boundary
+ int remain =
+ (int) Math.IEEEremainder ( (double) lengthOfUnpaddedPacket ,
+ (double) 16 );
+ int padLen = 0;
+ // e.g. remainder -1, then we need to pad 1 extra
+ // byte to the end to make it to the 64 bit boundary
+ if ( remain < 0 )
+ padLen = Math.abs ( remain );
+ // e.g. remainder is +1 then we need to pad 3 extra bytes
+ else if ( remain > 0 )
+ padLen = 16-remain;
+
+ return ( padLen );
+ }
+
+ /** Byte swap */
+
+ public static synchronized byte[] swapBytes(byte[] bdata)
+ {
+ byte[] buff = new byte [ bdata.length ];
+ for ( int i=bdata.length-1; i>=0; i--) {
+ buff [ i ] = bdata[(bdata.length -1) - i ];
+ }
+
+ return buff;
+ }
+
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSdesPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSdesPacket.java
new file mode 100644
index 00000000000..99bad6a2ddb
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSdesPacket.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+public class RtcpSdesPacket extends RtcpPacket {
+
+ void decodePayload(byte[] payload, int length) {
+ // TODO the implementation
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSessionUtils.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSessionUtils.java
new file mode 100644
index 00000000000..050d31a6e87
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSessionUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+import com.google.android.exoplayer2.util.net.Connectivity;
+import com.google.android.exoplayer2.util.net.NetworkUtils;
+
+/**
+ * This class provides generic utility functions for
+ * RTCP sessions
+ */
+public class RtcpSessionUtils {
+
+ public static long SSRC() {
+ return (long)((65535L * Math.random()) + 1L);
+ }
+
+ public static String CNAME() {
+ String iface = "eth0";
+
+ try {
+
+ iface = Connectivity.isConnectedEthernet() ? "eth0" : "wlan0";
+ } catch (Exception e) {
+
+ }
+
+ StringBuilder cname = new StringBuilder();
+ String macAddress = NetworkUtils.getMACAddress(iface);
+ String[] tokensMacAddr = macAddress.split(":");
+
+ //TODO append urn:uuid:
+
+ cname.append(tokensMacAddr[1]);
+ cname.append(tokensMacAddr[2]);
+ cname.append(tokensMacAddr[3]);
+ cname.append('@');
+ cname.append(tokensMacAddr[4]);
+
+ return cname.toString();
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSrPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSrPacket.java
new file mode 100644
index 00000000000..aa20695f540
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpSrPacket.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+public class RtcpSrPacket extends RtcpPacket {
+
+ void decodePayload(byte[] payload, int length) {
+ // TODO the implementation
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpTokenPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpTokenPacket.java
new file mode 100644
index 00000000000..efbcf84d571
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/rtp/rtcp/RtcpTokenPacket.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.util.rtp.rtcp;
+
+public class RtcpTokenPacket extends RtcpPacket {
+
+ protected void decodePayload(byte[] payload, int length) {
+ // TODO the implementation
+ }
+
+ public byte[] getTokenElement() { return null; }
+
+ public int getSmt() { return 0; }
+
+ public long getRelativeExpirationTime() {
+ return 0L;
+ }
+}