├── .gitignore
├── README
├── app
├── AndroidManifest.xml
├── ic_launcher-web.png
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── activity_main.xml
│ ├── menu
│ │ └── main.xml
│ ├── values-sw600dp
│ │ └── dimens.xml
│ ├── values-sw720dp-land
│ │ └── dimens.xml
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── ivygroup
│ └── wfdplayer
│ ├── MainActivity.java
│ ├── MyBroadcastReceiver.java
│ ├── SinkPlayer.java
│ └── WfdSinkController.java
├── cplib.sh
├── framework-modify
├── WifiDisplayController.diff
└── WifiDisplayController.java
└── native
├── Android.mk
├── jni
├── Android.mk
├── SinkPlayer.cpp
├── SinkPlayer.h
└── ivygroup_wfdplayer_sinkplayer.cpp
└── wifi-display
├── ANetworkSession.cpp
├── ANetworkSession.h
├── Android.mk
├── Parameters.cpp
├── Parameters.h
├── ParsedMessage.cpp
├── ParsedMessage.h
├── TimeSeries.cpp
├── TimeSeries.h
├── sink
├── LinearRegression.cpp
├── LinearRegression.h
├── RTPSink.cpp
├── RTPSink.h
├── TunnelRenderer.cpp
├── TunnelRenderer.h
├── WifiDisplaySink.cpp
└── WifiDisplaySink.h
├── source
├── Converter.cpp
├── Converter.h
├── MediaPuller.cpp
├── MediaPuller.h
├── PlaybackSession.cpp
├── PlaybackSession.h
├── RepeaterSource.cpp
├── RepeaterSource.h
├── Sender.cpp
├── Sender.h
├── TSPacketizer.cpp
├── TSPacketizer.h
├── WifiDisplaySource.cpp
└── WifiDisplaySource.h
├── udptest.cpp
└── wfd.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 | obj/
15 | libs/
16 | proguard/
17 |
18 | # Local configuration file (sdk path, etc)
19 | local.properties
20 |
21 | # Eclipse project files
22 | .classpath
23 | .project
24 |
25 | .svn
26 | .settings
27 | .externalToolBuilders/
28 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | 编译说明:
2 | 1. 编译APK
3 | 需要在源码下边编译,可以用以下命令进行编译:
4 | make libwfd
5 | make libwfd_jni
6 | 把编译好的两个lib库放到APP的相关路径下, 可以执行脚本(cplib.sh)进行快速copy
7 | 然后在Eclipse下编译APK
8 | 把此APK安装进手机. 手机必须支持Miracast.
9 | 2. 修改framework.
10 | 把 framework-modify/路径下的patch, 打到framework中
11 | 编译services.jar, 用如下命令:
12 | make services
13 | 把编译好的 services.jar, 替换手机中的 system/framework/services.jar 文件.
14 |
15 | 使用说明:
16 | 1. 重启手机使services.jar起做用.
17 | 2. 打开setting->display->wifidisplay: 点一下搜索显示设备, 则在其它的Source端可以看到本Sink.
18 | 3. 用其它手机连接, 然后快速切换到上一步编译好的APK中.
19 |
20 |
21 | 注意: 此程序并不是一个完美的完成品, 未完成部分如下:
22 | 1. 不用修改Framework, 而直接在程序中管理Wifi direct的连接及发现部分.
23 | 2. 目前的连接好的IP不是计算得来的, 需要计算找到对方的IP.
24 | 3. 播放的一些控制及释放等.
25 |
26 | 此程序的大部分移植自 Android4.2.2的源码, 但是在4.3中此块源码竟然被删掉了, 真是猜不透Google怎么想的.
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivygroup/miracast-sink/214d77aec1d14d3deb7841cbd073c0ab6872d87d/app/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/app/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-17
15 |
--------------------------------------------------------------------------------
/app/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivygroup/miracast-sink/214d77aec1d14d3deb7841cbd073c0ab6872d87d/app/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivygroup/miracast-sink/214d77aec1d14d3deb7841cbd073c0ab6872d87d/app/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivygroup/miracast-sink/214d77aec1d14d3deb7841cbd073c0ab6872d87d/app/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivygroup/miracast-sink/214d77aec1d14d3deb7841cbd073c0ab6872d87d/app/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/app/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
--------------------------------------------------------------------------------
/app/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WfdPlayer
5 | Settings
6 | Hello world!
7 |
8 | Start
9 | End
10 |
11 |
--------------------------------------------------------------------------------
/app/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/com/ivygroup/wfdplayer/MainActivity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.ivygroup.wfdplayer;
3 |
4 | import android.net.wifi.p2p.WifiP2pManager;
5 | import android.os.Bundle;
6 | import android.app.Activity;
7 | import android.content.BroadcastReceiver;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.IntentFilter;
11 | import android.util.Log;
12 | import android.view.Menu;
13 | import android.view.SurfaceHolder;
14 | import android.view.SurfaceView;
15 | import android.view.View;
16 | import android.view.View.OnClickListener;
17 |
18 | public class MainActivity extends Activity implements OnClickListener{
19 | private static final String TAG = "MainActivity";
20 |
21 | private final BroadcastReceiver mMyReceiver = new MyBroadcastReceiver();
22 | private WfdSinkController mWfdSinkController;
23 | private SinkPlayer mSinkPlayer;
24 | private String mHost;
25 | private int mPort;
26 |
27 | private SurfaceView mSurfaceView;
28 | private SurfaceHolder mSurfaceHolder;
29 | private boolean mSurfaceHolderReady;
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | Log.d(TAG, "onCreate called.");
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | mSurfaceHolderReady = false;
38 |
39 | // mWfdSinkController = new WfdSinkController(this);
40 |
41 |
42 | findViewById(R.id.btn_start).setOnClickListener(this);
43 | findViewById(R.id.btn_end).setOnClickListener(this);
44 |
45 |
46 | // IntentFilter intentFilter = new IntentFilter();
47 | // intentFilter.addAction("com.ivygroup.wfdplayer.tolinksource");
48 | // registerReceiver(mMyReceiver, intentFilter, null, null);
49 |
50 |
51 | mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView1);
52 | /**
53 | * 获取与当前surfaceView相关联的那个的surefaceHolder
54 | */
55 | mSurfaceHolder = mSurfaceView.getHolder();
56 | /**
57 | * 注册当surfaceView创建、改变和销毁时应该执行的方法
58 | */
59 | mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
60 |
61 | @Override
62 | public void surfaceDestroyed(SurfaceHolder holder) {
63 | Log.i("MainActivity", "surfaceHolder被销毁了");
64 | mSurfaceHolderReady = false;
65 | }
66 |
67 | @Override
68 | public void surfaceCreated(SurfaceHolder holder) {
69 | Log.i("MainActivity", "surfaceHolder被create了");
70 | mSurfaceHolderReady = true;
71 | }
72 |
73 | @Override
74 | public void surfaceChanged(SurfaceHolder holder, int format, int width,
75 | int height) {
76 | Log.i("MainActivity", "surfaceHolder被改变了");
77 | mSurfaceHolderReady = true;
78 | tryInitAndStartPlayer();
79 | }
80 | });
81 |
82 | /**
83 | * 这里必须设置为SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS哦,意思
84 | * 是创建一个push的'surface',主要的特点就是不进行缓冲
85 | */
86 | /*mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);*/
87 |
88 | Intent myIntent = getIntent();
89 | if (myIntent != null) {
90 | String host = myIntent.getStringExtra("host");
91 | int port = myIntent.getIntExtra("port", 7236);
92 | Log.d(TAG, "host = " + host + ", port = " + port);
93 | mHost = host;
94 | mPort = port;
95 | tryInitAndStartPlayer();
96 | }
97 | }
98 |
99 | @Override
100 | protected void onDestroy() {
101 | // unregisterReceiver(mMyReceiver);
102 | super.onDestroy();
103 | }
104 |
105 | @Override
106 | public boolean onCreateOptionsMenu(Menu menu) {
107 | // Inflate the menu; this adds items to the action bar if it is present.
108 | getMenuInflater().inflate(R.menu.main, menu);
109 | return true;
110 | }
111 |
112 | @Override
113 | public void onClick(View arg0) {
114 | switch (arg0.getId()) {
115 | case R.id.btn_start:
116 | // mWfdSinkController.updateWfdEnableState();
117 | if (mSinkPlayer != null) {
118 | mSinkPlayer.release();
119 | mSinkPlayer = null;
120 | }
121 | // mSinkPlayer = new SinkPlayer(mHost, mPort);
122 | break;
123 |
124 | case R.id.btn_end:
125 | /*if (mSinkPlayer != null) {
126 | mSinkPlayer.release();
127 | mSinkPlayer = null;
128 | }*/
129 | break;
130 |
131 | default:
132 | break;
133 | }
134 | }
135 |
136 | private synchronized void tryInitAndStartPlayer() {
137 | if (!mSurfaceHolderReady) {
138 | return;
139 | }
140 |
141 | if (mHost == null || mHost.isEmpty()) {
142 | return;
143 | }
144 |
145 | Thread thread = new Thread() {
146 | @Override
147 | public void run() {
148 | if (mSinkPlayer != null) {
149 | mSinkPlayer.release();
150 | mSinkPlayer = null;
151 | }
152 | mSinkPlayer = new SinkPlayer();
153 | mSinkPlayer.setDisplay(mSurfaceHolder);
154 | mSinkPlayer.native_startSink(mHost, mPort);
155 | }
156 | };
157 |
158 | thread.start();
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/com/ivygroup/wfdplayer/MyBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package com.ivygroup.wfdplayer;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 |
9 | public class MyBroadcastReceiver extends BroadcastReceiver {
10 | private static final String TAG = "MyBroadcastReceiver";
11 |
12 | @Override
13 | public void onReceive(Context context, Intent intent) {
14 | Log.d(TAG, "receive broadcast ");
15 |
16 | String host = intent.getStringExtra("host");
17 | int port = intent.getIntExtra("port", 7236);
18 | Log.d(TAG, "Receive the link broadcast. host = " + host + ", port = " + port);
19 |
20 | Intent targetIntent = new Intent(context, MainActivity.class);
21 | targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
22 | targetIntent.putExtra("host", host);
23 | targetIntent.putExtra("port", port);
24 |
25 | context.startActivity(targetIntent);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/com/ivygroup/wfdplayer/SinkPlayer.java:
--------------------------------------------------------------------------------
1 | package com.ivygroup.wfdplayer;
2 |
3 | import android.view.Surface;
4 | import android.view.SurfaceHolder;
5 |
6 | public class SinkPlayer {
7 | private SurfaceHolder mSurfaceHolder;
8 |
9 | private int mNativeSinkPlayer; //accessed by native methods
10 | private int mNativeSurfaceTexture; // accessed by native methods
11 |
12 | public SinkPlayer() {
13 | }
14 |
15 | public void release() {
16 | _release();
17 | }
18 |
19 | public void setDisplay(SurfaceHolder sh) {
20 | mSurfaceHolder = sh;
21 | Surface surface;
22 | if (sh != null) {
23 | surface = sh.getSurface();
24 | } else {
25 | surface = null;
26 | }
27 | _setVideoSurface(surface);
28 | updateSurfaceScreenOn();
29 | }
30 |
31 | private void updateSurfaceScreenOn() {
32 | if (mSurfaceHolder != null) {
33 | mSurfaceHolder.setKeepScreenOn(true); // TODO;
34 | }
35 | }
36 |
37 | private native static void native_init();
38 | private native void _release();
39 | private native void _setVideoSurface(Surface surface);
40 | public native void native_startSink(String host, int port);
41 |
42 | static {
43 | System.loadLibrary("wfd");
44 | System.loadLibrary("wfd_jni");
45 | native_init();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/com/ivygroup/wfdplayer/WfdSinkController.java:
--------------------------------------------------------------------------------
1 | package com.ivygroup.wfdplayer;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.net.NetworkInfo;
8 | import android.net.wifi.p2p.WifiP2pGroup;
9 | import android.net.wifi.p2p.WifiP2pManager;
10 | import android.net.wifi.p2p.WifiP2pManager.Channel;
11 | import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
12 | import android.net.wifi.p2p.WifiP2pWfdInfo;
13 | import android.util.Log;
14 |
15 | public class WfdSinkController {
16 | private static final String TAG = "WfdSinkController";
17 | private static final boolean DEBUG = true;
18 |
19 | private static final int DEFAULT_CONTROL_PORT = 7236;
20 | private static final int MAX_THROUGHPUT = 50;
21 |
22 | private final Context mContext;
23 |
24 | private final WifiP2pManager mWifiP2pManager;
25 | private final Channel mWifiP2pChannel;
26 |
27 | private boolean mWifiP2pEnabled;
28 | private boolean mWfdEnabled;
29 | private boolean mWfdEnabling;
30 | private NetworkInfo mNetworkInfo;
31 |
32 |
33 |
34 | public WfdSinkController(Context context) {
35 | mContext = context;
36 |
37 | mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
38 | mWifiP2pChannel = mWifiP2pManager.initialize(context, mContext.getMainLooper(), null);
39 |
40 | IntentFilter intentFilter = new IntentFilter();
41 | intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
42 | intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
43 | intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
44 | context.registerReceiver(mWifiP2pReceiver, intentFilter, null, null);
45 | }
46 |
47 | private void handleStateChanged(boolean enabled) {
48 | mWifiP2pEnabled = enabled;
49 | updateWfdEnableState();
50 | }
51 |
52 | private void handleConnectionChanged(NetworkInfo networkInfo) {
53 | mNetworkInfo = networkInfo;
54 | if (mWfdEnabled && networkInfo.isConnected()) {
55 | mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
56 | @Override
57 | public void onGroupInfoAvailable(WifiP2pGroup info) {
58 | if (DEBUG) {
59 | Log.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
60 | }
61 |
62 | // TODO: to connect source. How to get host and port?
63 |
64 |
65 | }
66 | });
67 | } else {
68 | disconnect();
69 | }
70 | }
71 |
72 | public void updateWfdEnableState() {
73 | if (mWifiP2pEnabled) {
74 | // WFD should be enabled.
75 | if (!mWfdEnabled && !mWfdEnabling) {
76 | mWfdEnabling = true;
77 |
78 | WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
79 | wfdInfo.setWfdEnabled(true);
80 | wfdInfo.setDeviceType(WifiP2pWfdInfo.PRIMARY_SINK);
81 | wfdInfo.setSessionAvailable(true);
82 | wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
83 | wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
84 | mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new WifiP2pManager.ActionListener() {
85 | @Override
86 | public void onSuccess() {
87 | if (DEBUG) {
88 | Log.d(TAG, "Successfully set WFD info.");
89 | }
90 | if (mWfdEnabling) {
91 | mWfdEnabling = false;
92 | mWfdEnabled = true;
93 | // reportFeatureState();
94 | }
95 | }
96 |
97 | @Override
98 | public void onFailure(int reason) {
99 | if (DEBUG) {
100 | Log.d(TAG, "Failed to set WFD info with reason " + reason + ".");
101 | }
102 | mWfdEnabling = false;
103 | }
104 | });
105 | }
106 | } else {
107 | // WFD should be disabled.
108 | mWfdEnabling = false;
109 | mWfdEnabled = false;
110 | // reportFeatureState();
111 | disconnect();
112 | }
113 | }
114 |
115 | private void disconnect() {
116 | // updateConnection();
117 | }
118 |
119 | private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
120 | @Override
121 | public void onReceive(Context context, Intent intent) {
122 | final String action = intent.getAction();
123 | if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
124 | // This broadcast is sticky so we'll always get the initial Wifi P2P state
125 | // on startup.
126 | boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
127 | WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
128 | WifiP2pManager.WIFI_P2P_STATE_ENABLED;
129 | if (DEBUG) {
130 | Log.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
131 | + enabled);
132 | }
133 | handleStateChanged(enabled);
134 |
135 |
136 | } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
137 | if (DEBUG) {
138 | Log.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
139 | }
140 | // handlePeersChanged();
141 |
142 | } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
143 | NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
144 | WifiP2pManager.EXTRA_NETWORK_INFO);
145 | if (DEBUG) {
146 | Log.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
147 | + networkInfo);
148 | }
149 | handleConnectionChanged(networkInfo);
150 |
151 | }
152 | }
153 | };
154 |
155 | private static String describeWifiP2pGroup(WifiP2pGroup group) {
156 | return group != null ? group.toString().replace('\n', ',') : "null";
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/cplib.sh:
--------------------------------------------------------------------------------
1 | cp ../out/target/product/redhookbay/system/lib/libwfd_jni.so ./app/libs/armeabi/
2 | cp ../out/target/product/redhookbay/system/lib/libwfd.so ./app/libs/armeabi/
3 |
--------------------------------------------------------------------------------
/framework-modify/WifiDisplayController.diff:
--------------------------------------------------------------------------------
1 | diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
2 | index 56feea6..093555e 100644
3 | --- a/services/java/com/android/server/display/WifiDisplayController.java
4 | +++ b/services/java/com/android/server/display/WifiDisplayController.java
5 | @@ -79,7 +79,7 @@ import libcore.util.Objects;
6 | */
7 | final class WifiDisplayController implements DumpUtils.Dump {
8 | private static final String TAG = "WifiDisplayController";
9 | - private static final boolean DEBUG = false;
10 | + private static final boolean DEBUG = true;
11 |
12 | private static final int DEFAULT_CONTROL_PORT = 7236;
13 | private static final int MAX_THROUGHPUT = 50;
14 | @@ -276,9 +276,11 @@ final class WifiDisplayController implements DumpUtils.Dump {
15 |
16 | WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
17 | wfdInfo.setWfdEnabled(true);
18 | - wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
19 | + // wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
20 | + // wfdInfo.setDeviceType(WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK);
21 | + wfdInfo.setDeviceType(WifiP2pWfdInfo.PRIMARY_SINK);
22 | wfdInfo.setSessionAvailable(true);
23 | - wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
24 | + // wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
25 | wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
26 | mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
27 | @Override
28 | @@ -650,7 +652,8 @@ final class WifiDisplayController implements DumpUtils.Dump {
29 | // Intel modification is to set go_intent to 14 in order to become GO.
30 | // It would allow to choose the operating channel with the help of an internal
31 | // algorithm executed in wpa_supplicant
32 | - config.groupOwnerIntent = WifiP2pConfig.MAX_GROUP_OWNER_INTENT - 1;
33 | + // config.groupOwnerIntent = WifiP2pConfig.MAX_GROUP_OWNER_INTENT - 1;
34 | + config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
35 |
36 | WifiDisplay display = createWifiDisplay(mConnectingDevice);
37 | advertiseDisplay(display, null, 0, 0, 0);
38 | @@ -798,6 +801,44 @@ final class WifiDisplayController implements DumpUtils.Dump {
39 | }
40 | }
41 | });
42 | + } else {
43 | + Slog.i(TAG, "mDesiedDevice is null");
44 | + mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
45 | + @Override
46 | + public void onGroupInfoAvailable(WifiP2pGroup info) {
47 | + if (DEBUG) {
48 | + Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
49 | + }
50 | +
51 | + Inet4Address addr = getInterfaceAddress(info);
52 | + if (addr == null) {
53 | + Slog.i(TAG, "Failed to get local interface address for communicating "
54 | + + "with Wifi display: ");
55 | + return; // done
56 | + }
57 | +
58 | + // final int port = getPortNumber(mConnectedDevice);
59 | +
60 | + // final String host = "192.168.49.25";
61 | + // final int port = 8554;
62 | +
63 | + final String host = "192.168.49.1";
64 | + final int port = DEFAULT_CONTROL_PORT;
65 | +
66 | + final String iface = host + ":" + port;
67 | +
68 | + Slog.d(TAG, "connected . host and port = " + iface);
69 | +
70 | + // Send a intent to my app to connect the source.
71 | + Intent intent = new Intent("com.ivygroup.wfdplayer.tolinksource");
72 | + intent.putExtra("host", host);
73 | + intent.putExtra("port", port);
74 | +
75 | + mContext.sendBroadcast(intent);
76 | +
77 | + return;
78 | + }
79 | + });
80 | }
81 | } else {
82 | disconnect();
83 |
--------------------------------------------------------------------------------
/native/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 | include $(CLEAR_VARS)
3 |
4 | include $(call all-makefiles-under,$(LOCAL_PATH))
5 |
--------------------------------------------------------------------------------
/native/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | LOCAL_SRC_FILES:= \
6 | SinkPlayer.cpp \
7 | ivygroup_wfdplayer_sinkplayer.cpp \
8 |
9 | LOCAL_C_INCLUDES:= \
10 | $(TOP)/frameworks/av/include/media/stagefright \
11 | $(TOP)/miracast-sink/native/wifi-display \
12 | $(TOP)/frameworks/native/include/media/openmax \
13 | $(PV_INCLUDES) \
14 | $(JNI_H_INCLUDE) \
15 | $(call include-path-for, corecg graphics)
16 |
17 | LOCAL_SHARED_LIBRARIES:= \
18 | libandroid_runtime \
19 | libwfd \
20 | libstagefright_foundation \
21 | libutils \
22 | libnativehelper \
23 | libui \
24 | libgui \
25 | libskia \
26 |
27 |
28 |
29 |
30 | LOCAL_MODULE:= libwfd_jni
31 |
32 | LOCAL_MODULE_TAGS:= optional
33 |
34 | include $(BUILD_SHARED_LIBRARY)
35 |
36 |
--------------------------------------------------------------------------------
/native/jni/SinkPlayer.cpp:
--------------------------------------------------------------------------------
1 | #define LOG_NDEBUG 0
2 | #define LOG_TAG "SinkPlayer"
3 |
4 | #include "SinkPlayer.h"
5 |
6 | #include "ANetworkSession.h"
7 | #include "sink/WifiDisplaySink.h"
8 | #include
9 | #include
10 |
11 | namespace android {
12 |
13 | SinkPlayer::SinkPlayer() {
14 | }
15 |
16 | SinkPlayer::~SinkPlayer() {
17 | }
18 |
19 | status_t SinkPlayer::start(const char *host, int32_t port) {
20 | mLooper = new ALooper;
21 | mNetSession = new ANetworkSession;
22 | mSink = new WifiDisplaySink(mNetSession, mSurfaceTexture);
23 | mNetSession->start();
24 | mLooper->setName("sink_player");
25 | mLooper->registerHandler(mSink);
26 |
27 | LOGD("will start sink by %s:%d", host, port);
28 |
29 | mSink->start(host, port);
30 | mLooper->start(true /* runOnCallingThread */);
31 | return OK;
32 | }
33 |
34 | status_t SinkPlayer::setSurfaceTexture(const sp &surfaceTexture) {
35 | ALOGD("setSurfaceTexture called. surfacetexture = %p", surfaceTexture.get());
36 | mSurfaceTexture = surfaceTexture;
37 | return OK;
38 | }
39 |
40 | status_t SinkPlayer::dispose() {
41 | /*mSink->stop();
42 |
43 | mLooper->stop();
44 | mNetSession->stop();*/
45 |
46 | return OK;
47 | }
48 |
49 | } // namespace android
50 |
--------------------------------------------------------------------------------
/native/jni/SinkPlayer.h:
--------------------------------------------------------------------------------
1 | #ifndef SINK_PLAYER_H_
2 |
3 | #define SINK_PLAYER_H_
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | namespace android {
10 |
11 | struct ALooper;
12 | struct ANetworkSession;
13 | struct IRemoteDisplayClient;
14 | struct WifiDisplaySink;
15 | struct ISurfaceTexture;
16 |
17 | class SinkPlayer : public RefBase {
18 | public:
19 | SinkPlayer();
20 |
21 | status_t setSurfaceTexture(
22 | const sp& surfaceTexture);
23 |
24 | status_t start(const char *host, int32_t port);
25 | status_t dispose();
26 |
27 | protected:
28 | virtual ~SinkPlayer();
29 |
30 | public:
31 |
32 |
33 |
34 | private:
35 | sp mNetLooper;
36 | sp mLooper;
37 | sp mNetSession;
38 | sp mSink;
39 | sp mSurfaceTexture;
40 |
41 | DISALLOW_EVIL_CONSTRUCTORS(SinkPlayer);
42 | };
43 |
44 | }
45 |
46 | #endif // SINK_PLAYER_H_
47 |
--------------------------------------------------------------------------------
/native/jni/ivygroup_wfdplayer_sinkplayer.cpp:
--------------------------------------------------------------------------------
1 | #define LOG_NDEBUG 0
2 | #define LOG_TAG "ivygroup_wfdplayer_sinkplayer"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | //#include
10 | #include
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include "jni.h"
19 | #include "JNIHelp.h"
20 | #include "android_runtime/AndroidRuntime.h"
21 | #include "android_runtime/android_view_Surface.h"
22 | #include "utils/Errors.h" // for status_t
23 | #include "utils/KeyedVector.h"
24 | #include "utils/String8.h"
25 | #include "foundation/AString.h"
26 |
27 | #include
28 | #include
29 |
30 | #include "SinkPlayer.h"
31 |
32 |
33 | using namespace android;
34 |
35 |
36 | struct fields_t {
37 | jfieldID native_sinkplayer;
38 | jfieldID native_surfacetexture;
39 | };
40 | static fields_t fields;
41 | static Mutex sLock;
42 |
43 | static void setPlayer(JNIEnv* env, jobject thiz, const sp& player) {
44 | Mutex::Autolock l(sLock);
45 | sp old = (SinkPlayer*)env->GetIntField(thiz, fields.native_sinkplayer);
46 | if (old != 0) {
47 | old->decStrong(thiz);
48 | }
49 | env->SetIntField(thiz, fields.native_sinkplayer, (int)player.get());
50 | }
51 |
52 | static sp getPlayer_l(JNIEnv* env, jobject thiz) {
53 | Mutex::Autolock l(sLock);
54 | SinkPlayer* const p = (SinkPlayer*)env->GetIntField(thiz, fields.native_sinkplayer);
55 | return sp(p);
56 | }
57 |
58 | static sp getPlayer(JNIEnv* env, jobject thiz) {
59 | sp mp = getPlayer_l(env, thiz);
60 | if (mp == NULL) {
61 | //
62 | sp player = new SinkPlayer();
63 | if (player.get()) {
64 | player->incStrong(thiz);
65 | }
66 |
67 | setPlayer(env, thiz, player);
68 | return player;
69 |
70 | } else {
71 | return mp;
72 | }
73 | }
74 |
75 | static void
76 | ivygroup_wfdplayer_sinkplayer_native_init(JNIEnv *env) {
77 | jclass clazz;
78 |
79 | clazz = env->FindClass("com/ivygroup/wfdplayer/SinkPlayer");
80 | if (clazz == NULL) {
81 | return;
82 | }
83 |
84 | fields.native_sinkplayer = env->GetFieldID(clazz, "mNativeSinkPlayer", "I");
85 | if (fields.native_sinkplayer == NULL) {
86 | return;
87 | }
88 |
89 | fields.native_surfacetexture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "I");
90 | if (fields.native_surfacetexture == NULL) {
91 | return;
92 | }
93 | }
94 |
95 | static void
96 | ivygroup_wfdplayer_sinkplayer_release(JNIEnv *env, jobject thiz) {
97 | setPlayer(env, thiz, 0);
98 | }
99 |
100 | static sp
101 | getVideoSurfaceTexture(JNIEnv* env, jobject thiz) {
102 | ISurfaceTexture * const p = (ISurfaceTexture*)env->GetIntField(thiz, fields.native_surfacetexture);
103 | return sp(p);
104 | }
105 |
106 | static void
107 | decVideoSurfaceRef(JNIEnv *env, jobject thiz)
108 | {
109 | sp mp = getPlayer(env, thiz);
110 | if (mp == NULL) {
111 | return;
112 | }
113 |
114 | sp old_st = getVideoSurfaceTexture(env, thiz);
115 | if (old_st != NULL) {
116 | old_st->decStrong((void*)decVideoSurfaceRef);
117 | }
118 | }
119 |
120 | static void
121 | setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
122 | {
123 | sp mp = getPlayer(env, thiz);
124 | if (mp == NULL) {
125 | if (mediaPlayerMustBeAlive) {
126 | jniThrowException(env, "java/lang/IllegalStateException", NULL);
127 | }
128 | return;
129 | }
130 |
131 | decVideoSurfaceRef(env, thiz);
132 |
133 | sp new_st;
134 | if (jsurface) {
135 | sp surface(android_view_Surface_getSurface(env, jsurface));
136 | if (surface != NULL) {
137 | new_st = surface->getSurfaceTexture();
138 | if (new_st == NULL) {
139 |
140 | jniThrowException(env, "java/lang/IllegalArgumentException",
141 | "The surface does not have a binding SurfaceTexture!");
142 | return;
143 | }
144 | new_st->incStrong((void*)decVideoSurfaceRef);
145 |
146 | } else {
147 | jniThrowException(env, "java/lang/IllegalArgumentException",
148 | "The surface has been released");
149 | return;
150 | }
151 | }
152 |
153 |
154 | env->SetIntField(thiz, fields.native_surfacetexture, (int)new_st.get());
155 |
156 | // This will fail if the media player has not been initialized yet. This
157 | // can be the case if setDisplay() on MediaPlayer.java has been called
158 | // before setDataSource(). The redundant call to setVideoSurfaceTexture()
159 | // in prepare/prepareAsync covers for this case.
160 | mp->setSurfaceTexture(new_st);
161 | }
162 |
163 | static void
164 | android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
165 | {
166 | setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
167 | }
168 |
169 | static void
170 | ivygroup_wfdplayer_sinkplayer_startSink(JNIEnv* env, jobject thiz, jstring host, jint port) {
171 | if (host == NULL) {
172 | jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
173 | return;
174 | }
175 |
176 | const char *tmp = env->GetStringUTFChars(host, NULL);
177 | if (tmp == NULL) { // Out of memory
178 | return;
179 | }
180 |
181 | AString hostStr(tmp);
182 | env->ReleaseStringUTFChars(host, tmp);
183 | tmp = NULL;
184 |
185 | sp p = getPlayer(env, thiz);
186 | p->start(hostStr.c_str(), port);
187 | }
188 |
189 | static JNINativeMethod nativeMethods[] = {
190 | // {"native_possibleEncoding", "([B)Ljava/lang/String;", (void*)possibleEncoding}
191 | {"native_init", "()V", (void *)ivygroup_wfdplayer_sinkplayer_native_init},
192 | {"_release", "()V", (void *)ivygroup_wfdplayer_sinkplayer_release},
193 | {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
194 | {"native_startSink", "(Ljava/lang/String;I)V", (void*)ivygroup_wfdplayer_sinkplayer_startSink}
195 | };
196 |
197 |
198 | int registerNativeMethods(JNIEnv* env) {
199 | int result = -1;
200 | jclass clazz = env->FindClass("com/ivygroup/wfdplayer/SinkPlayer");
201 |
202 | if (NULL != clazz) {
203 | if (env->RegisterNatives(clazz, nativeMethods, sizeof(nativeMethods)
204 | / sizeof(nativeMethods[0])) == JNI_OK) {
205 | result = 0;
206 | }
207 | }
208 |
209 | return result;
210 | }
211 |
212 |
213 | jint JNI_OnLoad(JavaVM* vm, void* reserved) {
214 | JNIEnv* env = NULL;
215 | jint result = -1;
216 |
217 | if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
218 | return result;
219 | }
220 |
221 | if (env == NULL) {
222 | return result;
223 | }
224 | if (registerNativeMethods(env) != 0) {
225 | return result;
226 | }
227 |
228 | result = JNI_VERSION_1_4;
229 | return result;
230 | }
231 |
--------------------------------------------------------------------------------
/native/wifi-display/ANetworkSession.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef A_NETWORK_SESSION_H_
18 |
19 | #define A_NETWORK_SESSION_H_
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include
27 |
28 | namespace android {
29 |
30 | struct AMessage;
31 |
32 | // Helper class to manage a number of live sockets (datagram and stream-based)
33 | // on a single thread. Clients are notified about activity through AMessages.
34 | struct ANetworkSession : public RefBase {
35 | ANetworkSession();
36 |
37 | status_t start();
38 | status_t stop();
39 |
40 | status_t createRTSPClient(
41 | const char *host, unsigned port, const sp ¬ify,
42 | int32_t *sessionID);
43 |
44 | status_t createRTSPServer(
45 | const struct in_addr &addr, unsigned port,
46 | const sp ¬ify, int32_t *sessionID);
47 |
48 | status_t createUDPSession(
49 | unsigned localPort, const sp ¬ify, int32_t *sessionID);
50 |
51 | status_t createUDPSession(
52 | unsigned localPort,
53 | const char *remoteHost,
54 | unsigned remotePort,
55 | const sp ¬ify,
56 | int32_t *sessionID);
57 |
58 | status_t connectUDPSession(
59 | int32_t sessionID, const char *remoteHost, unsigned remotePort);
60 |
61 | // passive
62 | status_t createTCPDatagramSession(
63 | const struct in_addr &addr, unsigned port,
64 | const sp ¬ify, int32_t *sessionID);
65 |
66 | // active
67 | status_t createTCPDatagramSession(
68 | unsigned localPort,
69 | const char *remoteHost,
70 | unsigned remotePort,
71 | const sp ¬ify,
72 | int32_t *sessionID);
73 |
74 | status_t destroySession(int32_t sessionID);
75 |
76 | status_t sendRequest(
77 | int32_t sessionID, const void *data, ssize_t size = -1);
78 |
79 | enum NotificationReason {
80 | kWhatError,
81 | kWhatConnected,
82 | kWhatClientConnected,
83 | kWhatData,
84 | kWhatDatagram,
85 | kWhatBinaryData,
86 | };
87 |
88 | protected:
89 | virtual ~ANetworkSession();
90 |
91 | private:
92 | struct NetworkThread;
93 | struct Session;
94 |
95 | Mutex mLock;
96 | sp mThread;
97 |
98 | int32_t mNextSessionID;
99 |
100 | int mPipeFd[2];
101 |
102 | KeyedVector > mSessions;
103 |
104 | enum Mode {
105 | kModeCreateUDPSession,
106 | kModeCreateTCPDatagramSessionPassive,
107 | kModeCreateTCPDatagramSessionActive,
108 | kModeCreateRTSPServer,
109 | kModeCreateRTSPClient,
110 | };
111 | status_t createClientOrServer(
112 | Mode mode,
113 | const struct in_addr *addr,
114 | unsigned port,
115 | const char *remoteHost,
116 | unsigned remotePort,
117 | const sp ¬ify,
118 | int32_t *sessionID);
119 |
120 | void threadLoop();
121 | void interrupt();
122 |
123 | static status_t MakeSocketNonBlocking(int s);
124 |
125 | DISALLOW_EVIL_CONSTRUCTORS(ANetworkSession);
126 | };
127 |
128 | } // namespace android
129 |
130 | #endif // A_NETWORK_SESSION_H_
131 |
--------------------------------------------------------------------------------
/native/wifi-display/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | LOCAL_SRC_FILES:= \
6 | ANetworkSession.cpp \
7 | Parameters.cpp \
8 | ParsedMessage.cpp \
9 | sink/LinearRegression.cpp \
10 | sink/RTPSink.cpp \
11 | sink/TunnelRenderer.cpp \
12 | sink/WifiDisplaySink.cpp \
13 | source/Converter.cpp \
14 | source/MediaPuller.cpp \
15 | source/PlaybackSession.cpp \
16 | source/RepeaterSource.cpp \
17 | source/Sender.cpp \
18 | source/TSPacketizer.cpp \
19 | source/WifiDisplaySource.cpp \
20 | TimeSeries.cpp \
21 |
22 | LOCAL_C_INCLUDES:= \
23 | $(TOP)/frameworks/av/media/libstagefright \
24 | $(TOP)/frameworks/native/include/media/openmax \
25 | $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \
26 |
27 | LOCAL_SHARED_LIBRARIES:= \
28 | libbinder \
29 | libcutils \
30 | libgui \
31 | libmedia \
32 | libstagefright \
33 | libstagefright_foundation \
34 | libui \
35 | libutils \
36 |
37 | LOCAL_MODULE:= libwfd
38 |
39 | LOCAL_MODULE_TAGS:= optional
40 |
41 | include $(BUILD_SHARED_LIBRARY)
42 |
43 | ################################################################################
44 |
45 | include $(CLEAR_VARS)
46 |
47 | LOCAL_SRC_FILES:= \
48 | wfd.cpp \
49 |
50 | LOCAL_SHARED_LIBRARIES:= \
51 | libbinder \
52 | libgui \
53 | libmedia \
54 | libstagefright \
55 | libstagefright_foundation \
56 | libstagefright_wfd \
57 | libutils \
58 |
59 | LOCAL_MODULE:= wfd
60 |
61 | LOCAL_MODULE_TAGS := debug
62 |
63 | # include $(BUILD_EXECUTABLE)
64 |
65 | ################################################################################
66 |
67 | include $(CLEAR_VARS)
68 |
69 | LOCAL_SRC_FILES:= \
70 | udptest.cpp \
71 |
72 | LOCAL_SHARED_LIBRARIES:= \
73 | libbinder \
74 | libgui \
75 | libmedia \
76 | libstagefright \
77 | libstagefright_foundation \
78 | libstagefright_wfd \
79 | libutils \
80 |
81 | LOCAL_MODULE:= udptest
82 |
83 | LOCAL_MODULE_TAGS := debug
84 |
85 | # include $(BUILD_EXECUTABLE)
86 |
--------------------------------------------------------------------------------
/native/wifi-display/Parameters.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include "Parameters.h"
18 |
19 | #include
20 |
21 | namespace android {
22 |
23 | // static
24 | sp Parameters::Parse(const char *data, size_t size) {
25 | sp params = new Parameters;
26 | status_t err = params->parse(data, size);
27 |
28 | if (err != OK) {
29 | return NULL;
30 | }
31 |
32 | return params;
33 | }
34 |
35 | Parameters::Parameters() {}
36 |
37 | Parameters::~Parameters() {}
38 |
39 | status_t Parameters::parse(const char *data, size_t size) {
40 | size_t i = 0;
41 | while (i < size) {
42 | size_t nameStart = i;
43 | while (i < size && data[i] != ':') {
44 | ++i;
45 | }
46 |
47 | if (i == size || i == nameStart) {
48 | return ERROR_MALFORMED;
49 | }
50 |
51 | AString name(&data[nameStart], i - nameStart);
52 | name.trim();
53 | name.tolower();
54 |
55 | ++i;
56 |
57 | size_t valueStart = i;
58 |
59 | while (i + 1 < size && (data[i] != '\r' || data[i + 1] != '\n')) {
60 | ++i;
61 | }
62 |
63 | AString value(&data[valueStart], i - valueStart);
64 | value.trim();
65 |
66 | mDict.add(name, value);
67 |
68 | i += 2;
69 | }
70 |
71 | return OK;
72 | }
73 |
74 | bool Parameters::findParameter(const char *name, AString *value) const {
75 | AString key = name;
76 | key.tolower();
77 |
78 | ssize_t index = mDict.indexOfKey(key);
79 |
80 | if (index < 0) {
81 | value->clear();
82 |
83 | return false;
84 | }
85 |
86 | *value = mDict.valueAt(index);
87 | return true;
88 | }
89 |
90 | } // namespace android
91 |
--------------------------------------------------------------------------------
/native/wifi-display/Parameters.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | namespace android {
23 |
24 | struct Parameters : public RefBase {
25 | static sp Parse(const char *data, size_t size);
26 |
27 | bool findParameter(const char *name, AString *value) const;
28 |
29 | protected:
30 | virtual ~Parameters();
31 |
32 | private:
33 | KeyedVector mDict;
34 |
35 | Parameters();
36 | status_t parse(const char *data, size_t size);
37 |
38 | DISALLOW_EVIL_CONSTRUCTORS(Parameters);
39 | };
40 |
41 | } // namespace android
42 |
--------------------------------------------------------------------------------
/native/wifi-display/ParsedMessage.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include "ParsedMessage.h"
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | namespace android {
24 |
25 | // static
26 | sp ParsedMessage::Parse(
27 | const char *data, size_t size, bool noMoreData, size_t *length) {
28 | sp msg = new ParsedMessage;
29 | ssize_t res = msg->parse(data, size, noMoreData);
30 |
31 | if (res < 0) {
32 | *length = 0;
33 | return NULL;
34 | }
35 |
36 | *length = res;
37 | return msg;
38 | }
39 |
40 | ParsedMessage::ParsedMessage() {
41 | }
42 |
43 | ParsedMessage::~ParsedMessage() {
44 | }
45 |
46 | bool ParsedMessage::findString(const char *name, AString *value) const {
47 | AString key = name;
48 | key.tolower();
49 |
50 | ssize_t index = mDict.indexOfKey(key);
51 |
52 | if (index < 0) {
53 | value->clear();
54 |
55 | return false;
56 | }
57 |
58 | *value = mDict.valueAt(index);
59 | return true;
60 | }
61 |
62 | bool ParsedMessage::findInt32(const char *name, int32_t *value) const {
63 | AString stringValue;
64 |
65 | if (!findString(name, &stringValue)) {
66 | return false;
67 | }
68 |
69 | char *end;
70 | *value = strtol(stringValue.c_str(), &end, 10);
71 |
72 | if (end == stringValue.c_str() || *end != '\0') {
73 | *value = 0;
74 | return false;
75 | }
76 |
77 | return true;
78 | }
79 |
80 | const char *ParsedMessage::getContent() const {
81 | return mContent.c_str();
82 | }
83 |
84 | ssize_t ParsedMessage::parse(const char *data, size_t size, bool noMoreData) {
85 | if (size == 0) {
86 | return -1;
87 | }
88 |
89 | ssize_t lastDictIndex = -1;
90 |
91 | size_t offset = 0;
92 | while (offset < size) {
93 | size_t lineEndOffset = offset;
94 | while (lineEndOffset + 1 < size
95 | && (data[lineEndOffset] != '\r'
96 | || data[lineEndOffset + 1] != '\n')) {
97 | ++lineEndOffset;
98 | }
99 |
100 | if (lineEndOffset + 1 >= size) {
101 | return -1;
102 | }
103 |
104 | AString line(&data[offset], lineEndOffset - offset);
105 |
106 | if (offset == 0) {
107 | // Special handling for the request/status line.
108 |
109 | mDict.add(AString("_"), line);
110 | offset = lineEndOffset + 2;
111 |
112 | continue;
113 | }
114 |
115 | if (lineEndOffset == offset) {
116 | offset += 2;
117 | break;
118 | }
119 |
120 | if (line.c_str()[0] == ' ' || line.c_str()[0] == '\t') {
121 | // Support for folded header values.
122 |
123 | if (lastDictIndex >= 0) {
124 | // Otherwise it's malformed since the first header line
125 | // cannot continue anything...
126 |
127 | AString &value = mDict.editValueAt(lastDictIndex);
128 | value.append(line);
129 | }
130 |
131 | offset = lineEndOffset + 2;
132 | continue;
133 | }
134 |
135 | ssize_t colonPos = line.find(":");
136 | if (colonPos >= 0) {
137 | AString key(line, 0, colonPos);
138 | key.trim();
139 | key.tolower();
140 |
141 | line.erase(0, colonPos + 1);
142 |
143 | lastDictIndex = mDict.add(key, line);
144 | }
145 |
146 | offset = lineEndOffset + 2;
147 | }
148 |
149 | for (size_t i = 0; i < mDict.size(); ++i) {
150 | mDict.editValueAt(i).trim();
151 | }
152 |
153 | // Found the end of headers.
154 |
155 | int32_t contentLength;
156 | if (!findInt32("content-length", &contentLength) || contentLength < 0) {
157 | contentLength = 0;
158 | }
159 |
160 | size_t totalLength = offset + contentLength;
161 |
162 | if (size < totalLength) {
163 | return -1;
164 | }
165 |
166 | mContent.setTo(&data[offset], contentLength);
167 |
168 | return totalLength;
169 | }
170 |
171 | void ParsedMessage::getRequestField(size_t index, AString *field) const {
172 | AString line;
173 | CHECK(findString("_", &line));
174 |
175 | size_t prevOffset = 0;
176 | size_t offset = 0;
177 | for (size_t i = 0; i <= index; ++i) {
178 | ssize_t spacePos = line.find(" ", offset);
179 |
180 | if (spacePos < 0) {
181 | spacePos = line.size();
182 | }
183 |
184 | prevOffset = offset;
185 | offset = spacePos + 1;
186 | }
187 |
188 | field->setTo(line, prevOffset, offset - prevOffset - 1);
189 | }
190 |
191 | bool ParsedMessage::getStatusCode(int32_t *statusCode) const {
192 | AString statusCodeString;
193 | getRequestField(1, &statusCodeString);
194 |
195 | char *end;
196 | *statusCode = strtol(statusCodeString.c_str(), &end, 10);
197 |
198 | if (*end != '\0' || end == statusCodeString.c_str()
199 | || (*statusCode) < 100 || (*statusCode) > 999) {
200 | *statusCode = 0;
201 | return false;
202 | }
203 |
204 | return true;
205 | }
206 |
207 | AString ParsedMessage::debugString() const {
208 | AString line;
209 | CHECK(findString("_", &line));
210 |
211 | line.append("\n");
212 |
213 | for (size_t i = 0; i < mDict.size(); ++i) {
214 | const AString &key = mDict.keyAt(i);
215 | const AString &value = mDict.valueAt(i);
216 |
217 | if (key == AString("_")) {
218 | continue;
219 | }
220 |
221 | line.append(key);
222 | line.append(": ");
223 | line.append(value);
224 | line.append("\n");
225 | }
226 |
227 | line.append("\n");
228 | line.append(mContent);
229 |
230 | return line;
231 | }
232 |
233 | // static
234 | bool ParsedMessage::GetAttribute(
235 | const char *s, const char *key, AString *value) {
236 | value->clear();
237 |
238 | size_t keyLen = strlen(key);
239 |
240 | for (;;) {
241 | while (isspace(*s)) {
242 | ++s;
243 | }
244 |
245 | const char *colonPos = strchr(s, ';');
246 |
247 | size_t len =
248 | (colonPos == NULL) ? strlen(s) : colonPos - s;
249 |
250 | if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
251 | value->setTo(&s[keyLen + 1], len - keyLen - 1);
252 | return true;
253 | }
254 |
255 | if (colonPos == NULL) {
256 | return false;
257 | }
258 |
259 | s = colonPos + 1;
260 | }
261 | }
262 |
263 | // static
264 | bool ParsedMessage::GetInt32Attribute(
265 | const char *s, const char *key, int32_t *value) {
266 | AString stringValue;
267 | if (!GetAttribute(s, key, &stringValue)) {
268 | *value = 0;
269 | return false;
270 | }
271 |
272 | char *end;
273 | *value = strtol(stringValue.c_str(), &end, 10);
274 |
275 | if (end == stringValue.c_str() || *end != '\0') {
276 | *value = 0;
277 | return false;
278 | }
279 |
280 | return true;
281 | }
282 |
283 | } // namespace android
284 |
285 |
--------------------------------------------------------------------------------
/native/wifi-display/ParsedMessage.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | namespace android {
23 |
24 | // Encapsulates an "HTTP/RTSP style" response, i.e. a status line,
25 | // key/value pairs making up the headers and an optional body/content.
26 | struct ParsedMessage : public RefBase {
27 | static sp Parse(
28 | const char *data, size_t size, bool noMoreData, size_t *length);
29 |
30 | bool findString(const char *name, AString *value) const;
31 | bool findInt32(const char *name, int32_t *value) const;
32 |
33 | const char *getContent() const;
34 |
35 | void getRequestField(size_t index, AString *field) const;
36 | bool getStatusCode(int32_t *statusCode) const;
37 |
38 | AString debugString() const;
39 |
40 | static bool GetAttribute(const char *s, const char *key, AString *value);
41 |
42 | static bool GetInt32Attribute(
43 | const char *s, const char *key, int32_t *value);
44 |
45 |
46 | protected:
47 | virtual ~ParsedMessage();
48 |
49 | private:
50 | KeyedVector mDict;
51 | AString mContent;
52 |
53 | ParsedMessage();
54 |
55 | ssize_t parse(const char *data, size_t size, bool noMoreData);
56 |
57 | DISALLOW_EVIL_CONSTRUCTORS(ParsedMessage);
58 | };
59 |
60 | } // namespace android
61 |
--------------------------------------------------------------------------------
/native/wifi-display/TimeSeries.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #include "TimeSeries.h"
18 |
19 | #include
20 | #include
21 |
22 | namespace android {
23 |
24 | TimeSeries::TimeSeries()
25 | : mCount(0),
26 | mSum(0.0) {
27 | }
28 |
29 | void TimeSeries::add(double val) {
30 | if (mCount < kHistorySize) {
31 | mValues[mCount++] = val;
32 | mSum += val;
33 | } else {
34 | mSum -= mValues[0];
35 | memmove(&mValues[0], &mValues[1], (kHistorySize - 1) * sizeof(double));
36 | mValues[kHistorySize - 1] = val;
37 | mSum += val;
38 | }
39 | }
40 |
41 | double TimeSeries::mean() const {
42 | if (mCount < 1) {
43 | return 0.0;
44 | }
45 |
46 | return mSum / mCount;
47 | }
48 |
49 | double TimeSeries::sdev() const {
50 | if (mCount < 1) {
51 | return 0.0;
52 | }
53 |
54 | double m = mean();
55 |
56 | double sum = 0.0;
57 | for (size_t i = 0; i < mCount; ++i) {
58 | double tmp = mValues[i] - m;
59 | tmp *= tmp;
60 |
61 | sum += tmp;
62 | }
63 |
64 | return sqrt(sum / mCount);
65 | }
66 |
67 | } // namespace android
68 |
--------------------------------------------------------------------------------
/native/wifi-display/TimeSeries.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef TIME_SERIES_H_
18 |
19 | #define TIME_SERIES_H_
20 |
21 | #include
22 |
23 | namespace android {
24 |
25 | struct TimeSeries {
26 | TimeSeries();
27 |
28 | void add(double val);
29 |
30 | double mean() const;
31 | double sdev() const;
32 |
33 | private:
34 | enum {
35 | kHistorySize = 20
36 | };
37 | double mValues[kHistorySize];
38 |
39 | size_t mCount;
40 | double mSum;
41 | };
42 |
43 | } // namespace android
44 |
45 | #endif // TIME_SERIES_H_
46 |
47 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/LinearRegression.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | //#define LOG_NDEBUG 0
18 | #define LOG_TAG "LinearRegression"
19 | #include
20 |
21 | #include "LinearRegression.h"
22 |
23 | #include
24 | #include
25 |
26 | namespace android {
27 |
28 | LinearRegression::LinearRegression(size_t historySize)
29 | : mHistorySize(historySize),
30 | mCount(0),
31 | mHistory(new Point[mHistorySize]),
32 | mSumX(0.0),
33 | mSumY(0.0) {
34 | }
35 |
36 | LinearRegression::~LinearRegression() {
37 | delete[] mHistory;
38 | mHistory = NULL;
39 | }
40 |
41 | void LinearRegression::addPoint(float x, float y) {
42 | if (mCount == mHistorySize) {
43 | const Point &oldest = mHistory[0];
44 |
45 | mSumX -= oldest.mX;
46 | mSumY -= oldest.mY;
47 |
48 | memmove(&mHistory[0], &mHistory[1], (mHistorySize - 1) * sizeof(Point));
49 | --mCount;
50 | }
51 |
52 | Point *newest = &mHistory[mCount++];
53 | newest->mX = x;
54 | newest->mY = y;
55 |
56 | mSumX += x;
57 | mSumY += y;
58 | }
59 |
60 | bool LinearRegression::approxLine(float *n1, float *n2, float *b) const {
61 | static const float kEpsilon = 1.0E-4;
62 |
63 | if (mCount < 2) {
64 | return false;
65 | }
66 |
67 | float sumX2 = 0.0f;
68 | float sumY2 = 0.0f;
69 | float sumXY = 0.0f;
70 |
71 | float meanX = mSumX / (float)mCount;
72 | float meanY = mSumY / (float)mCount;
73 |
74 | for (size_t i = 0; i < mCount; ++i) {
75 | const Point &p = mHistory[i];
76 |
77 | float x = p.mX - meanX;
78 | float y = p.mY - meanY;
79 |
80 | sumX2 += x * x;
81 | sumY2 += y * y;
82 | sumXY += x * y;
83 | }
84 |
85 | float T = sumX2 + sumY2;
86 | float D = sumX2 * sumY2 - sumXY * sumXY;
87 | float root = sqrt(T * T * 0.25 - D);
88 |
89 | float L1 = T * 0.5 - root;
90 |
91 | if (fabs(sumXY) > kEpsilon) {
92 | *n1 = 1.0;
93 | *n2 = (2.0 * L1 - sumX2) / sumXY;
94 |
95 | float mag = sqrt((*n1) * (*n1) + (*n2) * (*n2));
96 |
97 | *n1 /= mag;
98 | *n2 /= mag;
99 | } else {
100 | *n1 = 0.0;
101 | *n2 = 1.0;
102 | }
103 |
104 | *b = (*n1) * meanX + (*n2) * meanY;
105 |
106 | return true;
107 | }
108 |
109 | } // namespace android
110 |
111 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/LinearRegression.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef LINEAR_REGRESSION_H_
18 |
19 | #define LINEAR_REGRESSION_H_
20 |
21 | #include
22 | #include
23 |
24 | namespace android {
25 |
26 | // Helper class to fit a line to a set of points minimizing the sum of
27 | // squared (orthogonal) distances from line to individual points.
28 | struct LinearRegression {
29 | LinearRegression(size_t historySize);
30 | ~LinearRegression();
31 |
32 | void addPoint(float x, float y);
33 |
34 | bool approxLine(float *n1, float *n2, float *b) const;
35 |
36 | private:
37 | struct Point {
38 | float mX, mY;
39 | };
40 |
41 | size_t mHistorySize;
42 | size_t mCount;
43 | Point *mHistory;
44 |
45 | float mSumX, mSumY;
46 |
47 | DISALLOW_EVIL_CONSTRUCTORS(LinearRegression);
48 | };
49 |
50 | } // namespace android
51 |
52 | #endif // LINEAR_REGRESSION_H_
53 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/RTPSink.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #define LOG_NDEBUG 0
18 | #define LOG_TAG "RTPSink"
19 | #include
20 |
21 | #include "RTPSink.h"
22 |
23 | #include "ANetworkSession.h"
24 | #include "TunnelRenderer.h"
25 |
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | namespace android {
34 |
35 | struct RTPSink::Source : public RefBase {
36 | Source(uint16_t seq, const sp &buffer,
37 | const sp queueBufferMsg);
38 |
39 | bool updateSeq(uint16_t seq, const sp &buffer);
40 |
41 | void addReportBlock(uint32_t ssrc, const sp &buf);
42 |
43 | protected:
44 | virtual ~Source();
45 |
46 | private:
47 | static const uint32_t kMinSequential = 2;
48 | static const uint32_t kMaxDropout = 3000;
49 | static const uint32_t kMaxMisorder = 100;
50 | static const uint32_t kRTPSeqMod = 1u << 16;
51 |
52 | sp mQueueBufferMsg;
53 |
54 | uint16_t mMaxSeq;
55 | uint32_t mCycles;
56 | uint32_t mBaseSeq;
57 | uint32_t mBadSeq;
58 | uint32_t mProbation;
59 | uint32_t mReceived;
60 | uint32_t mExpectedPrior;
61 | uint32_t mReceivedPrior;
62 |
63 | void initSeq(uint16_t seq);
64 | void queuePacket(const sp &buffer);
65 |
66 | DISALLOW_EVIL_CONSTRUCTORS(Source);
67 | };
68 |
69 | ////////////////////////////////////////////////////////////////////////////////
70 |
71 | RTPSink::Source::Source(
72 | uint16_t seq, const sp &buffer,
73 | const sp queueBufferMsg)
74 | : mQueueBufferMsg(queueBufferMsg),
75 | mProbation(kMinSequential) {
76 | initSeq(seq);
77 | mMaxSeq = seq - 1;
78 |
79 | buffer->setInt32Data(mCycles | seq);
80 | queuePacket(buffer);
81 | }
82 |
83 | RTPSink::Source::~Source() {
84 | }
85 |
86 | void RTPSink::Source::initSeq(uint16_t seq) {
87 | mMaxSeq = seq;
88 | mCycles = 0;
89 | mBaseSeq = seq;
90 | mBadSeq = kRTPSeqMod + 1;
91 | mReceived = 0;
92 | mExpectedPrior = 0;
93 | mReceivedPrior = 0;
94 | }
95 |
96 | bool RTPSink::Source::updateSeq(uint16_t seq, const sp &buffer) {
97 | uint16_t udelta = seq - mMaxSeq;
98 |
99 | if (mProbation) {
100 | // Startup phase
101 |
102 | if (seq == mMaxSeq + 1) {
103 | buffer->setInt32Data(mCycles | seq);
104 | queuePacket(buffer);
105 |
106 | --mProbation;
107 | mMaxSeq = seq;
108 | if (mProbation == 0) {
109 | initSeq(seq);
110 | ++mReceived;
111 |
112 | return true;
113 | }
114 | } else {
115 | // Packet out of sequence, restart startup phase
116 |
117 | mProbation = kMinSequential - 1;
118 | mMaxSeq = seq;
119 |
120 | #if 0
121 | mPackets.clear();
122 | mTotalBytesQueued = 0;
123 | ALOGI("XXX cleared packets");
124 | #endif
125 |
126 | buffer->setInt32Data(mCycles | seq);
127 | queuePacket(buffer);
128 | }
129 |
130 | return false;
131 | }
132 |
133 | if (udelta < kMaxDropout) {
134 | // In order, with permissible gap.
135 |
136 | if (seq < mMaxSeq) {
137 | // Sequence number wrapped - count another 64K cycle
138 | mCycles += kRTPSeqMod;
139 | }
140 |
141 | mMaxSeq = seq;
142 | } else if (udelta <= kRTPSeqMod - kMaxMisorder) {
143 | // The sequence number made a very large jump
144 |
145 | if (seq == mBadSeq) {
146 | // Two sequential packets -- assume that the other side
147 | // restarted without telling us so just re-sync
148 | // (i.e. pretend this was the first packet)
149 |
150 | initSeq(seq);
151 | } else {
152 | mBadSeq = (seq + 1) & (kRTPSeqMod - 1);
153 |
154 | return false;
155 | }
156 | } else {
157 | // Duplicate or reordered packet.
158 | }
159 |
160 | ++mReceived;
161 |
162 | buffer->setInt32Data(mCycles | seq);
163 | queuePacket(buffer);
164 |
165 | return true;
166 | }
167 |
168 | void RTPSink::Source::queuePacket(const sp &buffer) {
169 | sp msg = mQueueBufferMsg->dup();
170 | msg->setBuffer("buffer", buffer);
171 | msg->post();
172 | }
173 |
174 | void RTPSink::Source::addReportBlock(
175 | uint32_t ssrc, const sp &buf) {
176 | uint32_t extMaxSeq = mMaxSeq | mCycles;
177 | uint32_t expected = extMaxSeq - mBaseSeq + 1;
178 |
179 | int64_t lost = (int64_t)expected - (int64_t)mReceived;
180 | if (lost > 0x7fffff) {
181 | lost = 0x7fffff;
182 | } else if (lost < -0x800000) {
183 | lost = -0x800000;
184 | }
185 |
186 | uint32_t expectedInterval = expected - mExpectedPrior;
187 | mExpectedPrior = expected;
188 |
189 | uint32_t receivedInterval = mReceived - mReceivedPrior;
190 | mReceivedPrior = mReceived;
191 |
192 | int64_t lostInterval = expectedInterval - receivedInterval;
193 |
194 | uint8_t fractionLost;
195 | if (expectedInterval == 0 || lostInterval <=0) {
196 | fractionLost = 0;
197 | } else {
198 | fractionLost = (lostInterval << 8) / expectedInterval;
199 | }
200 |
201 | uint8_t *ptr = buf->data() + buf->size();
202 |
203 | ptr[0] = ssrc >> 24;
204 | ptr[1] = (ssrc >> 16) & 0xff;
205 | ptr[2] = (ssrc >> 8) & 0xff;
206 | ptr[3] = ssrc & 0xff;
207 |
208 | ptr[4] = fractionLost;
209 |
210 | ptr[5] = (lost >> 16) & 0xff;
211 | ptr[6] = (lost >> 8) & 0xff;
212 | ptr[7] = lost & 0xff;
213 |
214 | ptr[8] = extMaxSeq >> 24;
215 | ptr[9] = (extMaxSeq >> 16) & 0xff;
216 | ptr[10] = (extMaxSeq >> 8) & 0xff;
217 | ptr[11] = extMaxSeq & 0xff;
218 |
219 | // XXX TODO:
220 |
221 | ptr[12] = 0x00; // interarrival jitter
222 | ptr[13] = 0x00;
223 | ptr[14] = 0x00;
224 | ptr[15] = 0x00;
225 |
226 | ptr[16] = 0x00; // last SR
227 | ptr[17] = 0x00;
228 | ptr[18] = 0x00;
229 | ptr[19] = 0x00;
230 |
231 | ptr[20] = 0x00; // delay since last SR
232 | ptr[21] = 0x00;
233 | ptr[22] = 0x00;
234 | ptr[23] = 0x00;
235 | }
236 |
237 | ////////////////////////////////////////////////////////////////////////////////
238 |
239 | RTPSink::RTPSink(
240 | const sp &netSession,
241 | const sp &surfaceTex)
242 | : mNetSession(netSession),
243 | mSurfaceTex(surfaceTex),
244 | mRTPPort(0),
245 | mRTPSessionID(0),
246 | mRTCPSessionID(0),
247 | mFirstArrivalTimeUs(-1ll),
248 | mNumPacketsReceived(0ll),
249 | mRegression(1000),
250 | mMaxDelayMs(-1ll),
251 | mIsConnectRemotePort(false) {
252 | }
253 |
254 | RTPSink::~RTPSink() {
255 | if (mRTCPSessionID != 0) {
256 | mNetSession->destroySession(mRTCPSessionID);
257 | }
258 |
259 | if (mRTPSessionID != 0) {
260 | mNetSession->destroySession(mRTPSessionID);
261 | }
262 | }
263 |
264 | status_t RTPSink::init(bool useTCPInterleaving) {
265 | if (useTCPInterleaving) {
266 | return OK;
267 | }
268 |
269 | int clientRtp;
270 |
271 | sp rtpNotify = new AMessage(kWhatRTPNotify, id());
272 | sp rtcpNotify = new AMessage(kWhatRTCPNotify, id());
273 | for (clientRtp = 15550;; clientRtp += 2) {
274 | int32_t rtpSession;
275 | status_t err = mNetSession->createUDPSession(
276 | clientRtp, rtpNotify, &rtpSession);
277 |
278 | if (err != OK) {
279 | ALOGI("failed to create RTP socket on port %d", clientRtp);
280 | continue;
281 | }
282 |
283 | int32_t rtcpSession;
284 | err = mNetSession->createUDPSession(
285 | clientRtp + 1, rtcpNotify, &rtcpSession);
286 |
287 | if (err == OK) {
288 | mRTPPort = clientRtp;
289 | mRTPSessionID = rtpSession;
290 | mRTCPSessionID = rtcpSession;
291 | break;
292 | }
293 |
294 | ALOGI("failed to create RTCP socket on port %d", clientRtp + 1);
295 | mNetSession->destroySession(rtpSession);
296 | }
297 |
298 | if (mRTPPort == 0) {
299 | return UNKNOWN_ERROR;
300 | }
301 |
302 | return OK;
303 | }
304 |
305 | int32_t RTPSink::getRTPPort() const {
306 | return mRTPPort;
307 | }
308 |
309 | void RTPSink::onMessageReceived(const sp &msg) {
310 | switch (msg->what()) {
311 | case kWhatRTPNotify:
312 | case kWhatRTCPNotify:
313 | {
314 | int32_t reason;
315 | CHECK(msg->findInt32("reason", &reason));
316 |
317 | switch (reason) {
318 | case ANetworkSession::kWhatError:
319 | {
320 | int32_t sessionID;
321 | CHECK(msg->findInt32("sessionID", &sessionID));
322 |
323 | int32_t err;
324 | CHECK(msg->findInt32("err", &err));
325 |
326 | AString detail;
327 | CHECK(msg->findString("detail", &detail));
328 |
329 | ALOGE("An error occurred in session %d (%d, '%s/%s').",
330 | sessionID,
331 | err,
332 | detail.c_str(),
333 | strerror(-err));
334 |
335 | mNetSession->destroySession(sessionID);
336 |
337 | if (sessionID == mRTPSessionID) {
338 | mRTPSessionID = 0;
339 | } else if (sessionID == mRTCPSessionID) {
340 | mRTCPSessionID = 0;
341 | }
342 | break;
343 | }
344 |
345 | case ANetworkSession::kWhatDatagram:
346 | {
347 | int32_t sessionID;
348 | CHECK(msg->findInt32("sessionID", &sessionID));
349 |
350 | sp data;
351 | CHECK(msg->findBuffer("data", &data));
352 |
353 | int32_t fromPort = 0;
354 | AString fromAddr;
355 | if (msg->findString("fromAddr", &fromAddr)
356 | && msg->findInt32("fromPort", &fromPort)) {
357 | if (!mIsConnectRemotePort) {
358 | connect(fromAddr.c_str(), fromPort, fromPort+1);
359 | }
360 | }
361 |
362 | status_t err;
363 | if (msg->what() == kWhatRTPNotify) {
364 | err = parseRTP(data);
365 | } else {
366 | err = parseRTCP(data);
367 | }
368 | break;
369 | }
370 |
371 | default:
372 | TRESPASS();
373 | }
374 | break;
375 | }
376 |
377 | case kWhatSendRR:
378 | {
379 | onSendRR();
380 | break;
381 | }
382 |
383 | case kWhatPacketLost:
384 | {
385 | onPacketLost(msg);
386 | break;
387 | }
388 |
389 | case kWhatInject:
390 | {
391 | int32_t isRTP;
392 | CHECK(msg->findInt32("isRTP", &isRTP));
393 |
394 | sp buffer;
395 | CHECK(msg->findBuffer("buffer", &buffer));
396 |
397 | status_t err;
398 | if (isRTP) {
399 | err = parseRTP(buffer);
400 | } else {
401 | err = parseRTCP(buffer);
402 | }
403 | break;
404 | }
405 |
406 | default:
407 | TRESPASS();
408 | }
409 | }
410 |
411 | status_t RTPSink::injectPacket(bool isRTP, const sp &buffer) {
412 | sp msg = new AMessage(kWhatInject, id());
413 | msg->setInt32("isRTP", isRTP);
414 | msg->setBuffer("buffer", buffer);
415 | msg->post();
416 |
417 | return OK;
418 | }
419 |
420 | status_t RTPSink::parseRTP(const sp &buffer) {
421 | size_t size = buffer->size();
422 | if (size < 12) {
423 | // Too short to be a valid RTP header.
424 | return ERROR_MALFORMED;
425 | }
426 |
427 | const uint8_t *data = buffer->data();
428 |
429 | if ((data[0] >> 6) != 2) {
430 | // Unsupported version.
431 | return ERROR_UNSUPPORTED;
432 | }
433 |
434 | if (data[0] & 0x20) {
435 | // Padding present.
436 |
437 | size_t paddingLength = data[size - 1];
438 |
439 | if (paddingLength + 12 > size) {
440 | // If we removed this much padding we'd end up with something
441 | // that's too short to be a valid RTP header.
442 | return ERROR_MALFORMED;
443 | }
444 |
445 | size -= paddingLength;
446 | }
447 |
448 | int numCSRCs = data[0] & 0x0f;
449 |
450 | size_t payloadOffset = 12 + 4 * numCSRCs;
451 |
452 | if (size < payloadOffset) {
453 | // Not enough data to fit the basic header and all the CSRC entries.
454 | return ERROR_MALFORMED;
455 | }
456 |
457 | if (data[0] & 0x10) {
458 | // Header eXtension present.
459 |
460 | if (size < payloadOffset + 4) {
461 | // Not enough data to fit the basic header, all CSRC entries
462 | // and the first 4 bytes of the extension header.
463 |
464 | return ERROR_MALFORMED;
465 | }
466 |
467 | const uint8_t *extensionData = &data[payloadOffset];
468 |
469 | size_t extensionLength =
470 | 4 * (extensionData[2] << 8 | extensionData[3]);
471 |
472 | if (size < payloadOffset + 4 + extensionLength) {
473 | return ERROR_MALFORMED;
474 | }
475 |
476 | payloadOffset += 4 + extensionLength;
477 | }
478 |
479 | uint32_t srcId = U32_AT(&data[8]);
480 | uint32_t rtpTime = U32_AT(&data[4]);
481 | uint16_t seqNo = U16_AT(&data[2]);
482 |
483 | int64_t arrivalTimeUs;
484 | CHECK(buffer->meta()->findInt64("arrivalTimeUs", &arrivalTimeUs));
485 |
486 | if (mFirstArrivalTimeUs < 0ll) {
487 | mFirstArrivalTimeUs = arrivalTimeUs;
488 | }
489 | arrivalTimeUs -= mFirstArrivalTimeUs;
490 |
491 | int64_t arrivalTimeMedia = (arrivalTimeUs * 9ll) / 100ll;
492 |
493 | ALOGV("seqNo: %d, SSRC 0x%08x, diff %lld",
494 | seqNo, srcId, rtpTime - arrivalTimeMedia);
495 |
496 | mRegression.addPoint((float)rtpTime, (float)arrivalTimeMedia);
497 |
498 | ++mNumPacketsReceived;
499 |
500 | float n1, n2, b;
501 | if (mRegression.approxLine(&n1, &n2, &b)) {
502 | ALOGV("Line %lld: %.2f %.2f %.2f, slope %.2f",
503 | mNumPacketsReceived, n1, n2, b, -n1 / n2);
504 |
505 | float expectedArrivalTimeMedia = (b - n1 * (float)rtpTime) / n2;
506 | float latenessMs = (arrivalTimeMedia - expectedArrivalTimeMedia) / 90.0;
507 |
508 | if (mMaxDelayMs < 0ll || latenessMs > mMaxDelayMs) {
509 | mMaxDelayMs = latenessMs;
510 | ALOGI("packet was %.2f ms late", latenessMs);
511 | }
512 | }
513 |
514 | sp meta = buffer->meta();
515 | meta->setInt32("ssrc", srcId);
516 | meta->setInt32("rtp-time", rtpTime);
517 | meta->setInt32("PT", data[1] & 0x7f);
518 | meta->setInt32("M", data[1] >> 7);
519 |
520 | buffer->setRange(payloadOffset, size - payloadOffset);
521 |
522 | ssize_t index = mSources.indexOfKey(srcId);
523 | if (index < 0) {
524 | if (mRenderer == NULL) {
525 | sp notifyLost = new AMessage(kWhatPacketLost, id());
526 | notifyLost->setInt32("ssrc", srcId);
527 |
528 | mRenderer = new TunnelRenderer(notifyLost, mSurfaceTex);
529 | looper()->registerHandler(mRenderer);
530 | }
531 |
532 | sp queueBufferMsg =
533 | new AMessage(TunnelRenderer::kWhatQueueBuffer, mRenderer->id());
534 |
535 | sp source = new Source(seqNo, buffer, queueBufferMsg);
536 | mSources.add(srcId, source);
537 | } else {
538 | mSources.valueAt(index)->updateSeq(seqNo, buffer);
539 | }
540 |
541 | return OK;
542 | }
543 |
544 | status_t RTPSink::parseRTCP(const sp &buffer) {
545 | const uint8_t *data = buffer->data();
546 | size_t size = buffer->size();
547 |
548 | while (size > 0) {
549 | if (size < 8) {
550 | // Too short to be a valid RTCP header
551 | return ERROR_MALFORMED;
552 | }
553 |
554 | if ((data[0] >> 6) != 2) {
555 | // Unsupported version.
556 | return ERROR_UNSUPPORTED;
557 | }
558 |
559 | if (data[0] & 0x20) {
560 | // Padding present.
561 |
562 | size_t paddingLength = data[size - 1];
563 |
564 | if (paddingLength + 12 > size) {
565 | // If we removed this much padding we'd end up with something
566 | // that's too short to be a valid RTP header.
567 | return ERROR_MALFORMED;
568 | }
569 |
570 | size -= paddingLength;
571 | }
572 |
573 | size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
574 |
575 | if (size < headerLength) {
576 | // Only received a partial packet?
577 | return ERROR_MALFORMED;
578 | }
579 |
580 | switch (data[1]) {
581 | case 200:
582 | {
583 | parseSR(data, headerLength);
584 | break;
585 | }
586 |
587 | case 201: // RR
588 | case 202: // SDES
589 | case 204: // APP
590 | break;
591 |
592 | case 205: // TSFB (transport layer specific feedback)
593 | case 206: // PSFB (payload specific feedback)
594 | // hexdump(data, headerLength);
595 | break;
596 |
597 | case 203:
598 | {
599 | parseBYE(data, headerLength);
600 | break;
601 | }
602 |
603 | default:
604 | {
605 | ALOGW("Unknown RTCP packet type %u of size %d",
606 | (unsigned)data[1], headerLength);
607 | break;
608 | }
609 | }
610 |
611 | data += headerLength;
612 | size -= headerLength;
613 | }
614 |
615 | return OK;
616 | }
617 |
618 | status_t RTPSink::parseBYE(const uint8_t *data, size_t size) {
619 | size_t SC = data[0] & 0x3f;
620 |
621 | if (SC == 0 || size < (4 + SC * 4)) {
622 | // Packet too short for the minimal BYE header.
623 | return ERROR_MALFORMED;
624 | }
625 |
626 | uint32_t id = U32_AT(&data[4]);
627 |
628 | return OK;
629 | }
630 |
631 | status_t RTPSink::parseSR(const uint8_t *data, size_t size) {
632 | size_t RC = data[0] & 0x1f;
633 |
634 | if (size < (7 + RC * 6) * 4) {
635 | // Packet too short for the minimal SR header.
636 | return ERROR_MALFORMED;
637 | }
638 |
639 | uint32_t id = U32_AT(&data[4]);
640 | uint64_t ntpTime = U64_AT(&data[8]);
641 | uint32_t rtpTime = U32_AT(&data[16]);
642 |
643 | ALOGV("SR: ssrc 0x%08x, ntpTime 0x%016llx, rtpTime 0x%08x",
644 | id, ntpTime, rtpTime);
645 |
646 | return OK;
647 | }
648 |
649 | status_t RTPSink::connect(
650 | const char *host, int32_t remoteRtpPort, int32_t remoteRtcpPort) {
651 | ALOGI("connecting RTP/RTCP sockets to %s:{%d,%d}",
652 | host, remoteRtpPort, remoteRtcpPort);
653 | mIsConnectRemotePort = true;
654 |
655 | status_t err =
656 | mNetSession->connectUDPSession(mRTPSessionID, host, remoteRtpPort);
657 |
658 | if (err != OK) {
659 | ALOGE("cant connect UDPSession, err = %d, mRTPSessionID = %d, host = %s, remoteRtpPort = %d",
660 | err, mRTPSessionID, host, remoteRtpPort);
661 | return err;
662 | }
663 |
664 | err = mNetSession->connectUDPSession(mRTCPSessionID, host, remoteRtcpPort);
665 |
666 | if (err != OK) {
667 | ALOGE("cant connect UDPSession, err = %d, mRTPSessionID = %d, host = %s, remoteRtcpPort = %d",
668 | err, mRTPSessionID, host, remoteRtcpPort);
669 | return err;
670 | }
671 |
672 | #if 0
673 | sp buf = new ABuffer(1500);
674 | memset(buf->data(), 0, buf->size());
675 |
676 | mNetSession->sendRequest(
677 | mRTPSessionID, buf->data(), buf->size());
678 |
679 | mNetSession->sendRequest(
680 | mRTCPSessionID, buf->data(), buf->size());
681 | #endif
682 |
683 | scheduleSendRR();
684 |
685 | return OK;
686 | }
687 |
688 | void RTPSink::scheduleSendRR() {
689 | (new AMessage(kWhatSendRR, id()))->post(2000000ll);
690 | }
691 |
692 | void RTPSink::addSDES(const sp &buffer) {
693 | uint8_t *data = buffer->data() + buffer->size();
694 | data[0] = 0x80 | 1;
695 | data[1] = 202; // SDES
696 | data[4] = 0xde; // SSRC
697 | data[5] = 0xad;
698 | data[6] = 0xbe;
699 | data[7] = 0xef;
700 |
701 | size_t offset = 8;
702 |
703 | data[offset++] = 1; // CNAME
704 |
705 | AString cname = "stagefright@somewhere";
706 | data[offset++] = cname.size();
707 |
708 | memcpy(&data[offset], cname.c_str(), cname.size());
709 | offset += cname.size();
710 |
711 | data[offset++] = 6; // TOOL
712 |
713 | AString tool = "stagefright/1.0";
714 | data[offset++] = tool.size();
715 |
716 | memcpy(&data[offset], tool.c_str(), tool.size());
717 | offset += tool.size();
718 |
719 | data[offset++] = 0;
720 |
721 | if ((offset % 4) > 0) {
722 | size_t count = 4 - (offset % 4);
723 | switch (count) {
724 | case 3:
725 | data[offset++] = 0;
726 | case 2:
727 | data[offset++] = 0;
728 | case 1:
729 | data[offset++] = 0;
730 | }
731 | }
732 |
733 | size_t numWords = (offset / 4) - 1;
734 | data[2] = numWords >> 8;
735 | data[3] = numWords & 0xff;
736 |
737 | buffer->setRange(buffer->offset(), buffer->size() + offset);
738 | }
739 |
740 | void RTPSink::onSendRR() {
741 | sp buf = new ABuffer(1500);
742 | buf->setRange(0, 0);
743 |
744 | uint8_t *ptr = buf->data();
745 | ptr[0] = 0x80 | 0;
746 | ptr[1] = 201; // RR
747 | ptr[2] = 0;
748 | ptr[3] = 1;
749 | ptr[4] = 0xde; // SSRC
750 | ptr[5] = 0xad;
751 | ptr[6] = 0xbe;
752 | ptr[7] = 0xef;
753 |
754 | buf->setRange(0, 8);
755 |
756 | size_t numReportBlocks = 0;
757 | for (size_t i = 0; i < mSources.size(); ++i) {
758 | uint32_t ssrc = mSources.keyAt(i);
759 | sp source = mSources.valueAt(i);
760 |
761 | if (numReportBlocks > 31 || buf->size() + 24 > buf->capacity()) {
762 | // Cannot fit another report block.
763 | break;
764 | }
765 |
766 | source->addReportBlock(ssrc, buf);
767 | ++numReportBlocks;
768 | }
769 |
770 | ptr[0] |= numReportBlocks; // 5 bit
771 |
772 | size_t sizeInWordsMinus1 = 1 + 6 * numReportBlocks;
773 | ptr[2] = sizeInWordsMinus1 >> 8;
774 | ptr[3] = sizeInWordsMinus1 & 0xff;
775 |
776 | buf->setRange(0, (sizeInWordsMinus1 + 1) * 4);
777 |
778 | addSDES(buf);
779 |
780 | mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
781 |
782 | scheduleSendRR();
783 | }
784 |
785 | void RTPSink::onPacketLost(const sp &msg) {
786 | uint32_t srcId;
787 | CHECK(msg->findInt32("ssrc", (int32_t *)&srcId));
788 |
789 | int32_t seqNo;
790 | CHECK(msg->findInt32("seqNo", &seqNo));
791 |
792 | int32_t blp = 0;
793 |
794 | sp buf = new ABuffer(1500);
795 | buf->setRange(0, 0);
796 |
797 | uint8_t *ptr = buf->data();
798 | ptr[0] = 0x80 | 1; // generic NACK
799 | ptr[1] = 205; // RTPFB
800 | ptr[2] = 0;
801 | ptr[3] = 3;
802 | ptr[4] = 0xde; // sender SSRC
803 | ptr[5] = 0xad;
804 | ptr[6] = 0xbe;
805 | ptr[7] = 0xef;
806 | ptr[8] = (srcId >> 24) & 0xff;
807 | ptr[9] = (srcId >> 16) & 0xff;
808 | ptr[10] = (srcId >> 8) & 0xff;
809 | ptr[11] = (srcId & 0xff);
810 | ptr[12] = (seqNo >> 8) & 0xff;
811 | ptr[13] = (seqNo & 0xff);
812 | ptr[14] = (blp >> 8) & 0xff;
813 | ptr[15] = (blp & 0xff);
814 |
815 | buf->setRange(0, 16);
816 |
817 | mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
818 | }
819 |
820 | } // namespace android
821 |
822 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/RTPSink.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef RTP_SINK_H_
18 |
19 | #define RTP_SINK_H_
20 |
21 | #include
22 |
23 | #include "LinearRegression.h"
24 |
25 | #include
26 |
27 | namespace android {
28 |
29 | struct ABuffer;
30 | struct ANetworkSession;
31 | struct TunnelRenderer;
32 |
33 | // Creates a pair of sockets for RTP/RTCP traffic, instantiates a renderer
34 | // for incoming transport stream data and occasionally sends statistics over
35 | // the RTCP channel.
36 | struct RTPSink : public AHandler {
37 | RTPSink(const sp &netSession,
38 | const sp &surfaceTex);
39 |
40 | // If TCP interleaving is used, no UDP sockets are created, instead
41 | // incoming RTP/RTCP packets (arriving on the RTSP control connection)
42 | // are manually injected by WifiDisplaySink.
43 | status_t init(bool useTCPInterleaving);
44 |
45 | status_t connect(
46 | const char *host, int32_t remoteRtpPort, int32_t remoteRtcpPort);
47 |
48 | int32_t getRTPPort() const;
49 |
50 | status_t injectPacket(bool isRTP, const sp &buffer);
51 |
52 | protected:
53 | virtual void onMessageReceived(const sp &msg);
54 | virtual ~RTPSink();
55 |
56 | private:
57 | enum {
58 | kWhatRTPNotify,
59 | kWhatRTCPNotify,
60 | kWhatSendRR,
61 | kWhatPacketLost,
62 | kWhatInject,
63 | };
64 |
65 | struct Source;
66 | struct StreamSource;
67 |
68 | sp mNetSession;
69 | sp mSurfaceTex;
70 | KeyedVector > mSources;
71 |
72 | int32_t mRTPPort;
73 | int32_t mRTPSessionID;
74 | int32_t mRTCPSessionID;
75 |
76 | int64_t mFirstArrivalTimeUs;
77 | int64_t mNumPacketsReceived;
78 | LinearRegression mRegression;
79 | int64_t mMaxDelayMs;
80 |
81 | sp mRenderer;
82 |
83 | bool mIsConnectRemotePort;
84 |
85 | status_t parseRTP(const sp &buffer);
86 | status_t parseRTCP(const sp &buffer);
87 | status_t parseBYE(const uint8_t *data, size_t size);
88 | status_t parseSR(const uint8_t *data, size_t size);
89 |
90 | void addSDES(const sp &buffer);
91 | void onSendRR();
92 | void onPacketLost(const sp &msg);
93 | void scheduleSendRR();
94 |
95 | DISALLOW_EVIL_CONSTRUCTORS(RTPSink);
96 | };
97 |
98 | } // namespace android
99 |
100 | #endif // RTP_SINK_H_
101 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/TunnelRenderer.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #define LOG_NDEBUG 0
18 | #define LOG_TAG "TunnelRenderer"
19 | #include
20 |
21 | #include "TunnelRenderer.h"
22 |
23 | #include "ATSParser.h"
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 |
35 | namespace android {
36 |
37 | struct TunnelRenderer::PlayerClient : public BnMediaPlayerClient {
38 | PlayerClient() {}
39 |
40 | virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) {
41 | ALOGI("notify %d, %d, %d", msg, ext1, ext2);
42 | }
43 |
44 | protected:
45 | virtual ~PlayerClient() {}
46 |
47 | private:
48 | DISALLOW_EVIL_CONSTRUCTORS(PlayerClient);
49 | };
50 |
51 | struct TunnelRenderer::StreamSource : public BnStreamSource {
52 | StreamSource(TunnelRenderer *owner);
53 |
54 | virtual void setListener(const sp &listener);
55 | virtual void setBuffers(const Vector > &buffers);
56 |
57 | virtual void onBufferAvailable(size_t index);
58 |
59 | virtual uint32_t flags() const;
60 |
61 | void doSomeWork();
62 |
63 | protected:
64 | virtual ~StreamSource();
65 |
66 | private:
67 | mutable Mutex mLock;
68 |
69 | TunnelRenderer *mOwner;
70 |
71 | sp mListener;
72 |
73 | Vector > mBuffers;
74 | List mIndicesAvailable;
75 |
76 | size_t mNumDeqeued;
77 |
78 | DISALLOW_EVIL_CONSTRUCTORS(StreamSource);
79 | };
80 |
81 | ////////////////////////////////////////////////////////////////////////////////
82 |
83 | TunnelRenderer::StreamSource::StreamSource(TunnelRenderer *owner)
84 | : mOwner(owner),
85 | mNumDeqeued(0) {
86 | }
87 |
88 | TunnelRenderer::StreamSource::~StreamSource() {
89 | }
90 |
91 | void TunnelRenderer::StreamSource::setListener(
92 | const sp &listener) {
93 | mListener = listener;
94 | }
95 |
96 | void TunnelRenderer::StreamSource::setBuffers(
97 | const Vector > &buffers) {
98 | mBuffers = buffers;
99 | }
100 |
101 | void TunnelRenderer::StreamSource::onBufferAvailable(size_t index) {
102 | CHECK_LT(index, mBuffers.size());
103 |
104 | {
105 | Mutex::Autolock autoLock(mLock);
106 | mIndicesAvailable.push_back(index);
107 | }
108 |
109 | doSomeWork();
110 | }
111 |
112 | uint32_t TunnelRenderer::StreamSource::flags() const {
113 | return kFlagAlignedVideoData;
114 | }
115 |
116 | void TunnelRenderer::StreamSource::doSomeWork() {
117 | Mutex::Autolock autoLock(mLock);
118 |
119 | while (!mIndicesAvailable.empty()) {
120 | sp srcBuffer = mOwner->dequeueBuffer();
121 | if (srcBuffer == NULL) {
122 | break;
123 | }
124 |
125 | ++mNumDeqeued;
126 |
127 | if (mNumDeqeued == 1) {
128 | ALOGI("fixing real time now.");
129 |
130 | sp extra = new AMessage;
131 |
132 | extra->setInt32(
133 | IStreamListener::kKeyDiscontinuityMask,
134 | ATSParser::DISCONTINUITY_ABSOLUTE_TIME);
135 |
136 | extra->setInt64("timeUs", ALooper::GetNowUs());
137 |
138 | mListener->issueCommand(
139 | IStreamListener::DISCONTINUITY,
140 | false /* synchronous */,
141 | extra);
142 | }
143 |
144 | ALOGV("dequeue TS packet of size %d", srcBuffer->size());
145 |
146 | size_t index = *mIndicesAvailable.begin();
147 | mIndicesAvailable.erase(mIndicesAvailable.begin());
148 |
149 | sp mem = mBuffers.itemAt(index);
150 | CHECK_LE(srcBuffer->size(), mem->size());
151 | CHECK_EQ((srcBuffer->size() % 188), 0u);
152 |
153 | memcpy(mem->pointer(), srcBuffer->data(), srcBuffer->size());
154 | mListener->queueBuffer(index, srcBuffer->size());
155 | }
156 | }
157 |
158 | ////////////////////////////////////////////////////////////////////////////////
159 |
160 | TunnelRenderer::TunnelRenderer(
161 | const sp ¬ifyLost,
162 | const sp &surfaceTex)
163 | : mNotifyLost(notifyLost),
164 | mSurfaceTex(surfaceTex),
165 | mTotalBytesQueued(0ll),
166 | mLastDequeuedExtSeqNo(-1),
167 | mFirstFailedAttemptUs(-1ll),
168 | mRequestedRetransmission(false) {
169 | }
170 |
171 | TunnelRenderer::~TunnelRenderer() {
172 | destroyPlayer();
173 | }
174 |
175 | void TunnelRenderer::queueBuffer(const sp &buffer) {
176 | Mutex::Autolock autoLock(mLock);
177 |
178 | mTotalBytesQueued += buffer->size();
179 |
180 | if (mPackets.empty()) {
181 | mPackets.push_back(buffer);
182 | return;
183 | }
184 |
185 | int32_t newExtendedSeqNo = buffer->int32Data();
186 |
187 | List >::iterator firstIt = mPackets.begin();
188 | List >::iterator it = --mPackets.end();
189 | for (;;) {
190 | int32_t extendedSeqNo = (*it)->int32Data();
191 |
192 | if (extendedSeqNo == newExtendedSeqNo) {
193 | // Duplicate packet.
194 | return;
195 | }
196 |
197 | if (extendedSeqNo < newExtendedSeqNo) {
198 | // Insert new packet after the one at "it".
199 | mPackets.insert(++it, buffer);
200 | return;
201 | }
202 |
203 | if (it == firstIt) {
204 | // Insert new packet before the first existing one.
205 | mPackets.insert(it, buffer);
206 | return;
207 | }
208 |
209 | --it;
210 | }
211 | }
212 |
213 | sp TunnelRenderer::dequeueBuffer() {
214 | Mutex::Autolock autoLock(mLock);
215 |
216 | sp buffer;
217 | int32_t extSeqNo;
218 | while (!mPackets.empty()) {
219 | buffer = *mPackets.begin();
220 | extSeqNo = buffer->int32Data();
221 |
222 | if (mLastDequeuedExtSeqNo < 0 || extSeqNo > mLastDequeuedExtSeqNo) {
223 | break;
224 | }
225 |
226 | // This is a retransmission of a packet we've already returned.
227 |
228 | mTotalBytesQueued -= buffer->size();
229 | buffer.clear();
230 | extSeqNo = -1;
231 |
232 | mPackets.erase(mPackets.begin());
233 | }
234 |
235 | if (mPackets.empty()) {
236 | if (mFirstFailedAttemptUs < 0ll) {
237 | mFirstFailedAttemptUs = ALooper::GetNowUs();
238 | mRequestedRetransmission = false;
239 | } else {
240 | ALOGV("no packets available for %.2f secs",
241 | (ALooper::GetNowUs() - mFirstFailedAttemptUs) / 1E6);
242 | }
243 |
244 | return NULL;
245 | }
246 |
247 | if (mLastDequeuedExtSeqNo < 0 || extSeqNo == mLastDequeuedExtSeqNo + 1) {
248 | if (mRequestedRetransmission) {
249 | ALOGI("Recovered after requesting retransmission of %d",
250 | extSeqNo);
251 | }
252 |
253 | mLastDequeuedExtSeqNo = extSeqNo;
254 | mFirstFailedAttemptUs = -1ll;
255 | mRequestedRetransmission = false;
256 |
257 | mPackets.erase(mPackets.begin());
258 |
259 | mTotalBytesQueued -= buffer->size();
260 |
261 | return buffer;
262 | }
263 |
264 | if (mFirstFailedAttemptUs < 0ll) {
265 | mFirstFailedAttemptUs = ALooper::GetNowUs();
266 |
267 | ALOGI("failed to get the correct packet the first time.");
268 | return NULL;
269 | }
270 |
271 | if (mFirstFailedAttemptUs + 50000ll > ALooper::GetNowUs()) {
272 | // We're willing to wait a little while to get the right packet.
273 |
274 | if (!mRequestedRetransmission) {
275 | ALOGI("requesting retransmission of seqNo %d",
276 | (mLastDequeuedExtSeqNo + 1) & 0xffff);
277 |
278 | sp notify = mNotifyLost->dup();
279 | notify->setInt32("seqNo", (mLastDequeuedExtSeqNo + 1) & 0xffff);
280 | notify->post();
281 |
282 | mRequestedRetransmission = true;
283 | } else {
284 | ALOGI("still waiting for the correct packet to arrive.");
285 | }
286 |
287 | return NULL;
288 | }
289 |
290 | ALOGI("dropping packet. extSeqNo %d didn't arrive in time",
291 | mLastDequeuedExtSeqNo + 1);
292 |
293 | // Permanent failure, we never received the packet.
294 | mLastDequeuedExtSeqNo = extSeqNo;
295 | mFirstFailedAttemptUs = -1ll;
296 | mRequestedRetransmission = false;
297 |
298 | mTotalBytesQueued -= buffer->size();
299 |
300 | mPackets.erase(mPackets.begin());
301 |
302 | return buffer;
303 | }
304 |
305 | void TunnelRenderer::onMessageReceived(const sp &msg) {
306 | switch (msg->what()) {
307 | case kWhatQueueBuffer:
308 | {
309 | sp buffer;
310 | CHECK(msg->findBuffer("buffer", &buffer));
311 |
312 | queueBuffer(buffer);
313 |
314 | if (mStreamSource == NULL) {
315 | if (mTotalBytesQueued > 0ll) {
316 | initPlayer();
317 | } else {
318 | ALOGI("Have %lld bytes queued...", mTotalBytesQueued);
319 | }
320 | } else {
321 | mStreamSource->doSomeWork();
322 | }
323 | break;
324 | }
325 |
326 | default:
327 | TRESPASS();
328 | }
329 | }
330 |
331 | void TunnelRenderer::initPlayer() {
332 | if (mSurfaceTex == NULL) {
333 | mComposerClient = new SurfaceComposerClient;
334 | CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
335 |
336 | DisplayInfo info;
337 | status_t st = SurfaceComposerClient::getDisplayInfo(0, &info);
338 | ALOGD("status = %d, w = %u, h = %u, xdpi = %f, ydpi = %f, fps = %f, density = %f",
339 | st, info.w, info.h,
340 | info.xdpi, info.ydpi,
341 | info.fps, info.density);
342 |
343 | ssize_t displayWidth = info.w;
344 | ssize_t displayHeight = info.h;
345 |
346 | if (st != OK) {
347 | displayWidth = 1920;
348 | displayHeight = 1080;
349 | }
350 |
351 | mSurfaceControl =
352 | mComposerClient->createSurface(
353 | String8("A Surface"),
354 | displayWidth,
355 | displayHeight,
356 | PIXEL_FORMAT_RGB_565,
357 | 0);
358 |
359 | CHECK(mSurfaceControl != NULL);
360 | CHECK(mSurfaceControl->isValid());
361 |
362 | SurfaceComposerClient::openGlobalTransaction();
363 | CHECK_EQ(mSurfaceControl->setLayer(INT_MAX), (status_t)OK);
364 | CHECK_EQ(mSurfaceControl->show(), (status_t)OK);
365 | SurfaceComposerClient::closeGlobalTransaction();
366 |
367 | mSurface = mSurfaceControl->getSurface();
368 | CHECK(mSurface != NULL);
369 | }
370 |
371 | sp sm = defaultServiceManager();
372 | sp binder = sm->getService(String16("media.player"));
373 | sp service = interface_cast(binder);
374 | CHECK(service.get() != NULL);
375 |
376 | mStreamSource = new StreamSource(this);
377 |
378 | mPlayerClient = new PlayerClient;
379 |
380 | mPlayer = service->create(getpid(), mPlayerClient, 0);
381 | CHECK(mPlayer != NULL);
382 | CHECK_EQ(mPlayer->setDataSource(mStreamSource), (status_t)OK);
383 |
384 | mPlayer->setVideoSurfaceTexture(
385 | mSurfaceTex != NULL ? mSurfaceTex : mSurface->getSurfaceTexture());
386 |
387 | mPlayer->start();
388 | }
389 |
390 | void TunnelRenderer::destroyPlayer() {
391 | mStreamSource.clear();
392 |
393 | mPlayer->stop();
394 | mPlayer.clear();
395 |
396 | if (mSurfaceTex == NULL) {
397 | mSurface.clear();
398 | mSurfaceControl.clear();
399 |
400 | mComposerClient->dispose();
401 | mComposerClient.clear();
402 | }
403 | }
404 |
405 | } // namespace android
406 |
407 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/TunnelRenderer.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef TUNNEL_RENDERER_H_
18 |
19 | #define TUNNEL_RENDERER_H_
20 |
21 | #include
22 | #include
23 |
24 | namespace android {
25 |
26 | struct ABuffer;
27 | struct SurfaceComposerClient;
28 | struct SurfaceControl;
29 | struct Surface;
30 | struct IMediaPlayer;
31 | struct IStreamListener;
32 |
33 | // This class reassembles incoming RTP packets into the correct order
34 | // and sends the resulting transport stream to a mediaplayer instance
35 | // for playback.
36 | struct TunnelRenderer : public AHandler {
37 | TunnelRenderer(
38 | const sp ¬ifyLost,
39 | const sp &surfaceTex);
40 |
41 | sp dequeueBuffer();
42 |
43 | enum {
44 | kWhatQueueBuffer,
45 | };
46 |
47 | protected:
48 | virtual void onMessageReceived(const sp &msg);
49 | virtual ~TunnelRenderer();
50 |
51 | private:
52 | struct PlayerClient;
53 | struct StreamSource;
54 |
55 | mutable Mutex mLock;
56 |
57 | sp mNotifyLost;
58 | sp mSurfaceTex;
59 |
60 | List > mPackets;
61 | int64_t mTotalBytesQueued;
62 |
63 | sp mComposerClient;
64 | sp mSurfaceControl;
65 | sp mSurface;
66 | sp mPlayerClient;
67 | sp mPlayer;
68 | sp mStreamSource;
69 |
70 | int32_t mLastDequeuedExtSeqNo;
71 | int64_t mFirstFailedAttemptUs;
72 | bool mRequestedRetransmission;
73 |
74 | void initPlayer();
75 | void destroyPlayer();
76 |
77 | void queueBuffer(const sp &buffer);
78 |
79 | DISALLOW_EVIL_CONSTRUCTORS(TunnelRenderer);
80 | };
81 |
82 | } // namespace android
83 |
84 | #endif // TUNNEL_RENDERER_H_
85 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/WifiDisplaySink.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | //#define LOG_NDEBUG 0
18 | #define LOG_TAG "WifiDisplaySink"
19 | #include
20 |
21 | #include "WifiDisplaySink.h"
22 | #include "ParsedMessage.h"
23 | #include "RTPSink.h"
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | namespace android {
31 |
32 | WifiDisplaySink::WifiDisplaySink(
33 | const sp &netSession,
34 | const sp &surfaceTex)
35 | : mState(UNDEFINED),
36 | mNetSession(netSession),
37 | mSurfaceTex(surfaceTex),
38 | mSessionID(0),
39 | mNextCSeq(1) {
40 | }
41 |
42 | WifiDisplaySink::~WifiDisplaySink() {
43 | }
44 |
45 | void WifiDisplaySink::start(const char *sourceHost, int32_t sourcePort) {
46 | sp msg = new AMessage(kWhatStart, id());
47 | msg->setString("sourceHost", sourceHost);
48 | msg->setInt32("sourcePort", sourcePort);
49 | msg->post();
50 | }
51 |
52 | void WifiDisplaySink::start(const char *uri) {
53 | sp msg = new AMessage(kWhatStart, id());
54 | msg->setString("setupURI", uri);
55 | msg->post();
56 | }
57 |
58 | // static
59 | bool WifiDisplaySink::ParseURL(
60 | const char *url, AString *host, int32_t *port, AString *path,
61 | AString *user, AString *pass) {
62 | host->clear();
63 | *port = 0;
64 | path->clear();
65 | user->clear();
66 | pass->clear();
67 |
68 | if (strncasecmp("rtsp://", url, 7)) {
69 | return false;
70 | }
71 |
72 | const char *slashPos = strchr(&url[7], '/');
73 |
74 | if (slashPos == NULL) {
75 | host->setTo(&url[7]);
76 | path->setTo("/");
77 | } else {
78 | host->setTo(&url[7], slashPos - &url[7]);
79 | path->setTo(slashPos);
80 | }
81 |
82 | ssize_t atPos = host->find("@");
83 |
84 | if (atPos >= 0) {
85 | // Split of user:pass@ from hostname.
86 |
87 | AString userPass(*host, 0, atPos);
88 | host->erase(0, atPos + 1);
89 |
90 | ssize_t colonPos = userPass.find(":");
91 |
92 | if (colonPos < 0) {
93 | *user = userPass;
94 | } else {
95 | user->setTo(userPass, 0, colonPos);
96 | pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1);
97 | }
98 | }
99 |
100 | const char *colonPos = strchr(host->c_str(), ':');
101 |
102 | if (colonPos != NULL) {
103 | char *end;
104 | unsigned long x = strtoul(colonPos + 1, &end, 10);
105 |
106 | if (end == colonPos + 1 || *end != '\0' || x >= 65536) {
107 | return false;
108 | }
109 |
110 | *port = x;
111 |
112 | size_t colonOffset = colonPos - host->c_str();
113 | size_t trailing = host->size() - colonOffset;
114 | host->erase(colonOffset, trailing);
115 | } else {
116 | *port = 554;
117 | }
118 |
119 | return true;
120 | }
121 |
122 | void WifiDisplaySink::onMessageReceived(const sp &msg) {
123 | switch (msg->what()) {
124 | case kWhatStart:
125 | {
126 | int32_t sourcePort;
127 |
128 | if (msg->findString("setupURI", &mSetupURI)) {
129 | AString path, user, pass;
130 | CHECK(ParseURL(
131 | mSetupURI.c_str(),
132 | &mRTSPHost, &sourcePort, &path, &user, &pass)
133 | && user.empty() && pass.empty());
134 | } else {
135 | CHECK(msg->findString("sourceHost", &mRTSPHost));
136 | CHECK(msg->findInt32("sourcePort", &sourcePort));
137 | }
138 |
139 | sp notify = new AMessage(kWhatRTSPNotify, id());
140 |
141 | status_t err = mNetSession->createRTSPClient(
142 | mRTSPHost.c_str(), sourcePort, notify, &mSessionID);
143 | CHECK_EQ(err, (status_t)OK);
144 |
145 | mState = CONNECTING;
146 | break;
147 | }
148 |
149 | case kWhatRTSPNotify:
150 | {
151 | int32_t reason;
152 | CHECK(msg->findInt32("reason", &reason));
153 |
154 | switch (reason) {
155 | case ANetworkSession::kWhatError:
156 | {
157 | int32_t sessionID;
158 | CHECK(msg->findInt32("sessionID", &sessionID));
159 |
160 | int32_t err;
161 | CHECK(msg->findInt32("err", &err));
162 |
163 | AString detail;
164 | CHECK(msg->findString("detail", &detail));
165 |
166 | ALOGE("An error occurred in session %d (%d, '%s/%s').",
167 | sessionID,
168 | err,
169 | detail.c_str(),
170 | strerror(-err));
171 |
172 | if (sessionID == mSessionID) {
173 | ALOGI("Lost control connection.");
174 |
175 | // The control connection is dead now.
176 | mNetSession->destroySession(mSessionID);
177 | mSessionID = 0;
178 |
179 | looper()->stop();
180 | }
181 | break;
182 | }
183 |
184 | case ANetworkSession::kWhatConnected:
185 | {
186 | ALOGI("We're now connected.");
187 | mState = CONNECTED;
188 |
189 | if (!mSetupURI.empty()) {
190 | status_t err =
191 | sendDescribe(mSessionID, mSetupURI.c_str());
192 |
193 | CHECK_EQ(err, (status_t)OK);
194 | }
195 | break;
196 | }
197 |
198 | case ANetworkSession::kWhatData:
199 | {
200 | onReceiveClientData(msg);
201 | break;
202 | }
203 |
204 | case ANetworkSession::kWhatBinaryData:
205 | {
206 | CHECK(sUseTCPInterleaving);
207 |
208 | int32_t channel;
209 | CHECK(msg->findInt32("channel", &channel));
210 |
211 | sp data;
212 | CHECK(msg->findBuffer("data", &data));
213 |
214 | mRTPSink->injectPacket(channel == 0 /* isRTP */, data);
215 | break;
216 | }
217 |
218 | default:
219 | TRESPASS();
220 | }
221 | break;
222 | }
223 |
224 | case kWhatStop:
225 | {
226 | looper()->stop();
227 | break;
228 | }
229 |
230 | default:
231 | TRESPASS();
232 | }
233 | }
234 |
235 | void WifiDisplaySink::registerResponseHandler(
236 | int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func) {
237 | ResponseID id;
238 | id.mSessionID = sessionID;
239 | id.mCSeq = cseq;
240 | mResponseHandlers.add(id, func);
241 | }
242 |
243 | status_t WifiDisplaySink::sendM2(int32_t sessionID) {
244 | ALOGD("call sendM2");
245 | AString request = "OPTIONS * RTSP/1.0\r\n";
246 | AppendCommonResponse(&request, mNextCSeq);
247 |
248 | request.append(
249 | "Require: org.wfa.wfd1.0\r\n"
250 | "\r\n");
251 |
252 | status_t err =
253 | mNetSession->sendRequest(sessionID, request.c_str(), request.size());
254 |
255 | if (err != OK) {
256 | return err;
257 | }
258 |
259 | registerResponseHandler(
260 | sessionID, mNextCSeq, &WifiDisplaySink::onReceiveM2Response);
261 |
262 | ++mNextCSeq;
263 |
264 | return OK;
265 | }
266 |
267 | status_t WifiDisplaySink::onReceiveM2Response(
268 | int32_t sessionID, const sp &msg) {
269 | ALOGD("onReceiveM2Response");
270 | int32_t statusCode;
271 | if (!msg->getStatusCode(&statusCode)) {
272 | return ERROR_MALFORMED;
273 | }
274 |
275 | if (statusCode != 200) {
276 | return ERROR_UNSUPPORTED;
277 | }
278 |
279 | return OK;
280 | }
281 |
282 | status_t WifiDisplaySink::onReceiveDescribeResponse(
283 | int32_t sessionID, const sp &msg) {
284 | ALOGD("onReceiveDescribeResponse");
285 | int32_t statusCode;
286 | if (!msg->getStatusCode(&statusCode)) {
287 | return ERROR_MALFORMED;
288 | }
289 |
290 | if (statusCode != 200) {
291 | return ERROR_UNSUPPORTED;
292 | }
293 |
294 | return sendSetup(sessionID, mSetupURI.c_str());
295 | }
296 |
297 | // on receive M6 response.
298 | status_t WifiDisplaySink::onReceiveSetupResponse(
299 | int32_t sessionID, const sp &msg) {
300 | ALOGD("onReceiveSetupResponse");
301 | int32_t statusCode;
302 | if (!msg->getStatusCode(&statusCode)) {
303 | return ERROR_MALFORMED;
304 | }
305 |
306 | if (statusCode != 200) {
307 | return ERROR_UNSUPPORTED;
308 | }
309 |
310 | if (!msg->findString("session", &mPlaybackSessionID)) {
311 | return ERROR_MALFORMED;
312 | }
313 |
314 | if (!ParsedMessage::GetInt32Attribute(
315 | mPlaybackSessionID.c_str(),
316 | "timeout",
317 | &mPlaybackSessionTimeoutSecs)) {
318 | mPlaybackSessionTimeoutSecs = -1;
319 | }
320 |
321 | ssize_t colonPos = mPlaybackSessionID.find(";");
322 | if (colonPos >= 0) {
323 | // Strip any options from the returned session id.
324 | mPlaybackSessionID.erase(
325 | colonPos, mPlaybackSessionID.size() - colonPos);
326 | }
327 |
328 | status_t err = configureTransport(msg);
329 |
330 | if (err != OK) {
331 | return err;
332 | }
333 |
334 | mState = PAUSED;
335 |
336 | AString playCommand = StringPrintf("rtsp://%s/wfd1.0/streamid=0", mPresentation_URL.c_str());
337 | return sendPlay(
338 | sessionID,
339 | !mSetupURI.empty()
340 | ? mSetupURI.c_str() : playCommand.c_str());
341 | }
342 |
343 | status_t WifiDisplaySink::configureTransport(const sp &msg) {
344 | ALOGD("configureTransport");
345 | if (sUseTCPInterleaving) {
346 | return OK;
347 | }
348 |
349 | AString transport;
350 | if (!msg->findString("transport", &transport)) {
351 | ALOGE("Missing 'transport' field in SETUP response.");
352 | return ERROR_MALFORMED;
353 | }
354 |
355 | AString sourceHost;
356 | if (!ParsedMessage::GetAttribute(
357 | transport.c_str(), "source", &sourceHost)) {
358 | sourceHost = mRTSPHost;
359 | }
360 |
361 | int rtpPort, rtcpPort;
362 |
363 | AString serverPortStr;
364 | if (ParsedMessage::GetAttribute(
365 | transport.c_str(), "server_port", &serverPortStr)) {
366 | if (sscanf(serverPortStr.c_str(), "%d-%d", &rtpPort, &rtcpPort) == 2) {
367 | if (rtpPort <= 0 || rtpPort > 65535
368 | || rtcpPort <=0 || rtcpPort > 65535
369 | || rtcpPort != rtpPort + 1) {
370 | ALOGE("Invalid server_port description '%s'.",
371 | serverPortStr.c_str());
372 |
373 | return ERROR_MALFORMED;
374 | }
375 |
376 | if (rtpPort & 1) {
377 | ALOGW("Server picked an odd numbered RTP port.");
378 | }
379 | } else if (sscanf(serverPortStr.c_str(), "%d", &rtpPort) == 1) {
380 | rtcpPort = rtpPort + 1;
381 | } else {
382 | ALOGE("Invalid server_port description '%s'.",
383 | serverPortStr.c_str());
384 | return ERROR_MALFORMED;
385 | }
386 |
387 | } else {
388 | // ALOGI("Missing 'server_port' in Transport field. using default port");
389 | // rtpPort = 33633;
390 | // rtcpPort = 33634;
391 |
392 | ALOGI("Missing 'server_port' in Transport field. so not link to source.(RTP)");
393 | return OK;
394 | }
395 |
396 | return OK; // Now, we not care the "server_port" parameter.
397 | // return mRTPSink->connect(sourceHost.c_str(), rtpPort, rtcpPort);
398 | }
399 |
400 | status_t WifiDisplaySink::onReceivePlayResponse(
401 | int32_t sessionID, const sp &msg) {
402 | ALOGD("onReceivePlayResponse");
403 | int32_t statusCode;
404 | if (!msg->getStatusCode(&statusCode)) {
405 | return ERROR_MALFORMED;
406 | }
407 |
408 | if (statusCode != 200) {
409 | return ERROR_UNSUPPORTED;
410 | }
411 |
412 | mState = PLAYING;
413 |
414 | return OK;
415 | }
416 |
417 | void WifiDisplaySink::onReceiveClientData(const sp &msg) {
418 | ALOGD("onReceiveClientData");
419 |
420 | int32_t sessionID;
421 | CHECK(msg->findInt32("sessionID", &sessionID));
422 |
423 | sp obj;
424 | CHECK(msg->findObject("data", &obj));
425 |
426 | sp data =
427 | static_cast(obj.get());
428 |
429 | ALOGV("session %d received '%s'",
430 | sessionID, data->debugString().c_str());
431 |
432 | AString method;
433 | AString uri;
434 | data->getRequestField(0, &method);
435 |
436 | int32_t cseq;
437 | if (!data->findInt32("cseq", &cseq)) {
438 | sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
439 | return;
440 | }
441 |
442 | if (method.startsWith("RTSP/")) {
443 | // This is a response.
444 |
445 | ResponseID id;
446 | id.mSessionID = sessionID;
447 | id.mCSeq = cseq;
448 |
449 | ssize_t index = mResponseHandlers.indexOfKey(id);
450 |
451 | if (index < 0) {
452 | ALOGW("Received unsolicited server response, cseq %d", cseq);
453 | return;
454 | }
455 |
456 | HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
457 | mResponseHandlers.removeItemsAt(index);
458 |
459 | status_t err = (this->*func)(sessionID, data);
460 | CHECK_EQ(err, (status_t)OK);
461 | } else {
462 | AString version;
463 | data->getRequestField(2, &version);
464 | if (!(version == AString("RTSP/1.0"))) {
465 | sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
466 | return;
467 | }
468 |
469 | if (method == "OPTIONS") {
470 | onOptionsRequest(sessionID, cseq, data);
471 | } else if (method == "GET_PARAMETER") {
472 | onGetParameterRequest(sessionID, cseq, data);
473 | } else if (method == "SET_PARAMETER") {
474 | onSetParameterRequest(sessionID, cseq, data);
475 | } else {
476 | sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
477 | }
478 | }
479 | }
480 |
481 | void WifiDisplaySink::onOptionsRequest(
482 | int32_t sessionID,
483 | int32_t cseq,
484 | const sp &data) {
485 | ALOGD("onOptionsRequest");
486 | AString response = "RTSP/1.0 200 OK\r\n";
487 | AppendCommonResponse(&response, cseq);
488 | response.append("Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n");
489 | response.append("\r\n");
490 |
491 | status_t err = mNetSession->sendRequest(sessionID, response.c_str());
492 | CHECK_EQ(err, (status_t)OK);
493 |
494 | err = sendM2(sessionID);
495 | CHECK_EQ(err, (status_t)OK);
496 | }
497 |
498 | // on receive M3
499 | void WifiDisplaySink::onGetParameterRequest(
500 | int32_t sessionID,
501 | int32_t cseq,
502 | const sp &data) {
503 | ALOGD("onGetParameterRequest");
504 |
505 | {
506 | // mRTPSink....
507 | }
508 |
509 | /* AString body =
510 | "wfd_video_formats: xxx\r\n"
511 | "wfd_audio_codecs: xxx\r\n"
512 | "wfd_client_rtp_ports: RTP/AVP/UDP;unicast xxx 0 mode=play\r\n"; */
513 |
514 | AString body =
515 | "wfd_video_formats: 48 00 02 02 0001DEFF 157C7FFF 00000FFF 00 0000 0000 00 none none\r\n"
516 | "wfd_audio_codecs: LPCM 00000003 00\r\n";
517 |
518 | body.append("wfd_content_protection: none\r\n");
519 | body.append("wfd_coupled_sink: 00 none\r\n");
520 | body.append("wfd_uibc_capability: none\r\n");
521 | body.append("wfd_standby_resume_capability: none\r\n");
522 | body.append("wfd_lg_dlna_uuid: none\r\n");
523 | //body.append("wfd_client_rtp_ports: RTP/AVP/UDP;unicast %d 0 mode=play\r\n",
524 | // mRTPSink->getRTPPort());
525 | body.append("wfd_client_rtp_ports: RTP/AVP/UDP;unicast 15550 0 mode=play\r\n");
526 | AString response = "RTSP/1.0 200 OK\r\n";
527 | AppendCommonResponse(&response, cseq);
528 | response.append("Content-Type: text/parameters\r\n");
529 | response.append(StringPrintf("Content-Length: %d\r\n", body.size()));
530 | response.append("\r\n");
531 | response.append(body);
532 |
533 | status_t err = mNetSession->sendRequest(sessionID, response.c_str());
534 | CHECK_EQ(err, (status_t)OK);
535 | }
536 |
537 | status_t WifiDisplaySink::sendDescribe(int32_t sessionID, const char *uri) {
538 | uri = "rtsp://xwgntvx.is.livestream-api.com/livestreamiphone/wgntv";
539 | uri = "rtsp://v2.cache6.c.youtube.com/video.3gp?cid=e101d4bf280055f9&fmt=18";
540 |
541 | ALOGD("sendDescribe");
542 |
543 | AString request = StringPrintf("DESCRIBE %s RTSP/1.0\r\n", uri);
544 | AppendCommonResponse(&request, mNextCSeq);
545 |
546 | request.append("Accept: application/sdp\r\n");
547 | request.append("\r\n");
548 |
549 | status_t err = mNetSession->sendRequest(
550 | sessionID, request.c_str(), request.size());
551 |
552 | if (err != OK) {
553 | return err;
554 | }
555 |
556 | registerResponseHandler(
557 | sessionID, mNextCSeq, &WifiDisplaySink::onReceiveDescribeResponse);
558 |
559 | ++mNextCSeq;
560 |
561 | return OK;
562 | }
563 |
564 | status_t WifiDisplaySink::sendSetup(int32_t sessionID, const char *uri) {
565 | ALOGD("sendSetup");
566 |
567 | mRTPSink = new RTPSink(mNetSession, mSurfaceTex);
568 | looper()->registerHandler(mRTPSink);
569 |
570 | status_t err = mRTPSink->init(sUseTCPInterleaving);
571 |
572 | if (err != OK) {
573 | looper()->unregisterHandler(mRTPSink->id());
574 | mRTPSink.clear();
575 | return err;
576 | }
577 |
578 | AString request = StringPrintf("SETUP %s RTSP/1.0\r\n", uri);
579 |
580 | AppendCommonResponse(&request, mNextCSeq);
581 |
582 | if (sUseTCPInterleaving) {
583 | request.append("Transport: RTP/AVP/TCP;interleaved=0-1\r\n");
584 | } else {
585 | int32_t rtpPort = mRTPSink->getRTPPort();
586 |
587 | request.append(
588 | StringPrintf(
589 | "Transport: RTP/AVP/UDP;unicast;client_port=%d\r\n",
590 | rtpPort));
591 | }
592 |
593 | request.append("\r\n");
594 |
595 | ALOGV("request = '%s'", request.c_str());
596 |
597 | err = mNetSession->sendRequest(sessionID, request.c_str(), request.size());
598 |
599 | if (err != OK) {
600 | return err;
601 | }
602 |
603 | registerResponseHandler(
604 | sessionID, mNextCSeq, &WifiDisplaySink::onReceiveSetupResponse);
605 |
606 | ++mNextCSeq;
607 |
608 | return OK;
609 | }
610 |
611 | status_t WifiDisplaySink::sendPlay(int32_t sessionID, const char *uri) {
612 | ALOGD("sendPlay");
613 | AString request = StringPrintf("PLAY %s RTSP/1.0\r\n", uri);
614 |
615 | AppendCommonResponse(&request, mNextCSeq);
616 |
617 | request.append(StringPrintf("Session: %s\r\n", mPlaybackSessionID.c_str()));
618 | request.append("\r\n");
619 |
620 | status_t err =
621 | mNetSession->sendRequest(sessionID, request.c_str(), request.size());
622 |
623 | if (err != OK) {
624 | return err;
625 | }
626 |
627 | registerResponseHandler(
628 | sessionID, mNextCSeq, &WifiDisplaySink::onReceivePlayResponse);
629 |
630 | ++mNextCSeq;
631 |
632 | return OK;
633 | }
634 |
635 | // on receive M4, M5.
636 | void WifiDisplaySink::onSetParameterRequest(
637 | int32_t sessionID,
638 | int32_t cseq,
639 | const sp &data) {
640 | ALOGD("onSetParameterRequest");
641 | const char *content = data->getContent();
642 |
643 | // if M4
644 | onSetParameterRequest_CheckM4Parameter(content);
645 |
646 | // if M5(setup) request. then send M6
647 | if (strstr(content, "wfd_trigger_method: SETUP\r\n") != NULL) {
648 | // AString uri = StringPrintf("rtsp://%s/wfd1.0/streamid=0", mPresentation_URL.c_str());
649 | AString uri = StringPrintf("rtsp://%s/wfd1.0/streamid=0", mPresentation_URL.c_str());
650 | status_t err =
651 | sendSetup(
652 | sessionID,
653 | uri.c_str());
654 |
655 | CHECK_EQ(err, (status_t)OK);
656 | }
657 |
658 | // response this M*
659 | AString response = "RTSP/1.0 200 OK\r\n";
660 | AppendCommonResponse(&response, cseq);
661 | response.append("\r\n");
662 |
663 | status_t err = mNetSession->sendRequest(sessionID, response.c_str());
664 | CHECK_EQ(err, (status_t)OK);
665 | }
666 |
667 | void WifiDisplaySink::onSetParameterRequest_CheckM4Parameter(const char *content) {
668 | ALOGV("onSetParameterRequest_CheckM4Parameter in");
669 | int len = strlen(content) + 1;
670 | char *contentTemp = new char[len];
671 | memset(contentTemp, 0, len);
672 | strcpy(contentTemp, content);
673 | contentTemp[len-1] = '\0';
674 | AString strOld(contentTemp);
675 | int pos1 = 0;
676 | int pos2 = 0;
677 | while (pos1 >= 0 && pos2 >= 0) {
678 | ALOGD("pos1 = %d, pos2 = %d", pos1, pos2);
679 | pos2 = strOld.find("\r\n", pos1);
680 | if (pos2 > 0) {
681 | char * array = new char[pos2 - pos1 + 1];
682 | memset(array, 0, pos2-pos1+1);
683 | strncpy(array, (strOld.c_str() + pos1), pos2 - pos1);
684 | AString oneLine = array;
685 | char tmp[20];
686 | memset(tmp, 20, 0);
687 | const char *startstr = "wfd_presentation_URL: rtsp://";
688 | int startstrlen = strlen(startstr);
689 | if (oneLine.startsWith(startstr)) {
690 | int endpos = oneLine.find("/", startstrlen);
691 | ALOGV("startlen = %d, endpos = %d", startstrlen, endpos);
692 | strncpy(tmp, oneLine.c_str() + startstrlen, endpos - startstrlen);
693 | tmp[endpos - startstrlen] = '\0';
694 | mPresentation_URL.clear();
695 | mPresentation_URL = tmp;
696 | break;
697 | }
698 | } else {
699 | break;
700 | }
701 | pos1 = pos2 + 2;
702 | }
703 |
704 | ALOGV("onSetParameterRequest_CheckM4Parameter result. mPresentation_URL = %s\n", mPresentation_URL.c_str());
705 | }
706 |
707 | void WifiDisplaySink::sendErrorResponse(
708 | int32_t sessionID,
709 | const char *errorDetail,
710 | int32_t cseq) {
711 | ALOGD("sendErrorResponse");
712 | AString response;
713 | response.append("RTSP/1.0 ");
714 | response.append(errorDetail);
715 | response.append("\r\n");
716 |
717 | AppendCommonResponse(&response, cseq);
718 |
719 | response.append("\r\n");
720 |
721 | status_t err = mNetSession->sendRequest(sessionID, response.c_str());
722 | CHECK_EQ(err, (status_t)OK);
723 | }
724 |
725 | // static
726 | void WifiDisplaySink::AppendCommonResponse(AString *response, int32_t cseq) {
727 | time_t now = time(NULL);
728 | struct tm *now2 = gmtime(&now);
729 | char buf[128];
730 | strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", now2);
731 |
732 | response->append("Date: ");
733 | response->append(buf);
734 | response->append("\r\n");
735 |
736 | response->append("User-Agent: stagefright/1.1 (Linux;Android 4.1)\r\n");
737 |
738 | if (cseq >= 0) {
739 | response->append(StringPrintf("CSeq: %d\r\n", cseq));
740 | }
741 | }
742 |
743 | } // namespace android
744 |
--------------------------------------------------------------------------------
/native/wifi-display/sink/WifiDisplaySink.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef WIFI_DISPLAY_SINK_H_
18 |
19 | #define WIFI_DISPLAY_SINK_H_
20 |
21 | #include "ANetworkSession.h"
22 |
23 | #include
24 | #include
25 |
26 | namespace android {
27 |
28 | struct ParsedMessage;
29 | struct RTPSink;
30 |
31 | // Represents the RTSP client acting as a wifi display sink.
32 | // Connects to a wifi display source and renders the incoming
33 | // transport stream using a MediaPlayer instance.
34 | struct WifiDisplaySink : public AHandler {
35 | WifiDisplaySink(
36 | const sp &netSession,
37 | const sp &surfaceTex = NULL);
38 |
39 | void start(const char *sourceHost, int32_t sourcePort);
40 | void start(const char *uri);
41 |
42 | protected:
43 | virtual ~WifiDisplaySink();
44 | virtual void onMessageReceived(const sp &msg);
45 |
46 | private:
47 | enum State {
48 | UNDEFINED,
49 | CONNECTING,
50 | CONNECTED,
51 | PAUSED,
52 | PLAYING,
53 | };
54 |
55 | enum {
56 | kWhatStart,
57 | kWhatRTSPNotify,
58 | kWhatStop,
59 | };
60 |
61 | struct ResponseID {
62 | int32_t mSessionID;
63 | int32_t mCSeq;
64 |
65 | bool operator<(const ResponseID &other) const {
66 | return mSessionID < other.mSessionID
67 | || (mSessionID == other.mSessionID
68 | && mCSeq < other.mCSeq);
69 | }
70 | };
71 |
72 | typedef status_t (WifiDisplaySink::*HandleRTSPResponseFunc)(
73 | int32_t sessionID, const sp &msg);
74 |
75 | static const bool sUseTCPInterleaving = false;
76 |
77 | State mState;
78 | sp mNetSession;
79 | sp mSurfaceTex;
80 | AString mSetupURI;
81 | AString mRTSPHost;
82 | int32_t mSessionID;
83 |
84 | AString mPresentation_URL;
85 |
86 | int32_t mNextCSeq;
87 |
88 | KeyedVector mResponseHandlers;
89 |
90 | sp mRTPSink;
91 | AString mPlaybackSessionID;
92 | int32_t mPlaybackSessionTimeoutSecs;
93 |
94 | status_t sendM2(int32_t sessionID);
95 | status_t sendDescribe(int32_t sessionID, const char *uri);
96 | status_t sendSetup(int32_t sessionID, const char *uri);
97 | status_t sendPlay(int32_t sessionID, const char *uri);
98 |
99 | status_t onReceiveM2Response(
100 | int32_t sessionID, const sp &msg);
101 |
102 | status_t onReceiveDescribeResponse(
103 | int32_t sessionID, const sp &msg);
104 |
105 | status_t onReceiveSetupResponse(
106 | int32_t sessionID, const sp &msg);
107 |
108 | status_t configureTransport(const sp &msg);
109 |
110 | status_t onReceivePlayResponse(
111 | int32_t sessionID, const sp &msg);
112 |
113 | void registerResponseHandler(
114 | int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
115 |
116 | void onReceiveClientData(const sp &msg);
117 |
118 | void onOptionsRequest(
119 | int32_t sessionID,
120 | int32_t cseq,
121 | const sp &data);
122 |
123 | void onGetParameterRequest(
124 | int32_t sessionID,
125 | int32_t cseq,
126 | const sp &data);
127 |
128 | void onSetParameterRequest(
129 | int32_t sessionID,
130 | int32_t cseq,
131 | const sp &data);
132 |
133 | void onSetParameterRequest_CheckM4Parameter(const char *content);
134 |
135 | void sendErrorResponse(
136 | int32_t sessionID,
137 | const char *errorDetail,
138 | int32_t cseq);
139 |
140 | static void AppendCommonResponse(AString *response, int32_t cseq);
141 |
142 | bool ParseURL(
143 | const char *url, AString *host, int32_t *port, AString *path,
144 | AString *user, AString *pass);
145 |
146 | DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
147 | };
148 |
149 | } // namespace android
150 |
151 | #endif // WIFI_DISPLAY_SINK_H_
152 |
--------------------------------------------------------------------------------
/native/wifi-display/source/Converter.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | //#define LOG_NDEBUG 0
18 | #define LOG_TAG "Converter"
19 | #include
20 |
21 | #include "Converter.h"
22 |
23 | #include "MediaPuller.h"
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 |
36 | #include
37 |
38 | namespace android {
39 |
40 | Converter::Converter(
41 | const sp ¬ify,
42 | const sp &codecLooper,
43 | const sp &format,
44 | bool usePCMAudio)
45 | : mInitCheck(NO_INIT),
46 | mNotify(notify),
47 | mCodecLooper(codecLooper),
48 | mInputFormat(format),
49 | mIsVideo(false),
50 | mIsPCMAudio(usePCMAudio),
51 | mNeedToManuallyPrependSPSPPS(false),
52 | mDoMoreWorkPending(false)
53 | #if ENABLE_SILENCE_DETECTION
54 | ,mFirstSilentFrameUs(-1ll)
55 | ,mInSilentMode(false)
56 | #endif
57 | {
58 | AString mime;
59 | CHECK(mInputFormat->findString("mime", &mime));
60 |
61 | if (!strncasecmp("video/", mime.c_str(), 6)) {
62 | mIsVideo = true;
63 | }
64 |
65 | CHECK(!usePCMAudio || !mIsVideo);
66 |
67 | mInitCheck = initEncoder();
68 |
69 | if (mInitCheck != OK) {
70 | if (mEncoder != NULL) {
71 | mEncoder->release();
72 | mEncoder.clear();
73 | }
74 | }
75 | }
76 |
77 | Converter::~Converter() {
78 | CHECK(mEncoder == NULL);
79 | }
80 |
81 | void Converter::shutdownAsync() {
82 | ALOGV("shutdown");
83 | (new AMessage(kWhatShutdown, id()))->post();
84 | }
85 |
86 | status_t Converter::initCheck() const {
87 | return mInitCheck;
88 | }
89 |
90 | size_t Converter::getInputBufferCount() const {
91 | return mEncoderInputBuffers.size();
92 | }
93 |
94 | sp Converter::getOutputFormat() const {
95 | return mOutputFormat;
96 | }
97 |
98 | bool Converter::needToManuallyPrependSPSPPS() const {
99 | return mNeedToManuallyPrependSPSPPS;
100 | }
101 |
102 | static int32_t getBitrate(const char *propName, int32_t defaultValue) {
103 | char val[PROPERTY_VALUE_MAX];
104 | if (property_get(propName, val, NULL)) {
105 | char *end;
106 | unsigned long x = strtoul(val, &end, 10);
107 |
108 | if (*end == '\0' && end > val && x > 0) {
109 | return x;
110 | }
111 | }
112 |
113 | return defaultValue;
114 | }
115 |
116 | status_t Converter::initEncoder() {
117 | AString inputMIME;
118 | CHECK(mInputFormat->findString("mime", &inputMIME));
119 |
120 | AString outputMIME;
121 | bool isAudio = false;
122 | if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_AUDIO_RAW)) {
123 | if (mIsPCMAudio) {
124 | outputMIME = MEDIA_MIMETYPE_AUDIO_RAW;
125 | } else {
126 | outputMIME = MEDIA_MIMETYPE_AUDIO_AAC;
127 | }
128 | isAudio = true;
129 | } else if (!strcasecmp(inputMIME.c_str(), MEDIA_MIMETYPE_VIDEO_RAW)) {
130 | outputMIME = MEDIA_MIMETYPE_VIDEO_AVC;
131 | } else {
132 | TRESPASS();
133 | }
134 |
135 | if (!mIsPCMAudio) {
136 | mEncoder = MediaCodec::CreateByType(
137 | mCodecLooper, outputMIME.c_str(), true /* encoder */);
138 |
139 | if (mEncoder == NULL) {
140 | return ERROR_UNSUPPORTED;
141 | }
142 | }
143 |
144 | mOutputFormat = mInputFormat->dup();
145 |
146 | if (mIsPCMAudio) {
147 | return OK;
148 | }
149 |
150 | mOutputFormat->setString("mime", outputMIME.c_str());
151 |
152 | int32_t audioBitrate = getBitrate("media.wfd.audio-bitrate", 128000);
153 | int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 5000000);
154 |
155 | ALOGI("using audio bitrate of %d bps, video bitrate of %d bps",
156 | audioBitrate, videoBitrate);
157 |
158 | if (isAudio) {
159 | mOutputFormat->setInt32("bitrate", audioBitrate);
160 | } else {
161 | mOutputFormat->setInt32("bitrate", videoBitrate);
162 | mOutputFormat->setInt32("bitrate-mode", OMX_Video_ControlRateConstant);
163 | mOutputFormat->setInt32("frame-rate", 30);
164 | mOutputFormat->setInt32("i-frame-interval", 15); // Iframes every 15 secs
165 |
166 | // Configure encoder to use intra macroblock refresh mode
167 | mOutputFormat->setInt32("intra-refresh-mode", OMX_VIDEO_IntraRefreshCyclic);
168 |
169 | int width, height, mbs;
170 | if (!mOutputFormat->findInt32("width", &width)
171 | || !mOutputFormat->findInt32("height", &height)) {
172 | return ERROR_UNSUPPORTED;
173 | }
174 |
175 | // Update macroblocks in a cyclic fashion with 10% of all MBs within
176 | // frame gets updated at one time. It takes about 10 frames to
177 | // completely update a whole video frame. If the frame rate is 30,
178 | // it takes about 333 ms in the best case (if next frame is not an IDR)
179 | // to recover from a lost/corrupted packet.
180 | mbs = (((width + 15) / 16) * ((height + 15) / 16) * 10) / 100;
181 | mOutputFormat->setInt32("intra-refresh-CIR-mbs", mbs);
182 | }
183 |
184 | ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
185 |
186 | mNeedToManuallyPrependSPSPPS = false;
187 |
188 | status_t err = NO_INIT;
189 |
190 | if (!isAudio) {
191 | sp tmp = mOutputFormat->dup();
192 | tmp->setInt32("prepend-sps-pps-to-idr-frames", 1);
193 |
194 | err = mEncoder->configure(
195 | tmp,
196 | NULL /* nativeWindow */,
197 | NULL /* crypto */,
198 | MediaCodec::CONFIGURE_FLAG_ENCODE);
199 |
200 | if (err == OK) {
201 | // Encoder supported prepending SPS/PPS, we don't need to emulate
202 | // it.
203 | mOutputFormat = tmp;
204 | } else {
205 | mNeedToManuallyPrependSPSPPS = true;
206 |
207 | ALOGI("We going to manually prepend SPS and PPS to IDR frames.");
208 | }
209 | }
210 |
211 | if (err != OK) {
212 | // We'll get here for audio or if we failed to configure the encoder
213 | // to automatically prepend SPS/PPS in the case of video.
214 |
215 | err = mEncoder->configure(
216 | mOutputFormat,
217 | NULL /* nativeWindow */,
218 | NULL /* crypto */,
219 | MediaCodec::CONFIGURE_FLAG_ENCODE);
220 | }
221 |
222 | if (err != OK) {
223 | return err;
224 | }
225 |
226 | err = mEncoder->start();
227 |
228 | if (err != OK) {
229 | return err;
230 | }
231 |
232 | err = mEncoder->getInputBuffers(&mEncoderInputBuffers);
233 |
234 | if (err != OK) {
235 | return err;
236 | }
237 |
238 | return mEncoder->getOutputBuffers(&mEncoderOutputBuffers);
239 | }
240 |
241 | void Converter::notifyError(status_t err) {
242 | sp notify = mNotify->dup();
243 | notify->setInt32("what", kWhatError);
244 | notify->setInt32("err", err);
245 | notify->post();
246 | }
247 |
248 | // static
249 | bool Converter::IsSilence(const sp &accessUnit) {
250 | const uint8_t *ptr = accessUnit->data();
251 | const uint8_t *end = ptr + accessUnit->size();
252 | while (ptr < end) {
253 | if (*ptr != 0) {
254 | return false;
255 | }
256 | ++ptr;
257 | }
258 |
259 | return true;
260 | }
261 |
262 | void Converter::onMessageReceived(const sp &msg) {
263 | switch (msg->what()) {
264 | case kWhatMediaPullerNotify:
265 | {
266 | int32_t what;
267 | CHECK(msg->findInt32("what", &what));
268 |
269 | if (!mIsPCMAudio && mEncoder == NULL) {
270 | ALOGV("got msg '%s' after encoder shutdown.",
271 | msg->debugString().c_str());
272 |
273 | if (what == MediaPuller::kWhatAccessUnit) {
274 | sp accessUnit;
275 | CHECK(msg->findBuffer("accessUnit", &accessUnit));
276 |
277 | void *mbuf;
278 | if (accessUnit->meta()->findPointer("mediaBuffer", &mbuf)
279 | && mbuf != NULL) {
280 | ALOGV("releasing mbuf %p", mbuf);
281 |
282 | accessUnit->meta()->setPointer("mediaBuffer", NULL);
283 |
284 | static_cast(mbuf)->release();
285 | mbuf = NULL;
286 | }
287 | }
288 | break;
289 | }
290 |
291 | if (what == MediaPuller::kWhatEOS) {
292 | mInputBufferQueue.push_back(NULL);
293 |
294 | feedEncoderInputBuffers();
295 |
296 | scheduleDoMoreWork();
297 | } else {
298 | CHECK_EQ(what, (status_t)MediaPuller::kWhatAccessUnit);
299 |
300 | sp