├── .gitignore
├── LiveView
├── .classpath
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── gen
│ └── com
│ │ └── example
│ │ └── liveview
│ │ ├── BuildConfig.java
│ │ └── R.java
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── lint.xml
├── 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_live_view.xml
│ ├── menu
│ │ └── live_view.xml
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── example
│ └── liveview
│ ├── CameraPreview.java
│ └── LiveView.java
├── MyRtsp
├── .classpath
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── gen
│ └── com
│ │ └── example
│ │ └── myrtsp
│ │ ├── BuildConfig.java
│ │ └── R.java
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── lint.xml
├── 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-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── example
│ └── myrtsp
│ └── MainActivity.java
├── README.md
├── RtspServer
├── .classpath
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── gen
│ └── com
│ │ └── optman
│ │ └── RtspServer
│ │ ├── BuildConfig.java
│ │ └── R.java
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── lint.xml
├── 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-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── optman
│ └── RtspServer
│ ├── RtpSession.java
│ ├── RtpSessionManager.java
│ ├── RtspServer.java
│ ├── RtspServerActivitiy.java
│ ├── StreamInfo.java
│ └── Utility.java
└── libRtsp
├── .classpath
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── gen
└── com
│ └── optman
│ └── librtsp
│ ├── BuildConfig.java
│ └── R.java
├── libs
└── android-support-v4.jar
├── lint.xml
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
└── values
│ ├── strings.xml
│ └── styles.xml
└── src
└── com
└── optman
├── rtp
├── player
│ ├── Player.java
│ ├── RtpPlayer.java
│ ├── RtpSampleSource.java
│ ├── RtspPlayer.java
│ ├── SimplePlayer.java
│ ├── Statistics.java
│ ├── TcpSinkPlayer.java
│ └── UdpSinkPlayer.java
├── receiver
│ ├── RtpAacStream.java
│ ├── RtpAvcStream.java
│ ├── RtpSink.java
│ ├── RtpStream.java
│ ├── Sample.java
│ ├── SampleHandler.java
│ └── UdpServer.java
└── sender
│ ├── CameraStream.java
│ ├── MicStream.java
│ ├── RtpAacStream.java
│ ├── RtpAvcStream.java
│ ├── RtpSocket.java
│ ├── RtpStream.java
│ └── RtpUdp.java
└── rtsp
├── RtspClient.java
├── RtspClientListener.java
├── TcpChannel.java
├── TcpDataHandler.java
├── TcpReceiver.java
└── TcpSender.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .git/
2 | .metadata
3 | bin/
4 |
5 |
--------------------------------------------------------------------------------
/LiveView/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LiveView/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | LiveView
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LiveView/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/LiveView/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/LiveView/gen/com/example/liveview/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.example.liveview;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/LiveView/gen/com/example/liveview/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.example.liveview;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class dimen {
14 | /** Default screen margins, per the Android Design guidelines.
15 |
16 | Example customization of dimensions originally defined in res/values/dimens.xml
17 | (such as screen margins) for screens with more than 820dp of available width. This
18 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
19 |
20 | */
21 | public static final int activity_horizontal_margin=0x7f040000;
22 | public static final int activity_vertical_margin=0x7f040001;
23 | }
24 | public static final class drawable {
25 | public static final int ic_launcher=0x7f020000;
26 | }
27 | public static final class id {
28 | public static final int camera_preview=0x7f070000;
29 | public static final int flip_protocol=0x7f070001;
30 | }
31 | public static final class layout {
32 | public static final int activity_live_view=0x7f030000;
33 | }
34 | public static final class menu {
35 | public static final int live_view=0x7f060000;
36 | }
37 | public static final class string {
38 | public static final int action_settings=0x7f050002;
39 | public static final int app_name=0x7f050000;
40 | public static final int hello_world=0x7f050001;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LiveView/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/ic_launcher-web.png
--------------------------------------------------------------------------------
/LiveView/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/LiveView/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/LiveView/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 |
--------------------------------------------------------------------------------
/LiveView/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-21
15 | android.library.reference.1=../appcompat_v7
16 |
--------------------------------------------------------------------------------
/LiveView/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/LiveView/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/LiveView/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/LiveView/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/LiveView/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/LiveView/res/layout/activity_live_view.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LiveView/res/menu/live_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/LiveView/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/LiveView/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/LiveView/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LiveView/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LiveView/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | LiveView
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LiveView/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/LiveView/src/com/example/liveview/CameraPreview.java:
--------------------------------------------------------------------------------
1 | package com.example.liveview;
2 |
3 |
4 | import java.io.IOException;
5 |
6 | import android.content.Context;
7 | import android.hardware.Camera;
8 | import android.util.Log;
9 | import android.view.Display;
10 | import android.view.Surface;
11 | import android.view.SurfaceHolder;
12 | import android.view.SurfaceView;
13 | import android.view.WindowManager;
14 |
15 | /** A basic Camera preview class */
16 | @SuppressWarnings("deprecation")
17 | public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
18 | private static final String TAG = "Camera";
19 | private SurfaceHolder mHolder;
20 | private Camera mCamera;
21 |
22 | public CameraPreview(Context context, Camera camera) {
23 | super(context);
24 | mCamera = camera;
25 |
26 | // Install a SurfaceHolder.Callback so we get notified when the
27 | // underlying surface is created and destroyed.
28 | mHolder = getHolder();
29 | mHolder.addCallback(this);
30 | // deprecated setting, but required on Android versions prior to 3.0
31 | //mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
32 | }
33 |
34 | public void surfaceCreated(SurfaceHolder holder) {
35 | // The Surface has been created, now tell the camera where to draw the preview.
36 | try {
37 | mCamera.setPreviewDisplay(holder);
38 | mCamera.startPreview();
39 | } catch (IOException e) {
40 | Log.d(TAG, "Error setting camera preview: " + e.getMessage());
41 | }
42 | }
43 |
44 | public void surfaceDestroyed(SurfaceHolder holder) {
45 | }
46 |
47 | public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
48 | // If your preview can change or rotate, take care of those events here.
49 | // Make sure to stop the preview before resizing or reformatting it.
50 |
51 | if (mHolder.getSurface() == null){
52 | // preview surface does not exist
53 | return;
54 | }
55 |
56 | // stop preview before making changes
57 | try {
58 | mCamera.stopPreview();
59 | } catch (Exception e){
60 | // ignore: tried to stop a non-existent preview
61 | }
62 |
63 | // set preview size and make any resize, rotate or
64 | // reformatting changes here
65 |
66 | // start preview with new settings
67 | try {
68 | mCamera.setPreviewDisplay(mHolder);
69 | mCamera.startPreview();
70 |
71 | } catch (Exception e){
72 | Log.d(TAG, "Error starting camera preview: " + e.getMessage());
73 | }
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/LiveView/src/com/example/liveview/LiveView.java:
--------------------------------------------------------------------------------
1 | package com.example.liveview;
2 |
3 | import java.io.IOException;
4 | import java.net.Socket;
5 | import java.net.UnknownHostException;
6 | import java.nio.ByteBuffer;
7 |
8 | import com.optman.rtp.sender.CameraStream;
9 | import com.optman.rtp.sender.MicStream;
10 | import com.optman.rtp.sender.RtpAacStream;
11 | import com.optman.rtp.sender.RtpAvcStream;
12 | import com.optman.rtp.sender.RtpSocket;
13 | import com.optman.rtp.sender.RtpUdp;
14 | import com.optman.rtsp.TcpChannel;
15 | import com.optman.rtsp.TcpSender;
16 |
17 | import android.app.Activity;
18 | import android.content.pm.ActivityInfo;
19 | import android.graphics.ImageFormat;
20 | import android.hardware.Camera;
21 | import android.hardware.Camera.Parameters;
22 | import android.hardware.Camera.Size;
23 | import android.media.MediaCodec;
24 | import android.media.MediaCodecInfo;
25 | import android.media.MediaFormat;
26 | import android.media.MediaCodec.BufferInfo;
27 | import android.os.Bundle;
28 | import android.support.v4.view.GestureDetectorCompat;
29 | import android.util.Log;
30 | import android.view.GestureDetector;
31 | import android.view.MotionEvent;
32 | import android.view.View;
33 | import android.widget.Button;
34 | import android.widget.FrameLayout;
35 |
36 |
37 | public class LiveView extends Activity {
38 |
39 | private Camera mCamera;
40 | private CameraPreview mPreview;
41 | private CameraStream videoStream;
42 | private MicStream audioStream;
43 | private RtpSocket videoSocket;
44 | private RtpSocket audioSocket;
45 |
46 | private GestureDetectorCompat mDetector;
47 | private int cameraIndex;
48 |
49 | private static long baseTimeUs = System.nanoTime()/1000;
50 | private static boolean overTcp = false;
51 |
52 | private TcpSender tcpSender;
53 |
54 |
55 | @Override
56 | protected void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | setContentView(R.layout.activity_live_view);
59 |
60 | mDetector = new GestureDetectorCompat(this, new MyGestureListener(this));
61 |
62 | Button protoBtn = (Button) findViewById(R.id.flip_protocol);
63 | protoBtn.setOnClickListener(new View.OnClickListener(){
64 | public void onClick(View v) {
65 | overTcp = !overTcp;
66 | restart();
67 | }
68 | });
69 |
70 |
71 | open();
72 | }
73 |
74 | @Override
75 | protected void onPause() {
76 | super.onPause();
77 |
78 | close();
79 | }
80 |
81 |
82 | private void restart() {
83 | close();
84 | open();
85 | }
86 |
87 | private void open() {
88 |
89 | Button protoBtn = (Button) findViewById(R.id.flip_protocol);
90 | if(overTcp)
91 | protoBtn.setText("TCP");
92 | else
93 | protoBtn.setText("UDP");
94 |
95 |
96 | // Create an instance of Camera
97 | mCamera = getCameraInstance(cameraIndex);
98 |
99 | Parameters p = mCamera.getParameters();
100 | p.setPreviewFormat(ImageFormat.YV12);
101 | p.setPreviewSize(1280, 720);
102 | mCamera.setParameters(p);
103 |
104 | // Create our Preview view and set it as the content of our activity.
105 | mPreview = new CameraPreview(this, mCamera);
106 | FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
107 | preview.addView(mPreview);
108 |
109 |
110 | (new Thread(new Runnable(){
111 |
112 | @Override
113 | public void run() {
114 | start();
115 | }
116 |
117 | })).start();
118 | }
119 |
120 | private void start(){
121 |
122 | if(!overTcp){
123 | videoSocket = new RtpUdp("127.0.0.1", 59526, false);
124 | audioSocket = new RtpUdp("127.0.0.1", 40272, false);
125 | }else{
126 | try {
127 | tcpSender = new TcpSender(new Socket("127.0.0.1", 8001));
128 | videoSocket = new TcpChannel(tcpSender, 0);
129 | audioSocket = new TcpChannel(tcpSender, 1);
130 | } catch (UnknownHostException e1) {
131 | e1.printStackTrace();
132 | } catch (IOException e1) {
133 | e1.printStackTrace();
134 | }
135 | }
136 |
137 |
138 | videoStream = new CameraStream(baseTimeUs, mCamera, videoSocket);
139 | videoStream.start();
140 |
141 |
142 | try {
143 | audioStream = new MicStream(baseTimeUs, 44100, 2, audioSocket);
144 | audioStream.start();
145 | } catch (IOException e) {
146 | e.printStackTrace();
147 | }
148 |
149 | }
150 |
151 | protected void close(){
152 |
153 | if(mCamera == null)
154 | return;
155 |
156 | FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
157 | preview.removeAllViews();
158 |
159 | videoStream.stop();
160 | videoSocket.close();
161 |
162 | audioStream.stop();
163 | audioSocket.close();
164 |
165 | if(tcpSender != null){
166 | tcpSender.close();
167 | tcpSender = null;
168 | }
169 |
170 | mCamera.stopPreview();
171 | mCamera.release();
172 |
173 | mCamera = null;
174 | }
175 |
176 | /** A safe way to get an instance of the Camera object. */
177 | public static Camera getCameraInstance(int cameraIndex){
178 | Camera c = null;
179 | try {
180 | c = Camera.open(cameraIndex); // attempt to get a Camera instance
181 |
182 | }
183 | catch (Exception e){
184 | // Camera is not available (in use or does not exist)
185 | }
186 | return c; // returns null if camera is unavailable
187 | }
188 |
189 | public void flipCamera(){
190 | cameraIndex = (cameraIndex + 1)%Camera.getNumberOfCameras();
191 |
192 | restart();
193 | }
194 |
195 |
196 | @Override
197 | public boolean onTouchEvent(MotionEvent event){
198 | this.mDetector.onTouchEvent(event);
199 | return super.onTouchEvent(event);
200 | }
201 |
202 | class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
203 |
204 | LiveView parent;
205 |
206 | public MyGestureListener(LiveView parent){
207 | this.parent = parent;
208 | }
209 |
210 | @Override
211 | public boolean onDown(MotionEvent event) {
212 | return true;
213 | }
214 |
215 | public boolean onSingleTapConfirmed(MotionEvent e){
216 |
217 | this.parent.flipCamera();
218 | return true;
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/MyRtsp/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/MyRtsp/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | MyRtsp
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/MyRtsp/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/MyRtsp/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/MyRtsp/gen/com/example/myrtsp/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.example.myrtsp;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/MyRtsp/gen/com/example/myrtsp/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.example.myrtsp;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class dimen {
14 | /** Default screen margins, per the Android Design guidelines.
15 |
16 | Example customization of dimensions originally defined in res/values/dimens.xml
17 | (such as screen margins) for screens with more than 820dp of available width. This
18 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
19 |
20 | */
21 | public static final int activity_horizontal_margin=0x7f040000;
22 | public static final int activity_vertical_margin=0x7f040001;
23 | }
24 | public static final class drawable {
25 | public static final int ic_launcher=0x7f020000;
26 | }
27 | public static final class id {
28 | public static final int action_settings=0x7f080004;
29 | public static final int sources=0x7f080001;
30 | public static final int startBtn=0x7f080002;
31 | public static final int statistics=0x7f080003;
32 | public static final int surface_view=0x7f080000;
33 | }
34 | public static final class layout {
35 | public static final int activity_main=0x7f030000;
36 | }
37 | public static final class menu {
38 | public static final int main=0x7f070000;
39 | }
40 | public static final class string {
41 | public static final int action_settings=0x7f050002;
42 | public static final int app_name=0x7f050000;
43 | public static final int hello_world=0x7f050001;
44 | }
45 | public static final class style {
46 | /**
47 | Base application theme, dependent on API level. This theme is replaced
48 | by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
49 |
50 |
51 | Theme customizations available in newer API levels can go in
52 | res/values-vXX/styles.xml, while customizations related to
53 | backward-compatibility can go here.
54 |
55 |
56 | Base application theme for API 11+. This theme completely replaces
57 | AppBaseTheme from res/values/styles.xml on API 11+ devices.
58 |
59 | API 11 theme customizations can go here.
60 |
61 | Base application theme for API 14+. This theme completely replaces
62 | AppBaseTheme from BOTH res/values/styles.xml and
63 | res/values-v11/styles.xml on API 14+ devices.
64 |
65 | API 14 theme customizations can go here.
66 | */
67 | public static final int AppBaseTheme=0x7f060000;
68 | /** Application theme.
69 | All customizations that are NOT specific to a particular API-level can go here.
70 | */
71 | public static final int AppTheme=0x7f060001;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/MyRtsp/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/ic_launcher-web.png
--------------------------------------------------------------------------------
/MyRtsp/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/MyRtsp/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/MyRtsp/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 |
--------------------------------------------------------------------------------
/MyRtsp/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 |
--------------------------------------------------------------------------------
/MyRtsp/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MyRtsp/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MyRtsp/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MyRtsp/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/MyRtsp/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/MyRtsp/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
23 |
24 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/MyRtsp/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/MyRtsp/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/MyRtsp/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/MyRtsp/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MyRtsp/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MyRtsp/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MyRtsp
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MyRtsp/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/MyRtsp/src/com/example/myrtsp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.myrtsp;
2 |
3 | import java.util.ArrayList;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.view.View;
9 | import android.widget.*;
10 | import android.widget.AdapterView.OnItemSelectedListener;
11 |
12 | import com.optman.rtp.player.Player;
13 | import com.optman.rtp.player.RtspPlayer;
14 | import com.optman.rtp.player.Statistics;
15 | import com.optman.rtp.player.UdpSinkPlayer;
16 | import com.google.android.exoplayer.MediaFormat;
17 | import com.google.android.exoplayer.VideoSurfaceView;
18 | import com.optman.rtp.player.*;
19 |
20 | public class MainActivity extends Activity {
21 |
22 | //private final static String TAG = "MyRtsp";
23 | private Player player;
24 |
25 | private int videoReceivePort = 59526;
26 | private int audioReceivePort = 40272;
27 |
28 | private boolean playerStarted = false;
29 |
30 | private Handler statsHandler = new Handler();
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 |
38 | ((Button) findViewById(R.id.startBtn)).setText("Stop");
39 |
40 | String sources[] =new String[7];
41 | sources[0]="RTP v 59526 a 40272 (AAC 0x1388 44100HZ stereo)";
42 | sources[1]="RTP v 50000";
43 | sources[2]="RTSP(UDP) 192.168.199.99:8554";
44 | sources[3]="RTSP(TCP) 111.197.87.142:1500(ipcam)";
45 | sources[4]="RTSP(UDP) 192.168.199.2:8554(video only)";
46 | sources[5]="RTSP(UDP) 192.168.199.214:9554";
47 | sources[6]="RTSP(TCP) 192.168.199.214:9554";
48 | final Spinner s = (Spinner) findViewById(R.id.sources);
49 | ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, sources);
50 | s.setAdapter(adapter);
51 | s.setOnItemSelectedListener(new OnItemSelectedListener(){
52 |
53 | @Override
54 | public void onItemSelected(AdapterView> parent, View view,
55 | int position, long id) {
56 |
57 | if(playerStarted)
58 | destroyPlayer();
59 |
60 | createPlayer(position);
61 | }
62 |
63 | @Override
64 | public void onNothingSelected(AdapterView> parent) {
65 | }
66 |
67 | });
68 |
69 | s.setSelection(5);
70 |
71 |
72 | //((TextView) findViewById(R.id.playerInfo)).setText("Udp Port: V " + videoReceivePort + " A " + audioReceivePort);
73 |
74 |
75 | final Button startBtn = (Button) findViewById(R.id.startBtn);
76 | startBtn.setOnClickListener((new View.OnClickListener() {
77 |
78 | @Override
79 | public void onClick(View v) {
80 |
81 | if(playerStarted){
82 | destroyPlayer();
83 | ((Button) findViewById(R.id.startBtn)).setText("Start");
84 | }else{
85 | createPlayer(s.getSelectedItemPosition());
86 | ((Button) findViewById(R.id.startBtn)).setText("Stop");
87 | }
88 |
89 | }
90 | }));
91 | }
92 |
93 | protected void onResume(){
94 | super.onResume();
95 | }
96 |
97 | private void createPlayer(int position) {
98 |
99 | MediaFormat videoFormat, audioFormat;
100 |
101 | VideoSurfaceView surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
102 | switch(position){
103 |
104 | case 0:
105 | videoFormat = MediaFormat.createVideoFormat("video/avc", 1024*1024, 0,0, null);
106 |
107 | ArrayList audioInitData = new ArrayList();
108 | audioInitData.add(new byte[]{0x13, (byte)0x88});
109 | audioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 64*1024, 2, 44100, audioInitData);
110 |
111 | player = new UdpSinkPlayer(videoReceivePort, audioReceivePort, surfaceView, new Handler(this.getMainLooper()), 2);
112 | player.setVideoFormat(videoFormat);
113 | player.setAudioFormat(audioFormat);
114 |
115 | player.setJitterBuffer(3*1000*1000);
116 | player.start();
117 | break;
118 |
119 | case 1:
120 | videoFormat = MediaFormat.createVideoFormat("video/avc", 1024*1024, 0,0, null);
121 |
122 | player = new UdpSinkPlayer(50000, 0, surfaceView, new Handler(this.getMainLooper()), 1);
123 | player.setVideoFormat(videoFormat);
124 | //player.setJitterBuffer(30*1000);
125 | player.setJitterBuffer(0);
126 | player.start();
127 | break;
128 |
129 | case 2:
130 | player = new RtspPlayer("rtsp://192.168.199.99:8554/", surfaceView, new Handler(this.getMainLooper()), 2, false);
131 | player.setJitterBuffer(3*1000*1000);
132 | player.start();
133 | break;
134 |
135 | case 3:
136 | player = new RtspPlayer("rtsp://111.197.87.142:1500/", surfaceView, new Handler(this.getMainLooper()), 1, true);
137 | player.setJitterBuffer(5*1000*1000);
138 | player.start();
139 | break;
140 |
141 | case 4:
142 | player = new RtspPlayer("rtsp://192.168.199.2:8554/", surfaceView, new Handler(this.getMainLooper()), 1, false);
143 | player.setJitterBuffer(30*1000);
144 | player.start();
145 | break;
146 |
147 | case 5:
148 | player = new RtspPlayer("rtsp://192.168.199.214:9554/", surfaceView, new Handler(this.getMainLooper()), 2, false);
149 | player.setJitterBuffer(100*1000);
150 | player.start();
151 | break;
152 |
153 | case 6:
154 | player = new RtspPlayer("rtsp://192.168.199.214:9554/", surfaceView, new Handler(this.getMainLooper()), 2, true);
155 | player.setJitterBuffer(100*1000);
156 | player.start();
157 | break;
158 | }
159 |
160 | playerStarted = true;
161 |
162 | startStatsTimer();
163 | }
164 |
165 | private void destroyPlayer(){
166 |
167 | if(player != null){
168 | player.stop();
169 | player = null;
170 | }
171 |
172 | playerStarted = false;
173 | }
174 |
175 | private void startStatsTimer() {
176 | statsHandler.postDelayed(new Runnable(){
177 |
178 | @Override
179 | public void run() {
180 |
181 | showStats();
182 |
183 | if(playerStarted)
184 | startStatsTimer();
185 | }
186 |
187 | }, 1000);
188 | }
189 |
190 | private void showStats() {
191 |
192 | if(player == null)
193 | return;
194 |
195 | TextView textView = (TextView) findViewById(R.id.statistics);
196 |
197 | Statistics st = player.getStats();
198 |
199 | textView.setText( "\r\n" +
200 | "v " + st.videoFrameCount + " a " + st.audioFrameCount + " drop " + st.transportDropPacketCount +
201 | " sync " + st.syncframeCount + " render drop " + st.renderDropframeCount +
202 | " state " + st.playbackState + "\r\n");
203 |
204 | }
205 |
206 |
207 |
208 | protected void onStop(){
209 |
210 | destroyPlayer();
211 |
212 | super.onStop();
213 | }
214 |
215 | protected void onDestroy() {
216 | android.os.Process.killProcess(android.os.Process.myPid());
217 | super.onDestroy();
218 | }
219 |
220 |
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | LiveView -- udp -- > RtspServer -- udp/tcp -> MyRtsp
2 |
3 | Camera/AudioRecord -> MediaCodec -> H264/AAC -> RTP
4 |
5 | MyRtsp(RtspPlayer) is build on ExoPlayer.
6 |
7 | Due to pixel format, now the only known supported device is Nexus 5!
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/RtspServer/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/RtspServer/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | RtspServer
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/RtspServer/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/RtspServer/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/RtspServer/gen/com/optman/RtspServer/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.optman.RtspServer;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/RtspServer/gen/com/optman/RtspServer/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.optman.RtspServer;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class dimen {
14 | /** Default screen margins, per the Android Design guidelines.
15 |
16 | Example customization of dimensions originally defined in res/values/dimens.xml
17 | (such as screen margins) for screens with more than 820dp of available width. This
18 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
19 |
20 | */
21 | public static final int activity_horizontal_margin=0x7f040000;
22 | public static final int activity_vertical_margin=0x7f040001;
23 | }
24 | public static final class drawable {
25 | public static final int ic_launcher=0x7f020000;
26 | }
27 | public static final class id {
28 | public static final int infoText=0x7f070001;
29 | public static final int surfaceview=0x7f070000;
30 | }
31 | public static final class layout {
32 | public static final int activity_main=0x7f030000;
33 | }
34 | public static final class menu {
35 | public static final int main=0x7f060000;
36 | }
37 | public static final class string {
38 | public static final int action_settings=0x7f050002;
39 | public static final int app_name=0x7f050000;
40 | public static final int hello_world=0x7f050001;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/RtspServer/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/ic_launcher-web.png
--------------------------------------------------------------------------------
/RtspServer/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/RtspServer/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/RtspServer/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 |
--------------------------------------------------------------------------------
/RtspServer/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-21
15 | android.library.reference.1=../appcompat_v7
16 |
--------------------------------------------------------------------------------
/RtspServer/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RtspServer/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RtspServer/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RtspServer/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/RtspServer/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RtspServer/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
14 |
15 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/RtspServer/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/RtspServer/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RtspServer/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/RtspServer/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/RtspServer/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RtspServer/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RtspServer
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RtspServer/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/RtpSession.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import java.net.InetAddress;
4 |
5 | import com.optman.rtp.sender.RtpSocket;
6 |
7 | public class RtpSession {
8 | InetAddress host;
9 | int port;
10 | RtpSocket tcpSocket;
11 | }
12 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/RtpSessionManager.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import java.io.IOException;
4 | import java.net.DatagramPacket;
5 | import java.net.DatagramSocket;
6 | import java.net.SocketException;
7 | import java.net.UnknownHostException;
8 | import java.util.HashSet;
9 | import java.util.Iterator;
10 | import java.util.Set;
11 |
12 | import com.optman.rtp.sender.RtpSocket;
13 |
14 | public class RtpSessionManager implements RtpSocket{
15 |
16 | private Set sessions = new HashSet();
17 |
18 | private DatagramSocket s;
19 |
20 | public RtpSessionManager(){
21 |
22 | try {
23 | s = new DatagramSocket();
24 |
25 | } catch (SocketException e) {
26 | e.printStackTrace();
27 | }
28 |
29 | }
30 |
31 | public synchronized void add(RtpSession session) {
32 | sessions.add(session);
33 | }
34 |
35 | public synchronized void remove(RtpSession session) {
36 | sessions.remove(session);
37 | }
38 |
39 |
40 | protected synchronized void sendData(byte[] data, int offset, int size){
41 |
42 | Iterator iter = sessions.iterator();
43 |
44 | while(iter.hasNext()){
45 |
46 | RtpSession session = iter.next();
47 |
48 | boolean hasError = false;
49 |
50 | try {
51 | if(session.tcpSocket != null){
52 | session.tcpSocket.sendPacket(data, offset, size);
53 | }else{
54 | DatagramPacket p;
55 | p = new DatagramPacket(data, offset, size, session.host, session.port);
56 | s.send(p);
57 | }
58 |
59 | } catch (UnknownHostException e) {
60 | hasError = true;
61 | } catch (IOException e) {
62 | hasError = true;
63 | }
64 |
65 | if(hasError)
66 | iter.remove();
67 |
68 | }
69 | }
70 |
71 | @Override
72 | public void sendPacket(byte[] data, int offset, int size) {
73 | sendData(data, offset, size);
74 |
75 | }
76 |
77 | public void close(){
78 |
79 | }
80 |
81 | protected synchronized String dump(){
82 |
83 | String result = "";
84 |
85 | Iterator iter = sessions.iterator();
86 |
87 | while(iter.hasNext()){
88 |
89 | RtpSession session = iter.next();
90 |
91 | result += session.host.toString() + " : " + session.port + "\r\n";
92 | }
93 |
94 | return result;
95 | }
96 |
97 |
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/RtspServer.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.PrintWriter;
7 | import java.net.Inet4Address;
8 | import java.net.InetAddress;
9 | import java.net.ServerSocket;
10 | import java.net.Socket;
11 | import java.net.UnknownHostException;
12 | import java.util.HashMap;
13 | import java.util.Iterator;
14 | import java.util.Map;
15 |
16 | import com.optman.rtp.sender.RtpSocket;
17 | import com.optman.rtsp.TcpChannel;
18 | import com.optman.rtsp.TcpSender;
19 | import com.optman.rtsp.*;
20 |
21 | import android.annotation.SuppressLint;
22 | import android.util.Base64;
23 | import android.util.Log;
24 |
25 | public class RtspServer {
26 |
27 | private ServerSocket server;
28 | private Thread thread;
29 | private RtpSessionManager videoSessions;
30 | private RtpSessionManager audioSessions;
31 | private final int videoPayloadType = 125;
32 | private final int audioPayloadType = 97;
33 | private final int audioSampleRate = 44100;
34 | private final int audioChannel = 2;
35 | private final String audioConfig = "1210";
36 | private final int port;
37 | private StreamInfo stream;
38 |
39 | enum MethodName{
40 | NA,
41 | Options,
42 | Describe,
43 | Setup,
44 | Play,
45 | GetParameter,
46 | TearDown,
47 | };
48 |
49 | class RequestInfo{
50 | InetAddress host;
51 | boolean isTcp;
52 | int videoPort;
53 | int audioPort;
54 | int videoChannel;
55 | int audioChannel;
56 | RtpSession videoSession;
57 | RtpSession audioSession;
58 | Socket socket;
59 | };
60 |
61 |
62 | public RtspServer(int port, RtpSessionManager videoSessions, RtpSessionManager audioSessions, StreamInfo stream){
63 |
64 | this.port = port;
65 | this.videoSessions = videoSessions;
66 | this.audioSessions = audioSessions;
67 | this.stream = stream;
68 |
69 | try {
70 | server = new ServerSocket(port);
71 | } catch (IOException e) {
72 | e.printStackTrace();
73 | }
74 | }
75 |
76 | public void start(){
77 | thread = new Thread(new Runnable(){
78 |
79 | @Override
80 | public void run() {
81 |
82 | while(true){
83 | try {
84 | final Socket client = server.accept();
85 |
86 | (new Thread(new Runnable(){
87 | public void run() {
88 | handleClient(client);
89 | }})
90 | ).start();
91 |
92 | } catch (IOException e) {
93 | e.printStackTrace();
94 | break;
95 | }
96 | }
97 |
98 | Log.i("RtspServer:", "exit accept.");
99 | }
100 |
101 | });
102 |
103 | thread.start();
104 | }
105 |
106 | public void stop(){
107 | try {
108 | server.close();
109 | thread.join();
110 | } catch (IOException e) {
111 | e.printStackTrace();
112 | } catch (InterruptedException e) {
113 | e.printStackTrace();
114 | }
115 | }
116 |
117 | private void handleClient(Socket client){
118 |
119 | RequestInfo info = new RequestInfo();
120 | info.host = client.getInetAddress();
121 | info.socket = client;
122 |
123 | try {
124 | DataInputStream reader = new DataInputStream(client.getInputStream());
125 | PrintWriter writer = new PrintWriter(client.getOutputStream());
126 |
127 | while(true){
128 | if(!handleRequest(reader, writer, info)){
129 | Log.i("RtspServer", "handle request fail.");
130 | break;
131 | }
132 | }
133 |
134 |
135 |
136 | } catch (IOException e) {
137 | e.printStackTrace();
138 | }
139 |
140 | removeSession(info);
141 |
142 | Log.i("RtspServer", "handle client finish.");
143 | }
144 |
145 | @SuppressWarnings("deprecation")
146 | @SuppressLint("DefaultLocale")
147 | private boolean handleRequest(DataInputStream reader, PrintWriter writer, RequestInfo info) throws IOException{
148 |
149 | MethodName method = MethodName.NA;
150 | Map headers = new HashMap();
151 |
152 | String line = reader.readLine();
153 | if(line == null){
154 | Log.i("RtspServer", "line is empty.");
155 | return false;
156 | }
157 |
158 | line = line.toUpperCase();
159 |
160 | while(true){
161 |
162 | if(line.startsWith("OPTIONS")){
163 | method = MethodName.Options;
164 | }else if(line.startsWith("DESCRIBE")){
165 | method = MethodName.Describe;
166 | }else if(line.startsWith("SETUP")){
167 | method = MethodName.Setup;
168 | }else if(line.startsWith("PLAY")){
169 | method = MethodName.Play;
170 | }else if(line.startsWith("GET_PARAMETER")){
171 | method = MethodName.GetParameter;
172 | }else if(line.startsWith("TEARTOWN")){
173 | method = MethodName.TearDown;
174 | }
175 |
176 | int start = line.indexOf(":", 0);
177 | if(start > 0){
178 | headers.put(line.substring(0, start).trim().toUpperCase(), line.substring(start + 1).trim());
179 | }
180 |
181 | Log.i("RTSP", line);
182 | if(line.length() == 0)
183 | break;
184 |
185 | line = reader.readLine();
186 | }
187 |
188 | boolean result = false;
189 |
190 | switch(method){
191 | case Options:
192 | result = handleOptions(headers, writer);
193 | break;
194 | case Describe:
195 | result = handleDescribe(headers, writer);
196 | break;
197 | case Setup:
198 | result = handleSetup(headers, writer, info);
199 | break;
200 | case Play:
201 | result = handlePlay(headers, writer, info);
202 | addSession(info);
203 | break;
204 | case GetParameter:
205 | result = handleGetParameter(headers, writer, info);
206 | break;
207 | case TearDown:
208 | result = handleTearDown(headers, writer, info);
209 | removeSession(info);
210 | break;
211 | default:
212 | result = false;
213 | break;
214 | }
215 |
216 | writer.flush();
217 |
218 |
219 | if(method == MethodName.Play && info.isTcp){
220 | //read until connection broken.
221 | byte[] dummy = new byte[1024];
222 | while(true){
223 | if( -1 == reader.read(dummy)){
224 | Log.i("RtspServer", "tcp connecton broken.");
225 | break;
226 | }
227 | }
228 | }
229 |
230 |
231 | return result;
232 | }
233 |
234 |
235 | private void addSession(RequestInfo info) {
236 |
237 | TcpSender sender = new TcpSender(info.socket);
238 |
239 |
240 | if(info.videoPort > 0 || info.isTcp){
241 |
242 | RtpSession session = new RtpSession();
243 | session.host = info.host;
244 | session.port = info.videoPort;
245 |
246 | if(info.isTcp)
247 | session.tcpSocket = new TcpChannel(sender, info.videoChannel);
248 |
249 | info.videoSession = session;
250 |
251 | videoSessions.add(session);
252 | }
253 |
254 | if(info.audioPort > 0 || info.isTcp){
255 |
256 | RtpSession session = new RtpSession();
257 | session.host = info.host;
258 | session.port = info.audioPort;
259 |
260 | if(info.isTcp)
261 | session.tcpSocket = new TcpChannel(sender, info.audioChannel);
262 |
263 | info.audioSession = session;
264 |
265 | audioSessions.add(session);
266 | }
267 |
268 | }
269 |
270 | private void removeSession(RequestInfo info) {
271 | videoSessions.remove(info.videoSession);
272 | audioSessions.remove(info.audioSession);
273 | }
274 |
275 | private void writeCommon(Map headers, PrintWriter writer){
276 |
277 | writer.write("RTSP/1.0 200 OK\r\n");
278 |
279 | String seq =headers.get("CSEQ");
280 | writer.write("Cseq: " + seq + "\r\n");
281 |
282 | writer.write("Server: RtspServer\r\n");
283 | }
284 |
285 | private boolean handleOptions(Map headers, PrintWriter writer) {
286 | writeCommon(headers, writer);
287 | writer.write("Public: OPTIONS,DESCRIBE,SETUP,TEARDOWN,PLAY\r\n");
288 | writer.write("Content-Length: 0\r\n");
289 | writer.write("\r\n");
290 |
291 | return true;
292 | }
293 |
294 | private boolean handleDescribe(Map headers, PrintWriter writer) {
295 |
296 | String localIp = Utility.getLocalIpAddress();
297 |
298 |
299 | String paramSets = "";
300 | Iterator iter = stream.getSpsPps().iterator();
301 | while(iter.hasNext()){
302 |
303 | byte[] item = iter.next();
304 | if(item == null)
305 | continue;
306 |
307 | if(paramSets.length()!=0)
308 | paramSets += ",";
309 |
310 | paramSets += Base64.encodeToString (item, 0, item.length, Base64.NO_WRAP);
311 | }
312 |
313 | //make android player happy, or it would startup!
314 | if(paramSets.length() == 0){
315 | paramSets = "Z01AHuiAbB7zeAiAAAADAIAAABgHixaJ,aOvvIA==";
316 | }
317 |
318 | String sdp = "v=0\r\n"
319 | + "o=- 3331435948 1116907222000 IN IP4 " + localIp + "\r\n"
320 | + "c=IN IP4 0.0.0.0\r\n"
321 | + "t=0 0\r\n"
322 |
323 | //video
324 | + "m=video 0 RTP/AVP " + videoPayloadType +"\r\n"
325 | + "a=rtpmap:" + videoPayloadType + " H264/90000\r\n"
326 | + "a=fmtp:125 packetization-mode=1;profile-level-id=4d401e;sprop-parameter-sets=" + paramSets + ";\r\n"
327 | + "a=control:rtsp://" + localIp + ":" + port +"/trackID=0\r\n"
328 |
329 | //audio
330 | + "m=audio 0 RTP/AVP " + audioPayloadType +"\r\n"
331 | + "a=rtpmap:" + audioPayloadType +" mpeg4-generic/" + audioSampleRate + "/" + audioChannel + "\r\n"
332 | + "a=fmtp:" + audioPayloadType +" streamtype=5; profile-level-id=15; mode=aac-hbr; config=" + audioConfig +"; " +
333 | "SizeLength=13; IndexLength=3; IndexDeltaLength=3; Profile=1;\r\n"
334 | + "a=control:rtsp://" + localIp + ":" + port +"/trackID=1\r\n"
335 | ;
336 |
337 |
338 | writeCommon(headers, writer);
339 | writer.write("Content-Type: application/sdp\r\n");
340 | writer.write("Content-Length: " + sdp.length() + "\r\n");
341 |
342 | writer.write("\r\n");
343 |
344 | writer.write(sdp);
345 |
346 | return true;
347 | }
348 |
349 | private boolean handleSetup(Map headers, PrintWriter writer, RequestInfo info) {
350 |
351 | writeCommon(headers, writer);
352 |
353 | int clientPort = 0;
354 | String line = headers.get("TRANSPORT");
355 | int start = line.indexOf("client_port=");
356 | if(start > 0){
357 | clientPort = Integer.parseInt(line.substring(line.indexOf("=", start) + 1, line.indexOf("-", start)));
358 | }
359 | int channelNum = 0;
360 | info.isTcp = line.indexOf("TCP") > 0;
361 | if(info.isTcp){
362 | start = line.indexOf("interleaved");
363 | if(start > 0){
364 | start = line.indexOf("=", start) + 1;
365 | int end = line.indexOf("-", start);
366 | channelNum = Integer.parseInt(line.substring(start, end));
367 | }
368 | }
369 |
370 |
371 | //NOTE: should use url instead.
372 | if(headers.get("SETUP RTSP").indexOf("TRACKID=0") > 0){
373 | info.videoPort = clientPort;
374 | info.videoChannel = channelNum;
375 | }
376 | else if(headers.get("SETUP RTSP").indexOf("TRACKID=1") > 0){
377 | info.audioPort = clientPort;
378 | info.audioChannel = channelNum;
379 | }
380 |
381 | if(info.isTcp)
382 | writer.write("Transport: RTP/AVP/TCP;unicast;interleaved=" + channelNum + "-" + (channelNum + 1)
383 | +";ssrc=E257CDCD;mode=play\r\n");
384 | else
385 | writer.write("Transport: RTP/AVP/UDP;unicast;client_port=" + clientPort + "-" + (clientPort + 1)
386 | +";server_port=57010-57011;ssrc=E257CDCD;mode=play\r\n");
387 |
388 | writer.write("Session: 12345678\r\n");
389 | writer.write("Content-Length: 0\r\n");
390 | writer.write("\r\n");
391 |
392 | return true;
393 | }
394 |
395 | private boolean handlePlay(Map headers, PrintWriter writer, RequestInfo info) {
396 | writeCommon(headers, writer);
397 | writer.write("Session: 12345678\r\n");
398 | writer.write("Content-Length: 0\r\n");
399 | writer.write("\r\n");
400 |
401 | return true;
402 | }
403 |
404 | private boolean handleGetParameter(Map headers, PrintWriter writer, RequestInfo info) {
405 | writeCommon(headers, writer);
406 | writer.write("Session: 12345678\r\n");
407 | writer.write("Content-Length: 0\r\n");
408 | writer.write("\r\n");
409 |
410 | return true;
411 | }
412 |
413 | private boolean handleTearDown(Map headers, PrintWriter writer, RequestInfo info) {
414 | writeCommon(headers, writer);
415 | writer.write("Session: 12345678\r\n");
416 | writer.write("Content-Length: 0\r\n");
417 | writer.write("\r\n");
418 |
419 | return false;
420 | }
421 | }
422 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/RtspServerActivitiy.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import android.app.Activity;
4 |
5 | import java.io.IOException;
6 | import java.net.ServerSocket;
7 | import java.net.Socket;
8 | import java.net.SocketException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import com.optman.rtp.player.Statistics;
13 | import com.optman.rtp.receiver.Sample;
14 | import com.optman.rtp.receiver.SampleHandler;
15 | import com.optman.rtp.receiver.UdpServer;
16 | import com.optman.rtsp.TcpDataHandler;
17 | import com.optman.rtsp.TcpReceiver;
18 | import com.optman.rtp.receiver.*;
19 | import com.optman.rtp.sender.*;
20 |
21 |
22 | import android.view.SurfaceHolder;
23 | import android.view.SurfaceHolder.Callback;
24 | import android.view.SurfaceView;
25 | import android.widget.TextView;
26 | import android.graphics.PixelFormat;
27 | import android.media.AudioManager;
28 | import android.media.MediaPlayer;
29 | import android.os.Bundle;
30 | import android.os.Handler;
31 |
32 |
33 | public class RtspServerActivitiy extends Activity implements Callback, StreamInfo{
34 |
35 | private SurfaceView surfaceView;
36 | private SurfaceHolder surfaceHolder;
37 | private MediaPlayer mediaPlayer;
38 | private RtspServer svr;
39 | private RtpSessionManager videoSessions = new RtpSessionManager();
40 | private RtpSessionManager audioSessions = new RtpSessionManager();
41 |
42 | private com.optman.rtp.receiver.RtpAvcStream videoReceiverStream;
43 | private UdpServer videoReceiver;
44 | private com.optman.rtp.sender.RtpAvcStream videoSenderStream;
45 |
46 | private com.optman.rtp.receiver.RtpAacStream audioReceiverStream;
47 | private UdpServer audioReceiver;
48 | private com.optman.rtp.sender.RtpAacStream audioSenderStream;
49 |
50 |
51 | private List spspps = new ArrayList();
52 |
53 | private Handler statsHandler = new Handler();
54 | private Statistics st = new Statistics();
55 |
56 | @Override
57 | protected void onCreate(Bundle savedInstanceState) {
58 | super.onCreate(savedInstanceState);
59 | setContentView(R.layout.activity_main);
60 |
61 |
62 | getWindow().setFormat(PixelFormat.UNKNOWN);
63 | surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
64 | surfaceHolder = surfaceView.getHolder();
65 | surfaceHolder.addCallback(this);
66 |
67 | spspps.add(null);
68 | spspps.add(null);
69 |
70 | startStatsTimer();
71 |
72 | svr = new RtspServer(9554, videoSessions, audioSessions, this);
73 | svr.start();
74 |
75 | startRtp();
76 |
77 |
78 | // (new Thread(new Runnable(){
79 | // @Override
80 | // public void run() {
81 | // playMovie();
82 | // }})).start();
83 |
84 |
85 | }
86 |
87 | private void startRtp() {
88 |
89 | videoSenderStream = new com.optman.rtp.sender.RtpAvcStream(videoSessions);
90 |
91 | videoReceiverStream = new com.optman.rtp.receiver.RtpAvcStream(new SampleHandler(){
92 |
93 | @Override
94 | public void onSample(Sample sample) {
95 |
96 | //long timeUs = System.nanoTime()/1000;
97 | long timeUs = sample.timestampUs;
98 |
99 | byte naluType = (byte) (sample.data[4] & 0x1f);
100 | if(naluType == 7 || naluType == 8){
101 | byte[] data = new byte[sample.data.length - 4];
102 | System.arraycopy(sample.data, 4, data, 0, data.length);
103 |
104 | spspps.set(naluType - 7, data);
105 | }
106 |
107 | try {
108 | videoSenderStream.addNalu(sample.data, 4, sample.data.length - 4, timeUs);
109 | } catch (IOException e) {
110 | e.printStackTrace();
111 | }
112 | }
113 |
114 | }, st);
115 |
116 | videoReceiver = new UdpServer(/*50000*/59526, videoReceiverStream);
117 | try {
118 | videoReceiver.open();
119 | } catch (SocketException e) {
120 | e.printStackTrace();
121 | }
122 |
123 | audioSenderStream = new com.optman.rtp.sender.RtpAacStream(44100, audioSessions);
124 | audioReceiverStream = new com.optman.rtp.receiver.RtpAacStream(44100, new SampleHandler(){
125 |
126 | @Override
127 | public void onSample(Sample sample) {
128 | try {
129 | audioSenderStream.addAU(sample.data, 0, sample.data.length, sample.timestampUs);
130 | } catch (IOException e) {
131 | e.printStackTrace();
132 | }
133 | }
134 | }, st);
135 |
136 | audioReceiver = new UdpServer(40272, audioReceiverStream);
137 |
138 |
139 | try {
140 | audioReceiver.open();
141 | } catch (SocketException e) {
142 | e.printStackTrace();
143 | }
144 |
145 |
146 |
147 | //tcp receiver
148 |
149 | try {
150 | final ServerSocket server = new ServerSocket(8001);
151 |
152 | (new Thread(new Runnable(){
153 |
154 | @Override
155 | public void run() {
156 |
157 | while(true){
158 |
159 | try {
160 | Socket s = server.accept();
161 |
162 | TcpReceiver client = new TcpReceiver(s);
163 |
164 | client.run(new TcpDataHandler(){
165 |
166 | @Override
167 | public void onData(int channel, byte[] data,
168 | int dataSize) {
169 |
170 | if(channel == 0)
171 | videoReceiverStream.onRtp(data, dataSize);
172 | else if(channel == 1)
173 | audioReceiverStream.onRtp(data, dataSize);
174 |
175 | }
176 |
177 | });
178 |
179 |
180 | } catch (IOException e) {
181 | e.printStackTrace();
182 | }
183 |
184 | }
185 | }
186 |
187 | })).start();
188 |
189 |
190 | } catch (IOException e) {
191 | e.printStackTrace();
192 | }
193 |
194 |
195 |
196 |
197 | }
198 |
199 | private void playMovie() {
200 | //String url = "rtsp://192.168.199.99:8554/";
201 | String url = "rtsp://127.0.0.1:9554/";
202 | //String url = "http://192.168.199.99/C%2b%2b/VisualStudioEmulatorforAndroid_high.mp4";
203 | mediaPlayer = new MediaPlayer();
204 |
205 | if (mediaPlayer.isPlaying()) {
206 | mediaPlayer.reset();
207 | }
208 |
209 | mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
210 |
211 | try {
212 | mediaPlayer.setDataSource(url);
213 | mediaPlayer.prepare(); // might take long! (for buffering, etc)
214 | mediaPlayer.start();
215 | } catch (IllegalArgumentException e) {
216 | e.printStackTrace();
217 | } catch (SecurityException e) {
218 | e.printStackTrace();
219 | } catch (IllegalStateException e) {
220 | e.printStackTrace();
221 | } catch (IOException e) {
222 | e.printStackTrace();
223 | }
224 | }
225 |
226 | @Override
227 | public void surfaceCreated(SurfaceHolder holder) {
228 | if(mediaPlayer == null)
229 | return;
230 |
231 | mediaPlayer.setDisplay(holder);
232 | }
233 |
234 | @Override
235 | public void surfaceChanged(SurfaceHolder holder, int format, int width,
236 | int height) {
237 |
238 | }
239 |
240 | @Override
241 | public void surfaceDestroyed(SurfaceHolder holder) {
242 |
243 | }
244 |
245 | @Override
246 | public List getSpsPps() {
247 |
248 | return spspps;
249 | }
250 |
251 | private void startStatsTimer() {
252 | statsHandler.postDelayed(new Runnable(){
253 |
254 | @Override
255 | public void run() {
256 | showStats();
257 | startStatsTimer();
258 | }
259 |
260 | }, 1000);
261 | }
262 |
263 | private void showStats() {
264 | TextView textView = (TextView) findViewById(R.id.infoText);
265 |
266 | String s = " v:\r\n" + videoSessions.dump() + "\r\n" +
267 | " a:\r\n " + audioSessions.dump() +
268 | " \r\n" +
269 | " v " + st.videoFrameCount + " a " + st.audioFrameCount + " drop " + st.transportDropPacketCount +
270 | " sync " + st.syncframeCount;
271 |
272 | textView.setText(s);
273 |
274 | }
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/StreamInfo.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import java.util.List;
4 |
5 | public interface StreamInfo {
6 | public List getSpsPps();
7 | }
8 |
--------------------------------------------------------------------------------
/RtspServer/src/com/optman/RtspServer/Utility.java:
--------------------------------------------------------------------------------
1 | package com.optman.RtspServer;
2 |
3 | import java.net.InetAddress;
4 | import java.net.NetworkInterface;
5 | import java.net.SocketException;
6 | import java.util.Enumeration;
7 |
8 | import android.util.Log;
9 |
10 | public class Utility {
11 |
12 | static public String getLocalIpAddress(){
13 |
14 | try
15 | {
16 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();)
17 | {
18 | NetworkInterface intf = en.nextElement();
19 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();)
20 | {
21 | InetAddress inetAddress = enumIpAddr.nextElement();
22 | if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress())
23 | {
24 | String result = inetAddress.getHostAddress().toString();
25 |
26 | //ignore hotspot address.
27 | if(result.equalsIgnoreCase("192.168.43.1")){
28 | Log.i("Utility", "ignore hotspot address");
29 | continue;
30 | }
31 |
32 | return result;
33 | }
34 | }
35 | }
36 | }
37 | catch (SocketException ex)
38 | {
39 | Log.e("Utility", ex.toString());
40 | }
41 | return "";
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/libRtsp/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/libRtsp/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | libRtsp
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/libRtsp/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/libRtsp/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/libRtsp/gen/com/optman/librtsp/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.optman.librtsp;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/libRtsp/gen/com/optman/librtsp/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.optman.librtsp;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class drawable {
14 | public static int ic_launcher=0x7f020000;
15 | }
16 | public static final class string {
17 | public static int app_name=0x7f030000;
18 | }
19 | public static final class style {
20 | /**
21 | Base application theme, dependent on API level. This theme is replaced
22 | by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
23 |
24 |
25 | Theme customizations available in newer API levels can go in
26 | res/values-vXX/styles.xml, while customizations related to
27 | backward-compatibility can go here.
28 |
29 |
30 | Base application theme for API 11+. This theme completely replaces
31 | AppBaseTheme from res/values/styles.xml on API 11+ devices.
32 |
33 | API 11 theme customizations can go here.
34 |
35 | Base application theme for API 14+. This theme completely replaces
36 | AppBaseTheme from BOTH res/values/styles.xml and
37 | res/values-v11/styles.xml on API 14+ devices.
38 |
39 | API 14 theme customizations can go here.
40 | */
41 | public static int AppBaseTheme=0x7f040000;
42 | /** Application theme.
43 | All customizations that are NOT specific to a particular API-level can go here.
44 | */
45 | public static int AppTheme=0x7f040001;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/libRtsp/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/libRtsp/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/libRtsp/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/libRtsp/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 |
--------------------------------------------------------------------------------
/libRtsp/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-21
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/libRtsp/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/libRtsp/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/libRtsp/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/libRtsp/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/libRtsp/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/optman/android-cam-rtsp/1a2c9bdc89194e1554afeff1efcdbd7f45ea35ba/libRtsp/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/libRtsp/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/libRtsp/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/libRtsp/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | libRtsp
4 |
5 |
6 |
--------------------------------------------------------------------------------
/libRtsp/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/Player.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import com.optman.rtp.player.Statistics;
4 | import com.google.android.exoplayer.MediaFormat;
5 |
6 | public interface Player {
7 | public void start();
8 | public void stop();
9 | public void setVideoFormat(MediaFormat format);
10 | public void setAudioFormat(MediaFormat format);
11 | public void addVideoPacket(byte[] data, int dataSize);
12 | public void addAudioPacket(byte[] data, int dataSize);
13 | public Statistics getStats();
14 | public void setJitterBuffer(long timeUs);
15 | }
16 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/RtpPlayer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaCodec.CryptoException;
5 | import android.os.Handler;
6 | import android.view.Surface;
7 | import android.view.SurfaceHolder;
8 |
9 | import com.optman.rtp.player.RtpSampleSource;
10 | import com.optman.rtp.player.Statistics;
11 | import com.google.android.exoplayer.*;
12 | import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
13 | import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
14 | import com.google.android.exoplayer.ExoPlayer.*;
15 |
16 | public class RtpPlayer implements SurfaceHolder.Callback, ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener {
17 |
18 | private VideoSurfaceView surfaceView;
19 | private ExoPlayer player;
20 | private TrackRenderer[] renders;
21 | private ExoPlayerComponent videoRenderer;
22 | private Handler handler;
23 | private Statistics stats;
24 |
25 | public RtpPlayer(int sourceCount, VideoSurfaceView view, Handler handler, Statistics stats){
26 | this.renders = new TrackRenderer[sourceCount];
27 | this.surfaceView = view;
28 | this.handler = handler;
29 | this.stats = stats;
30 | this.player = ExoPlayer.Factory.newInstance(sourceCount, 100, 100);
31 | player.addListener(this);
32 |
33 | surfaceView.getHolder().addCallback(this);
34 | }
35 |
36 | public void prepare(RtpSampleSource...sources){
37 |
38 | for(int i = 0; i < sources.length; i++){
39 | RtpSampleSource source = sources[i];
40 | if(source.getFormat().mimeType.startsWith("video/")){
41 | renders[i] = new MediaCodecVideoTrackRenderer(source, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING,
42 | 0, handler, this, 10);
43 | videoRenderer = renders[i];
44 | }else if(source.getFormat().mimeType.startsWith("audio/")){
45 | renders[i] = new MediaCodecAudioTrackRenderer(source, null, true);
46 | }else{
47 | throw new RuntimeException();
48 | }
49 | }
50 |
51 | player.prepare(renders);
52 |
53 | setSurface();
54 |
55 | }
56 |
57 | public void play(){
58 | player.setPlayWhenReady(true);
59 | }
60 |
61 | public void close(){
62 | player.stop();
63 | player.release();
64 | player = null;
65 | }
66 |
67 | private void setSurface() {
68 |
69 | Surface surface = surfaceView.getHolder().getSurface();
70 | if (videoRenderer == null || surface == null || !surface.isValid()) {
71 | return;
72 | }
73 | player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
74 | }
75 |
76 | @Override
77 | public void surfaceCreated(SurfaceHolder holder) {
78 | setSurface();
79 | }
80 |
81 | @Override
82 | public void surfaceChanged(SurfaceHolder holder, int format, int width,
83 | int height) {
84 | }
85 |
86 | @Override
87 | public void surfaceDestroyed(SurfaceHolder holder) {
88 | if (videoRenderer != null) {
89 | player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
90 | }
91 | }
92 |
93 | @Override
94 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
95 | stats.playbackState = playbackState;
96 | }
97 |
98 | @Override
99 | public void onPlayWhenReadyCommitted() {
100 | }
101 |
102 | @Override
103 | public void onPlayerError(ExoPlaybackException error) {
104 | }
105 |
106 | @Override
107 | public void onDecoderInitializationError(DecoderInitializationException e) {
108 |
109 | }
110 |
111 | @Override
112 | public void onCryptoError(CryptoException e) {
113 |
114 | }
115 |
116 | @Override
117 | public void onDroppedFrames(int count, long elapsed) {
118 | stats.renderDropframeCount += count;
119 | }
120 |
121 | @Override
122 | public void onVideoSizeChanged(int width, int height) {
123 | }
124 |
125 | @Override
126 | public void onDrawnToSurface(Surface surface) {
127 |
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/RtpSampleSource.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import java.io.IOException;
4 | import java.util.ArrayDeque;
5 | import java.util.Queue;
6 |
7 | import android.util.Log;
8 |
9 | import com.google.android.exoplayer.MediaFormat;
10 | import com.google.android.exoplayer.MediaFormatHolder;
11 | import com.google.android.exoplayer.SampleHolder;
12 | import com.google.android.exoplayer.SampleSource;
13 | import com.google.android.exoplayer.TrackInfo;
14 | import com.google.android.exoplayer.TrackRenderer;
15 | import com.optman.rtp.receiver.Sample;
16 |
17 |
18 | public class RtpSampleSource implements SampleSource {
19 |
20 | private static final String TAG = "RtpSampleSource";
21 |
22 | private boolean formatSent = false;
23 |
24 | // private List initializationData;
25 |
26 | private Queue samples = new ArrayDeque();
27 |
28 | private long durationUs = TrackRenderer.UNKNOWN_TIME_US;
29 |
30 | private MediaFormat format;
31 |
32 | public RtpSampleSource(){
33 | }
34 |
35 |
36 | public synchronized void setFormat(MediaFormat format){
37 | this.format = format;
38 | }
39 |
40 | public synchronized MediaFormat getFormat(){
41 | return this.format;
42 | }
43 |
44 | // public synchronized void setInitializationData(List data){
45 | // this.initializationData = data;
46 | // }
47 |
48 | public synchronized void addSample(Sample sample){
49 | samples.add(sample);
50 | }
51 |
52 | @Override
53 | public synchronized boolean prepare() throws IOException {
54 |
55 | return /*initializationData != null && */format != null;
56 | }
57 |
58 | @Override
59 | public int getTrackCount() {
60 | return 1;
61 | }
62 |
63 | @Override
64 | public TrackInfo getTrackInfo(int track) {
65 | return new TrackInfo(format.mimeType, durationUs);
66 | }
67 |
68 | @Override
69 | public void enable(int track, long timeUs) {
70 | }
71 |
72 | @Override
73 | public void disable(int track) {
74 | }
75 |
76 | @Override
77 | public boolean continueBuffering(long playbackPositionUs) throws IOException {
78 | return true;
79 | }
80 |
81 | @Override
82 | public synchronized int readData(int track, long playbackPositionUs,
83 | MediaFormatHolder formatHolder, SampleHolder sampleHolder,
84 | boolean onlyReadDiscontinuity) throws IOException {
85 |
86 | if(onlyReadDiscontinuity){
87 | return NOTHING_READ;
88 | }
89 |
90 | if(!formatSent){
91 |
92 | formatHolder.format = this.format;
93 | formatSent = true;
94 | return FORMAT_READ;
95 | }
96 |
97 | if(samples.peek() == null)
98 | return NOTHING_READ;
99 |
100 |
101 | if (sampleHolder.data != null){
102 |
103 | Sample sample = samples.poll();
104 | if(sample != null){
105 | sampleHolder.size = sample.data.length;
106 | sampleHolder.data.put(sample.data);
107 |
108 | sampleHolder.timeUs = sample.timestampUs;
109 | sampleHolder.flags = sample.keyframe ? android.media.MediaExtractor.SAMPLE_FLAG_SYNC : 0;
110 |
111 | if(sample.isVideo && sample.keyframe)
112 | Log.i(TAG, "video keyframe " +" timeS: " + sampleHolder.timeUs/1000000 );
113 | }
114 |
115 | }else{
116 | sampleHolder.size = 0;
117 | }
118 |
119 | return SAMPLE_READ;
120 | }
121 |
122 | @Override
123 | public void seekToUs(long timeUs) {
124 | }
125 |
126 | @Override
127 | public long getBufferedPositionUs() {
128 | return TrackRenderer.UNKNOWN_TIME_US;
129 | }
130 |
131 | @Override
132 | public void release() {
133 |
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/RtspPlayer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import android.os.Handler;
7 |
8 | import com.optman.rtp.player.Player;
9 | import com.optman.rtp.player.Statistics;
10 | import com.optman.rtp.player.TcpSinkPlayer;
11 | import com.optman.rtp.player.UdpSinkPlayer;
12 | import com.google.android.exoplayer.MediaFormat;
13 | import com.google.android.exoplayer.VideoSurfaceView;
14 | import com.optman.rtsp.RtspClient;
15 | import com.optman.rtsp.RtspClientListener;
16 |
17 | public class RtspPlayer implements Player{
18 |
19 | private int videoReceivePort = 40300;
20 | private int audioReceivePort = 40302;
21 |
22 | private Player player;
23 | private RtspClient rtspVideo, rtspAudio;
24 |
25 | int readyCount;
26 |
27 |
28 | public RtspPlayer(String url, VideoSurfaceView view, Handler handler, final int sourceCount, boolean overTcp) {
29 |
30 | if(overTcp)
31 | player = new TcpSinkPlayer(view, handler, sourceCount);
32 | else
33 | player = new UdpSinkPlayer(videoReceivePort, audioReceivePort, view, handler, sourceCount);
34 |
35 | rtspVideo = new RtspClient(url, true, (overTcp ? 0 : videoReceivePort), new RtspClientListener(){
36 |
37 | @Override
38 | public void onReady(RtspClient client) {
39 |
40 | List initData = new ArrayList();
41 | for(int i = 0; client.sps_pps!= null && i < client.sps_pps.size(); i++){
42 | byte[] src = client.sps_pps.get(i);
43 | byte[] dst = new byte[src.length + 4];
44 | dst[0] = 0;
45 | dst[1] = 0;
46 | dst[2] = 0;
47 | dst[3] = 1;
48 | System.arraycopy(src, 0, dst, 4, src.length);
49 |
50 | initData.add(dst);
51 | }
52 |
53 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1024*1024, 0,0, initData);
54 |
55 | player.setVideoFormat(format);
56 |
57 | onRtspReady(sourceCount);
58 | }
59 |
60 | @Override
61 | public void onRtpPacket(byte[] data, int dataSize) {
62 | if(player != null)
63 | player.addVideoPacket(data, dataSize);
64 | }});
65 |
66 | if(sourceCount > 1)
67 | rtspAudio = new RtspClient(url, false, (overTcp ? 0 : audioReceivePort), new RtspClientListener(){
68 |
69 | @Override
70 | public void onReady(RtspClient client) {
71 |
72 | if(client.audioConfig == null)
73 | return;
74 |
75 | List initData= new ArrayList();
76 | initData.add(client.audioConfig);
77 |
78 | MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", 64*1024, client.channel, client.sampleRate, initData);
79 |
80 | player.setAudioFormat(format);
81 |
82 | onRtspReady(sourceCount);
83 | }
84 |
85 | @Override
86 | public void onRtpPacket(byte[] data, int dataSize) {
87 | if(player != null)
88 | player.addAudioPacket(data, dataSize);
89 | }});
90 | }
91 |
92 | private void onRtspReady(int sourceCount){
93 | readyCount++;
94 |
95 | if(readyCount == sourceCount)
96 | player.start();
97 |
98 | }
99 |
100 | @Override
101 | public void start() {
102 |
103 | rtspVideo.start();
104 | if(rtspAudio != null)
105 | rtspAudio.start();
106 |
107 | }
108 |
109 | @Override
110 | public void stop() {
111 |
112 | player.stop();
113 | player = null;
114 |
115 | rtspVideo.stop();
116 | if(rtspAudio != null)
117 | rtspAudio.stop();
118 | }
119 |
120 | @Override
121 | public void setVideoFormat(MediaFormat format) {
122 | }
123 |
124 | @Override
125 | public void setAudioFormat(MediaFormat format) {
126 | }
127 |
128 | @Override
129 | public void addVideoPacket(byte[] data, int dataSize) {
130 | }
131 |
132 | @Override
133 | public void addAudioPacket(byte[] data, int dataSize) {
134 | }
135 |
136 | @Override
137 | public Statistics getStats() {
138 | return player.getStats();
139 | }
140 |
141 | @Override
142 | public void setJitterBuffer(long timeUs) {
143 | player.setJitterBuffer(timeUs);
144 | }
145 |
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/SimplePlayer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import android.os.Handler;
4 |
5 | import com.optman.rtp.player.RtpPlayer;
6 | import com.optman.rtp.player.RtpSampleSource;
7 | import com.optman.rtp.player.Statistics;
8 | import com.google.android.exoplayer.MediaFormat;
9 | import com.google.android.exoplayer.VideoSurfaceView;
10 | import com.optman.rtp.receiver.Sample;
11 |
12 | public class SimplePlayer {
13 |
14 | //private static final String TAG = "SimplePlayer";
15 |
16 | private RtpPlayer player;
17 |
18 | private int sourceCount;
19 |
20 | boolean firstBuffered = false;
21 | private long firstVideoSampleTimeUs = 0;
22 | private long firstAudioSampleTimeUs = 0;
23 | private long jitterBufferUs = 2*1000*1000;
24 |
25 | private RtpSampleSource videoSource;
26 | private RtpSampleSource audioSource;
27 |
28 | private MediaFormat videoFormat;
29 | private MediaFormat audioFormat;
30 |
31 | public Statistics stats;
32 |
33 | public SimplePlayer(VideoSurfaceView view, Handler handler, int sourceCount){
34 |
35 | this.sourceCount = sourceCount;
36 |
37 | stats = new Statistics();
38 |
39 | player = new RtpPlayer(sourceCount, view, handler, stats);
40 | }
41 |
42 | public void setVideoFormat(MediaFormat format){
43 | this.videoFormat = format;
44 | }
45 |
46 | public void setAudioFormat(MediaFormat format){
47 | this.audioFormat = format;
48 | }
49 |
50 | public void setJitterBuffer(long timeUs){
51 | this.jitterBufferUs = timeUs;
52 | }
53 |
54 | public void start(){
55 | videoSource = new RtpSampleSource();
56 | videoSource.setFormat(videoFormat);
57 |
58 | audioSource = new RtpSampleSource();
59 | audioSource.setFormat(audioFormat);
60 | }
61 |
62 | public void stop(){
63 | player.close();
64 | player = null;
65 | }
66 |
67 | protected void onVideoSample(Sample sample){
68 |
69 | if(videoSource == null)
70 | return;
71 |
72 | //ignore sample time stamp.
73 | if(this.jitterBufferUs == 0)
74 | sample.timestampUs = System.nanoTime()/1000;
75 |
76 | //Log.i(TAG, "video sample " + sample.timestampUs);
77 |
78 | if(firstVideoSampleTimeUs == 0)
79 | firstVideoSampleTimeUs = sample.timestampUs;
80 |
81 |
82 | if(!firstBuffered && sample.timestampUs > firstAudioSampleTimeUs //video should catch up with audio.
83 | && (sample.timestampUs - firstVideoSampleTimeUs > jitterBufferUs)) //video should have enough buffer.
84 | {
85 | // if(sourceCount > 1 && (firstVideoSampleTimeUs - firstAudioSampleTimeUs) < -10*1000000)
86 | // throw new RuntimeException();
87 |
88 |
89 | firstBuffered = true;
90 | readyToPlay();
91 | }
92 |
93 | //only video
94 | if(sourceCount == 1){
95 | sample.timestampUs -= firstVideoSampleTimeUs;
96 | sample.timestampUs += jitterBufferUs;
97 | }
98 |
99 | videoSource.addSample(sample);
100 |
101 | }
102 |
103 | public void onAudioSample(Sample sample){
104 |
105 | if(audioSource == null)
106 | return;
107 |
108 | //Log.i(TAG, "audio sample " + sample.timestampUs);
109 |
110 | if(firstAudioSampleTimeUs == 0){
111 | firstAudioSampleTimeUs = sample.timestampUs;
112 |
113 | //only audio
114 | if(sourceCount == 1 )
115 | readyToPlay();
116 | }
117 |
118 | audioSource.addSample(sample);
119 |
120 | }
121 |
122 | protected void readyToPlay(){
123 |
124 | if(sourceCount > 1)
125 | player.prepare(videoSource, audioSource);
126 | else
127 | player.prepare(videoSource);
128 |
129 | player.play();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/Statistics.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | public class Statistics {
4 | public int videoFrameCount;
5 | public int audioFrameCount;
6 | public int syncframeCount;
7 | public int renderDropframeCount;
8 | public int transportDropPacketCount;
9 | public int playbackState;
10 | }
11 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/TcpSinkPlayer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import android.os.Handler;
4 |
5 | import com.optman.rtp.player.Player;
6 | import com.optman.rtp.player.SimplePlayer;
7 | import com.optman.rtp.player.Statistics;
8 | import com.google.android.exoplayer.MediaFormat;
9 | import com.google.android.exoplayer.VideoSurfaceView;
10 | import com.optman.rtp.receiver.RtpAacStream;
11 | import com.optman.rtp.receiver.RtpAvcStream;
12 | import com.optman.rtp.receiver.RtpStream;
13 | import com.optman.rtp.receiver.Sample;
14 | import com.optman.rtp.receiver.SampleHandler;
15 |
16 | public class TcpSinkPlayer implements Player {
17 |
18 | private RtpStream videoStream;
19 | private RtpStream audioStream;
20 |
21 | private SimplePlayer player;
22 |
23 | private int audioSampleRate;
24 |
25 |
26 | public TcpSinkPlayer(VideoSurfaceView view, Handler handler, int sourceCount) {
27 |
28 | player = new SimplePlayer(view, handler, sourceCount);
29 | }
30 |
31 |
32 | public void setVideoFormat(MediaFormat format){
33 | player.setVideoFormat(format);
34 | }
35 |
36 |
37 | public void setAudioFormat(MediaFormat format){
38 | player.setAudioFormat(format);
39 | audioSampleRate = format.sampleRate;
40 | }
41 |
42 | public void start(){
43 |
44 | player.start();
45 |
46 | startRtp();
47 | }
48 |
49 | public void stop(){
50 |
51 | stopRtp();
52 |
53 | player.stop();
54 | player = null;
55 | }
56 |
57 | void startRtp(){
58 |
59 | videoStream = new RtpAvcStream(new SampleHandler(){
60 |
61 | @Override
62 | public void onSample(Sample sample) {
63 | player.onVideoSample(sample);
64 | }
65 |
66 | }, getStats());
67 |
68 | audioStream = new RtpAacStream(audioSampleRate, new SampleHandler(){
69 |
70 | @Override
71 | public void onSample(Sample sample) {
72 | player.onAudioSample(sample);
73 | }
74 |
75 | }, getStats());
76 | }
77 |
78 | void stopRtp(){
79 |
80 | if(videoStream != null)
81 | videoStream.close();
82 |
83 | if(audioStream != null)
84 | audioStream.close();
85 |
86 | }
87 |
88 |
89 | @Override
90 | public void addVideoPacket(byte[] data, int dataSize) {
91 | videoStream.onRtp(data, dataSize);
92 | }
93 |
94 |
95 | @Override
96 | public void addAudioPacket(byte[] data, int dataSize) {
97 | audioStream.onRtp(data, dataSize);
98 | }
99 |
100 |
101 | @Override
102 | public Statistics getStats() {
103 | return player.stats;
104 | }
105 |
106 |
107 | @Override
108 | public void setJitterBuffer(long timeUs) {
109 | player.setJitterBuffer(timeUs);
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/player/UdpSinkPlayer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.player;
2 |
3 | import java.net.SocketException;
4 |
5 | import android.os.Handler;
6 |
7 | import com.optman.rtp.player.Player;
8 | import com.optman.rtp.player.SimplePlayer;
9 | import com.optman.rtp.player.Statistics;
10 | import com.google.android.exoplayer.MediaFormat;
11 | import com.google.android.exoplayer.VideoSurfaceView;
12 | import com.optman.rtp.receiver.RtpAacStream;
13 | import com.optman.rtp.receiver.RtpAvcStream;
14 | import com.optman.rtp.receiver.RtpStream;
15 | import com.optman.rtp.receiver.Sample;
16 | import com.optman.rtp.receiver.SampleHandler;
17 | import com.optman.rtp.receiver.UdpServer;
18 |
19 | public class UdpSinkPlayer implements Player{
20 |
21 | //private final static String TAG = "UdpSinkPlayer";
22 |
23 | private int videoReceivePort;
24 | private int audioReceivePort;
25 |
26 | private UdpServer videoReceiver;
27 | private UdpServer audioReceiver;
28 |
29 | private RtpStream videoStream;
30 | private RtpStream audioStream;
31 |
32 | private SimplePlayer player;
33 |
34 | private int audioSampleRate;
35 |
36 | private int sourceCount;
37 |
38 |
39 | public UdpSinkPlayer(int videoReceivePort, int audioReceivePort, VideoSurfaceView view, Handler handler, int sourceCount) {
40 |
41 | player = new SimplePlayer(view, handler, sourceCount);
42 |
43 | this.videoReceivePort = videoReceivePort;
44 | this.audioReceivePort = audioReceivePort;
45 | this.sourceCount = sourceCount;
46 | }
47 |
48 |
49 | public void setVideoFormat(MediaFormat format){
50 | player.setVideoFormat(format);
51 | }
52 |
53 |
54 | public void setAudioFormat(MediaFormat format){
55 | player.setAudioFormat(format);
56 | if(format != null)
57 | audioSampleRate = format.sampleRate;
58 | }
59 |
60 | public void start(){
61 |
62 | player.start();
63 |
64 | try {
65 | startRtp();
66 | } catch (SocketException e) {
67 | e.printStackTrace();
68 | }
69 |
70 | }
71 |
72 | public void stop(){
73 |
74 | stopRtp();
75 |
76 | player.stop();
77 | player = null;
78 | }
79 |
80 | void startRtp() throws SocketException{
81 |
82 |
83 | videoStream = new RtpAvcStream(new SampleHandler(){
84 |
85 | @Override
86 | public void onSample(Sample sample) {
87 | player.onVideoSample(sample);
88 | }
89 |
90 | }, getStats());
91 | videoReceiver = new UdpServer(videoReceivePort, videoStream);
92 | videoReceiver.open();
93 |
94 | if(sourceCount > 1){
95 |
96 | audioStream = new RtpAacStream(audioSampleRate, new SampleHandler(){
97 |
98 | @Override
99 | public void onSample(Sample sample) {
100 | player.onAudioSample(sample);
101 | }
102 |
103 | }, getStats());
104 | audioReceiver = new UdpServer(audioReceivePort, audioStream);
105 | audioReceiver.open();
106 |
107 | }
108 | }
109 |
110 | void stopRtp(){
111 |
112 | if(videoStream != null){
113 | videoStream.close();
114 | videoStream = null;
115 | }
116 | if(videoReceiver != null){
117 | videoReceiver.close();
118 | videoReceiver = null;
119 | }
120 |
121 | if(audioStream != null){
122 | audioStream.close();
123 | audioStream = null;
124 | }
125 | if(audioReceiver != null){
126 | audioReceiver.close();
127 | audioReceiver = null;
128 | }
129 |
130 | }
131 |
132 |
133 | @Override
134 | public void addVideoPacket(byte[] data, int dataSize) {
135 | }
136 |
137 | @Override
138 | public void addAudioPacket(byte[] data, int dataSize) {
139 | }
140 |
141 |
142 | @Override
143 | public Statistics getStats() {
144 | return player.stats;
145 | }
146 |
147 | @Override
148 | public void setJitterBuffer(long timeUs) {
149 | player.setJitterBuffer(timeUs);
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/RtpAacStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.IOException;
5 |
6 | import com.optman.librtsp.BuildConfig;
7 | import com.optman.rtp.receiver.RtpStream;
8 | import com.optman.rtp.receiver.Sample;
9 | import com.optman.rtp.receiver.SampleHandler;
10 | import com.optman.rtp.player.Statistics;
11 |
12 | public class RtpAacStream extends RtpStream{
13 |
14 | private int sampleRate;
15 | protected SampleHandler handler;
16 |
17 | public RtpAacStream(int sampleRate, SampleHandler handler, Statistics stats){
18 | super(stats);
19 | this.sampleRate = sampleRate;
20 | this.handler = handler;
21 | }
22 |
23 | @Override
24 | protected boolean decodePayload(DataInputStream reader) throws IOException {
25 | int auHeadersLength = reader.readShort();
26 | auHeadersLength /= 8;
27 |
28 |
29 | if(BuildConfig.DEBUG && auHeadersLength != 2)
30 | throw new RuntimeException();
31 |
32 | int auTotalSize = 0;
33 |
34 |
35 | for(int i = 0; i < auHeadersLength/2; i++){
36 | long auHeader = reader.readShort() & ((-1) >>> 32);
37 |
38 | int auSize = (int)(auHeader >>> 3);
39 | //int auIndexDelta = (int)(auHeader & 0x07);
40 |
41 | auTotalSize += auSize;
42 | }
43 |
44 | if(BuildConfig.DEBUG && auTotalSize != reader.available())
45 | throw new RuntimeException();
46 |
47 | Sample sample = new Sample();
48 | sample.keyframe = true;
49 | sample.timestampUs = this.timestamp*1000000/sampleRate;
50 | sample.data = new byte[reader.available()];
51 | reader.readFully(sample.data);
52 |
53 | if(handler != null)
54 | handler.onSample(sample);
55 |
56 | if(stats != null)
57 | stats.audioFrameCount++;
58 |
59 | return true;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/RtpAvcStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataInputStream;
5 | import java.io.IOException;
6 |
7 | import com.optman.librtsp.BuildConfig;
8 | import com.optman.rtp.receiver.RtpStream;
9 | import com.optman.rtp.receiver.Sample;
10 | import com.optman.rtp.receiver.SampleHandler;
11 | import com.optman.rtp.player.Statistics;
12 |
13 | import android.util.Log;
14 |
15 | public class RtpAvcStream extends RtpStream{
16 |
17 |
18 | private boolean sync;
19 | private ByteArrayOutputStream fragmentBuf;
20 | private int fuaType;
21 |
22 | private SampleHandler handler;
23 |
24 | private boolean isSync = false;
25 | private boolean enforceSync = false;
26 |
27 | public RtpAvcStream(SampleHandler handler, Statistics stats){
28 | super(stats);
29 | this.handler = handler;
30 | }
31 |
32 | public boolean decodePayload(DataInputStream reader) throws IOException{
33 |
34 | if(isSync && discontinue())
35 | isSync = false;
36 |
37 | int naluHeader= reader.readByte() & 0xFF;
38 |
39 | //int forbidden_zero_bit = (naluHeader >>> 7) & 0x1;
40 | //int nal_ref_idc = (naluHeader >>> 5) & 0x03;
41 | int type = naluHeader & 0x1F;
42 |
43 | //Log.i("NAL", "F:" + forbidden_zero_bit + " NRI:" + nal_ref_idc + " Type:" + type);
44 |
45 | boolean result =false;
46 |
47 | if(type >= 1 && type <= 23){
48 | result = decodeSingleNalu(type, reader, (byte)naluHeader);
49 | }/*else if(type == 24){
50 | decodeStapA(reader);
51 | }else if(type == 25){
52 | decodeStapB(reader);
53 | }else if(type == 26){
54 | decodeMtap16(reader);
55 | }else if(type == 27){
56 | decodeMtap24(reader);
57 | }*/else if(type == 28){
58 | result = decodeFuA(reader, (byte)naluHeader);
59 | }/*else if(type == 29){
60 | decodeFuB(reader);
61 | }*/ else if(type == 30 || type == 31){
62 | //ignore.
63 | }
64 | else{
65 | Log.e("NAL", "unknown type!!!!!" + type);
66 | if(BuildConfig.DEBUG)
67 | throw new RuntimeException();
68 | }
69 |
70 | return result;
71 | }
72 |
73 | private void onNalu(Sample nalu){
74 |
75 | if(stats != null){
76 | stats.videoFrameCount++;
77 | if(nalu.keyframe)
78 | stats.syncframeCount++;
79 | }
80 |
81 | if(nalu.keyframe)
82 | isSync = true;
83 |
84 | if(enforceSync && !isSync){
85 | Log.i("NAL", "drop unsync nalu.");
86 | return;
87 | }
88 |
89 | nalu.isVideo = true;
90 | if(handler != null){
91 | nalu.data = addStartcode(nalu.data);
92 | handler.onSample(nalu);
93 | }
94 | }
95 |
96 | private byte[] addStartcode(byte[] src){
97 | byte[] dst = new byte[src.length + 4];
98 | dst[0] = 0;
99 | dst[1] = 0;
100 | dst[2] = 0;
101 | dst[3] = 1;
102 | System.arraycopy(src, 0, dst, 4, src.length);
103 |
104 | return dst;
105 | }
106 |
107 | private boolean decodeSingleNalu(int type, DataInputStream reader, byte header) throws IOException {
108 |
109 | Sample nalu = new Sample();
110 | nalu.timestampUs = timestamp*1000000/90000;
111 | nalu.keyframe = (type == 5);
112 |
113 | nalu.data = new byte[reader.available() + 1];
114 | nalu.data[0] = header;
115 | reader.readFully(nalu.data, 1, reader.available());
116 |
117 | onNalu(nalu);
118 |
119 | return true;
120 | }
121 |
122 | // private void decodeStapA(DataInputStream reader) {
123 | // if(BuildConfig.DEBUG)
124 | // throw new RuntimeException();
125 | //
126 | // }
127 | //
128 | // private void decodeStapB(DataInputStream reader) {
129 | // if(BuildConfig.DEBUG)
130 | // throw new RuntimeException();
131 | // }
132 | //
133 | // private void decodeMtap16(DataInputStream reader) {
134 | // if(BuildConfig.DEBUG)
135 | // throw new RuntimeException();
136 | // }
137 | //
138 | // private void decodeMtap24(DataInputStream reader) {
139 | // if(BuildConfig.DEBUG)
140 | // throw new RuntimeException();
141 | // }
142 |
143 | private boolean decodeFuA(DataInputStream reader, byte header) throws IOException {
144 | int fuHeader = reader.readByte() & 0xff;
145 |
146 | int startBit = (fuHeader >> 7) & 0x01;
147 | int endBit = (fuHeader >> 6) & 0x01;
148 | int reservedBit = (fuHeader >> 5) & 0x01;
149 | if(reservedBit != 0)
150 | throw new IOException();
151 |
152 | int naluType = fuHeader & 0x1f;
153 |
154 | //Log.i("FUA", "FUA S:" + startBit + " E:" + endBit + " T:" + naluType + " SN:" + currentSeqNum);
155 |
156 | if(discontinue() && sync){
157 | Log.i("FUA", "droped!");
158 | sync = false;
159 | return false;
160 | }
161 |
162 | if(startBit == 1){
163 | fuaType = naluType;
164 | fragmentBuf = new ByteArrayOutputStream();
165 | int naluHeader = (header | 0x1f) & (0xe0 | naluType);
166 | fragmentBuf.write(naluHeader);
167 | sync = true;
168 | }
169 |
170 | if(!sync)
171 | return false;
172 |
173 | byte[] data = new byte[reader.available()];
174 | reader.readFully(data);
175 | fragmentBuf.write(data);
176 |
177 |
178 | if(endBit == 1){
179 | Sample nalu = new Sample();
180 | nalu.timestampUs = timestamp*1000000/90000;
181 | nalu.keyframe = (fuaType == 5);
182 | nalu.data = fragmentBuf.toByteArray();
183 |
184 | onNalu(nalu);
185 |
186 | sync = false;
187 | }
188 |
189 | return true;
190 | }
191 |
192 | // private void decodeFuB(DataInputStream reader) {
193 | // if(BuildConfig.DEBUG)
194 | // throw new RuntimeException();
195 | // }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/RtpSink.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | public interface RtpSink {
4 | public void onRtp(byte[] data, int dataSize);
5 | }
6 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/RtpStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.DataInputStream;
5 | import java.io.IOException;
6 |
7 | import com.optman.rtp.receiver.RtpSink;
8 | import com.optman.rtp.player.Statistics;
9 |
10 | import android.util.Log;
11 |
12 | public abstract class RtpStream implements RtpSink{
13 |
14 | protected long timestamp;
15 |
16 | private long maxTimestamp = 0;
17 | private long tsWrapAroundCount = 0;
18 |
19 | private long maxSeqNum = 0;
20 | private long snWrapAroundCount = 0;
21 |
22 | private long lastSeqNum;
23 | protected long currentSeqNum;
24 |
25 | protected Statistics stats;
26 |
27 | public RtpStream(Statistics stats){
28 | this.stats = stats;
29 | }
30 |
31 | public void close(){
32 |
33 | }
34 |
35 | public void onRtp(byte[] data, int dataSize){
36 | try {
37 | decodeRtp(data, dataSize);
38 | } catch (IOException e) {
39 | e.printStackTrace();
40 | }
41 | }
42 |
43 |
44 | private void decodeRtp(byte[] buf, int length) throws IOException{
45 |
46 | DataInputStream reader = new DataInputStream(new ByteArrayInputStream(buf, 0, length));
47 |
48 | byte b = reader.readByte();
49 |
50 | // int version = (b & 0xc0) >> 6;
51 | // boolean padding = (b & 0x20) != 0;
52 | // boolean extension = (b & 0x10) != 0;
53 | int csrcCount = b & 0x0f;
54 |
55 | b = reader.readByte();
56 |
57 | //boolean marker = (b & 0x80 ) != 0;
58 | // int payloadType = b & 0x7F;
59 |
60 | long sequenceNumber = reader.readShort() & 0x000000000000ffffL;
61 | long timestamp = reader.readInt() & 0x00000000ffffffffL;
62 | /*int ssrc = */reader.readInt();
63 |
64 | for(int i = 0; i < csrcCount; i++)
65 | reader.readInt();
66 |
67 |
68 | // Log.i("RTP", "v:" + version + " p:" + padding + " e:" + extension + " p:" + payloadType +
69 | // " sn:" + sequenceNumber + " ts:" + timestamp);
70 |
71 |
72 | this.currentSeqNum = extendSequenceNumber(sequenceNumber);
73 | this.timestamp = extendTimestamp(timestamp);
74 |
75 |
76 | //duplicated packet.
77 | if(currentSeqNum == lastSeqNum){
78 | Log.i("RTP", "duplicate detected!");
79 | return;
80 | }
81 |
82 | if(discontinue() && lastSeqNum != 0){
83 | Log.i("RTP", "discontinue detected! " + (currentSeqNum - lastSeqNum));
84 |
85 |
86 | if(this.stats != null && currentSeqNum > lastSeqNum)
87 | this.stats.transportDropPacketCount += currentSeqNum - lastSeqNum;
88 | }
89 |
90 | decodePayload(reader);
91 |
92 | lastSeqNum = currentSeqNum;
93 | }
94 |
95 | protected boolean discontinue(){
96 | return lastSeqNum + 1 != currentSeqNum;
97 | }
98 |
99 | private long extendTimestamp(long timestamp){
100 |
101 | final long maxDelta = 90000*10; //90K clock rate in 10 seconds.
102 |
103 | long udelta = (timestamp - maxTimestamp) & 0x00000000ffffffffL;
104 |
105 | long wrapAroundCount;
106 |
107 | if(udelta < maxDelta){
108 | if(timestamp < maxTimestamp)
109 | tsWrapAroundCount++;
110 |
111 | maxTimestamp = timestamp;
112 |
113 | wrapAroundCount = tsWrapAroundCount;
114 |
115 | }else if(tsWrapAroundCount > 0){
116 | //a delay from last round.
117 | wrapAroundCount = tsWrapAroundCount - 1;
118 | }else{
119 | //first round
120 | maxTimestamp = timestamp;
121 | wrapAroundCount = 0;
122 | }
123 |
124 |
125 | return timestamp + 0xFFFFFFFF*wrapAroundCount;
126 |
127 | }
128 |
129 | private long extendSequenceNumber(long seq){
130 |
131 | final long maxDropout = 3000;
132 | //final long maxMisorder = 100;
133 |
134 | long udelta = (seq - maxSeqNum) & 0x000000000000ffffL;
135 |
136 | long wrapAroundCount;
137 |
138 |
139 | if(udelta < maxDropout){
140 | if(seq < maxSeqNum){
141 | snWrapAroundCount++;
142 | Log.i("RTP", "sequence wrap around.");
143 | }
144 |
145 | maxSeqNum = seq;
146 |
147 | wrapAroundCount = snWrapAroundCount;
148 |
149 | }else if(snWrapAroundCount > 0){
150 | //Disorder, a delay from last round.
151 | wrapAroundCount = (snWrapAroundCount - 1);
152 |
153 | }else{
154 | //first round
155 | maxSeqNum = seq;
156 | wrapAroundCount = 0;
157 | }
158 |
159 | return seq + 0xFFFF*wrapAroundCount;
160 | }
161 |
162 |
163 | protected abstract boolean decodePayload(DataInputStream reader) throws IOException;
164 |
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/Sample.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | public class Sample {
4 | public boolean isVideo;
5 | public long timestampUs;
6 | public boolean keyframe;
7 | public byte[] data;
8 | }
9 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/SampleHandler.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | import com.optman.rtp.receiver.Sample;
4 |
5 | public interface SampleHandler {
6 | public void onSample(Sample sample);
7 | }
8 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/receiver/UdpServer.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.receiver;
2 |
3 | import java.io.IOException;
4 | import java.net.DatagramPacket;
5 | import java.net.DatagramSocket;
6 | import java.net.SocketException;
7 |
8 | import com.optman.rtp.receiver.RtpSink;
9 |
10 | import android.util.Log;
11 |
12 | public class UdpServer implements Runnable{
13 |
14 | private DatagramSocket s;
15 | private int port;
16 | protected RtpSink sink;
17 | private Thread thread;
18 |
19 | public UdpServer(int port, RtpSink sink){
20 | this.port = port;
21 | this.sink = sink;
22 | }
23 |
24 | public void open() throws SocketException{
25 | Log.i("RTP", "listen at " + port);
26 | s = new DatagramSocket(port);
27 |
28 | thread = new Thread(this);
29 | thread.start();
30 | }
31 |
32 | public void close(){
33 | s.close();
34 | try {
35 | thread.join();
36 | } catch (InterruptedException e) {
37 | e.printStackTrace();
38 | }
39 | }
40 |
41 | @Override
42 | public void run() {
43 |
44 | byte[] buf = new byte[1500];
45 |
46 | while(true){
47 | try {
48 | DatagramPacket pack = new DatagramPacket(buf, buf.length);
49 | s.receive(pack);
50 |
51 | if(sink != null)
52 | sink.onRtp(pack.getData(), pack.getLength());
53 |
54 | } catch (IOException e) {
55 | e.printStackTrace();
56 | break;
57 | }
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/CameraStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import com.optman.rtp.sender.RtpAvcStream;
7 | import com.optman.rtp.sender.RtpSocket;
8 |
9 | import android.graphics.ImageFormat;
10 | import android.hardware.Camera;
11 | import android.hardware.Camera.Size;
12 | import android.media.MediaCodec;
13 | import android.media.MediaCodecInfo;
14 | import android.media.MediaFormat;
15 | import android.media.MediaCodec.BufferInfo;
16 | import android.util.Log;
17 |
18 |
19 | @SuppressWarnings("deprecation")
20 | public class CameraStream extends RtpAvcStream{
21 |
22 | final static String TAG = "CameraStream";
23 |
24 | private Camera mCamera;
25 | private MediaCodec mMediaCodec;
26 | private ByteBuffer[] sps_pps;
27 | private Thread thread;
28 | private boolean stoped;
29 | private long baseTimeUs;
30 |
31 |
32 | public CameraStream(long baseTimeUs, Camera c, RtpSocket socket) {
33 | super(socket);
34 |
35 | this.baseTimeUs = baseTimeUs;
36 | mCamera = c;
37 | }
38 |
39 | public void start() {
40 |
41 | stoped = false;
42 |
43 | final Size size = mCamera.getParameters().getPreviewSize();
44 |
45 | final int imageBufferSize = size.width*size.height*ImageFormat.getBitsPerPixel(ImageFormat.NV21)/8;
46 | final int yuvImageBufferSize = size.width*size.height*ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)/8;
47 |
48 | final byte[] yuvImageBuffer = new byte[yuvImageBufferSize];
49 |
50 | try {
51 | mMediaCodec = MediaCodec.createEncoderByType("video/avc");
52 | } catch (IOException e1) {
53 | e1.printStackTrace();
54 | }
55 | MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", size.width, size.height);
56 | mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 2*1000*1000);
57 | mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
58 | mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
59 | mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
60 | mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
61 | mMediaCodec.start();
62 |
63 |
64 | Camera.PreviewCallback callback = new Camera.PreviewCallback() {
65 | ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
66 | @Override
67 | public void onPreviewFrame(byte[] data, Camera camera) {
68 | long ptsUs = System.nanoTime()/1000 - baseTimeUs;
69 | try {
70 | int bufferIndex = mMediaCodec.dequeueInputBuffer(500000);
71 | if (bufferIndex>=0) {
72 |
73 | ByteBuffer buf = inputBuffers[bufferIndex];
74 | buf.clear();
75 |
76 | if (data == null)
77 | Log.e(TAG,"Symptom of the \"Callback buffer was to small\" problem...");
78 | else {
79 | YV12toYUV420PackedSemiPlanar(data, yuvImageBuffer, size.width, size.height);
80 | buf.put(yuvImageBuffer);
81 | //buf.put(data);
82 | }
83 |
84 | mMediaCodec.queueInputBuffer(bufferIndex, 0, buf.position(), ptsUs, 0);
85 | } else {
86 | Log.e(TAG,"No buffer available !");
87 | }
88 | } finally {
89 | mCamera.addCallbackBuffer(data);
90 | }
91 | }
92 | };
93 |
94 | for (int i=0;i<10;i++) mCamera.addCallbackBuffer(new byte[imageBufferSize]);
95 | mCamera.setPreviewCallbackWithBuffer(callback);
96 |
97 |
98 | thread = new Thread(new Runnable(){
99 |
100 | @Override
101 | public void run() {
102 |
103 | ByteBuffer[] outBuffers = mMediaCodec.getOutputBuffers();
104 | BufferInfo info = new BufferInfo();
105 | while(!stoped){
106 | int bufferIndex = mMediaCodec.dequeueOutputBuffer(info, 200*1000);
107 | if(bufferIndex >= 0){
108 | ByteBuffer buf = outBuffers[bufferIndex];
109 |
110 | buf.position(info.offset);
111 | buf.limit(info.offset + info.size);
112 |
113 | addSample(buf, info.size, info.presentationTimeUs, info.flags);
114 |
115 |
116 | mMediaCodec.releaseOutputBuffer(bufferIndex, false);
117 | }else if (bufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
118 | outBuffers = mMediaCodec.getOutputBuffers();
119 | }else if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
120 | }else{
121 | //throw new RuntimeException();
122 | }
123 | }
124 | }
125 |
126 | });
127 |
128 | thread.start();
129 |
130 | }
131 |
132 | public void stop(){
133 |
134 |
135 | mCamera.setPreviewCallbackWithBuffer(null);
136 |
137 | stoped = true;
138 |
139 | try {
140 | thread.join(2*1000);
141 | } catch (InterruptedException e) {
142 | e.printStackTrace();
143 | }
144 |
145 | mMediaCodec.stop();
146 | mMediaCodec.release();
147 | mMediaCodec = null;
148 |
149 | }
150 |
151 | public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) {
152 | /*
153 | * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12
154 | * We convert by putting the corresponding U and V bytes together (interleaved).
155 | */
156 | final int frameSize = width * height;
157 | final int qFrameSize = frameSize/4;
158 |
159 | System.arraycopy(input, 0, output, 0, frameSize); // Y
160 |
161 | for (int i = 0; i < qFrameSize; i++) {
162 | output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
163 | output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
164 | }
165 | return output;
166 | }
167 |
168 | private void addSample(ByteBuffer buf, int size, long timeUs, int flags){
169 |
170 | try{
171 |
172 | int startCode = buf.getInt(); //skip start code.
173 | if(startCode != 0x1000000)
174 | throw new RuntimeException();
175 |
176 | size -= 4;
177 |
178 |
179 | if(flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG){
180 | sps_pps = new ByteBuffer[2];
181 |
182 | int pos = findStartcode(buf);
183 | if(pos < 0)
184 | throw new RuntimeException();
185 |
186 | int first_sps_pps_size = pos - buf.position();
187 |
188 | sps_pps[0] = ByteBuffer.allocate(first_sps_pps_size);
189 | sps_pps[0].mark();
190 | buf.get(sps_pps[0].array());
191 |
192 | buf.getInt(); //skip start code
193 |
194 | sps_pps[1] = ByteBuffer.allocate(size - first_sps_pps_size - 4);
195 | sps_pps[1].mark();
196 |
197 | buf.get(sps_pps[1].array());
198 |
199 | return;
200 |
201 | }else if(flags == MediaCodec.BUFFER_FLAG_SYNC_FRAME){
202 | sps_pps[0].reset();
203 | sps_pps[1].reset();
204 | addNalu(sps_pps[0].array(), 0, sps_pps[0].capacity(), timeUs);
205 | addNalu(sps_pps[1].array(), 0, sps_pps[1].capacity(), timeUs);
206 | }
207 |
208 | addNalu(buf, size, timeUs);
209 |
210 | }catch(IOException e){
211 |
212 | }
213 |
214 |
215 | }
216 |
217 | private int findStartcode(ByteBuffer buf) {
218 |
219 | buf.mark();
220 |
221 | int zeroCount = 0;
222 |
223 | int pos = -1;
224 |
225 | while(buf.hasRemaining()){
226 | byte b = buf.get();
227 |
228 | if(b == 0){
229 | zeroCount++;
230 | continue;
231 |
232 | }else if( b == 1){
233 | if(zeroCount >= 2){
234 | pos = buf.position() - zeroCount - 1;
235 | break;
236 | }
237 | }
238 |
239 | zeroCount = 0;
240 | }
241 |
242 | buf.reset();
243 |
244 | return pos;
245 | }
246 |
247 | }
248 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/MicStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import com.optman.rtp.sender.RtpAacStream;
7 | import com.optman.rtp.sender.RtpSocket;
8 |
9 | import android.media.AudioFormat;
10 | import android.media.AudioRecord;
11 | import android.media.MediaCodec;
12 | import android.media.MediaCodecInfo;
13 | import android.media.MediaFormat;
14 | import android.media.MediaMuxer;
15 | import android.media.MediaRecorder;
16 | import android.media.MediaCodec.BufferInfo;
17 | import android.media.MediaMuxer.OutputFormat;
18 | import android.os.Environment;
19 | import android.util.Log;
20 |
21 |
22 | @SuppressWarnings("deprecation")
23 | public class MicStream extends RtpAacStream{
24 |
25 | private AudioRecord record;
26 | private MediaCodec mMediaCodec;
27 | private int sampleRate;
28 | private int channelNum;
29 | private boolean stoped;
30 | private Thread thread;
31 | private byte[] audioConfig;
32 | private long baseTimeUs;
33 |
34 | public MicStream(long baseTimeUs, int sampleRate, int channelNum, RtpSocket socket) {
35 | super(sampleRate, socket);
36 | this.baseTimeUs = baseTimeUs;
37 | this.sampleRate = sampleRate;
38 | this.channelNum =channelNum;
39 | }
40 |
41 | public void start() throws IOException{
42 |
43 | stoped = false;
44 |
45 | int channel = channelNum > 1 ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
46 | int format = AudioFormat.ENCODING_PCM_16BIT;
47 | final int bufSize = AudioRecord.getMinBufferSize(sampleRate, channel, format);
48 |
49 |
50 | MediaFormat mAudioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", sampleRate, channelNum);
51 | mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
52 | mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
53 | mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
54 |
55 |
56 | mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
57 | mMediaCodec.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
58 | mMediaCodec.start();
59 |
60 |
61 | record = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,channel, format, bufSize);
62 | record.startRecording();
63 |
64 | thread = new Thread(new Runnable(){
65 |
66 | @Override
67 | public void run() {
68 |
69 | ByteBuffer data = ByteBuffer.allocateDirect(bufSize);
70 |
71 | ByteBuffer[] inBuffers = mMediaCodec.getInputBuffers();
72 | ByteBuffer[] outBuffers = mMediaCodec.getOutputBuffers();
73 | BufferInfo info = new BufferInfo();
74 |
75 | while(!stoped){
76 | data.clear();
77 | int readSize = record.read(data, data.capacity());
78 | //Log.i("Audio", "readSize:" + readSize);
79 |
80 | if(readSize > 0){
81 | int inBufferIndex = mMediaCodec.dequeueInputBuffer(100*1000);
82 | if(inBufferIndex >= 0){
83 |
84 | long ptsUs = System.nanoTime()/1000 - baseTimeUs;
85 |
86 | ByteBuffer buf = inBuffers[inBufferIndex];
87 | buf.clear();
88 | buf.put(data);
89 |
90 | mMediaCodec.queueInputBuffer(inBufferIndex, 0, readSize, ptsUs, 0);
91 | }
92 | }
93 |
94 | while(!stoped){
95 | int outBufferIndex = mMediaCodec.dequeueOutputBuffer(info, 0);
96 | if(outBufferIndex >= 0){
97 | ByteBuffer buf = outBuffers[outBufferIndex];
98 |
99 | if(info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
100 | {
101 | buf.position(info.offset);
102 | buf.limit(info.offset + info.size);
103 |
104 | try {
105 | addAU(buf, info.size, info.presentationTimeUs);
106 | } catch (IOException e) {
107 | e.printStackTrace();
108 | }
109 |
110 | }
111 |
112 | mMediaCodec.releaseOutputBuffer(outBufferIndex, false);
113 | }else if(outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
114 | MediaFormat format = mMediaCodec.getOutputFormat();
115 | ByteBuffer buf= format.getByteBuffer("csd-0");
116 | audioConfig = new byte[buf.capacity()];
117 | buf.get(audioConfig);
118 |
119 | Log.i("MicStream", audioConfig.toString());
120 |
121 | }else if(outBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
122 | outBuffers = mMediaCodec.getOutputBuffers();
123 | }else{
124 | break;
125 | }
126 |
127 | }
128 |
129 | }
130 |
131 | }
132 |
133 | });
134 |
135 | thread.start();
136 | }
137 |
138 | public void stop(){
139 | stoped = true;
140 |
141 | try {
142 | thread.join(1000);
143 | } catch (InterruptedException e) {
144 | e.printStackTrace();
145 | }
146 |
147 |
148 | mMediaCodec.stop();
149 | mMediaCodec.release();
150 | mMediaCodec = null;
151 |
152 | record.stop();
153 | record.release();
154 | record = null;
155 | }
156 |
157 | public byte[] getAudioConfig(){
158 | return audioConfig;
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/RtpAacStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import com.optman.rtp.sender.RtpSocket;
7 | import com.optman.rtp.sender.RtpStream;
8 |
9 | public class RtpAacStream extends RtpStream {
10 |
11 | public RtpAacStream(int sampleRate, RtpSocket socket){
12 | super(97, sampleRate, socket);
13 | }
14 |
15 | public void addAU(ByteBuffer buf, int size, long timeUs) throws IOException{
16 | byte[] data = new byte[size];
17 | buf.get(data);
18 |
19 | addAU(data, 0, size, timeUs);
20 | }
21 |
22 | public void addAU(byte[] data, int offset, int size, long timeUs) throws IOException{
23 |
24 | int auHeadersLength = 1 * 2 * 8;
25 | int auHeader = size << 3;
26 |
27 | ByteBuffer payload = ByteBuffer.allocate(2 + 2 + size);
28 | payload.putShort((short)auHeadersLength);
29 | payload.putShort((short)auHeader);
30 |
31 | payload.put(data, offset, size);
32 |
33 | addPacket(payload.array(), 0, payload.position(), timeUs);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/RtpAvcStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 |
4 | import java.io.IOException;
5 | import java.nio.ByteBuffer;
6 |
7 | import com.optman.rtp.sender.RtpSocket;
8 | import com.optman.rtp.sender.RtpStream;
9 |
10 | public class RtpAvcStream extends RtpStream {
11 |
12 | //private final static String TAG = "AvcRtpStream";
13 |
14 | public RtpAvcStream(RtpSocket socket) {
15 | super(125, 90000, socket);
16 | }
17 |
18 | public void addNalu(ByteBuffer buf, int size, long timeUs) throws IOException{
19 | byte[] data = new byte[size];
20 | buf.get(data);
21 |
22 | addNalu(data, 0, size, timeUs);
23 | }
24 |
25 | public void addNalu(byte[] data, int offset, int size, long timeUs) throws IOException{
26 | if(size <= 1400){
27 | createSingleUnit(data, offset, size, timeUs);
28 | }else{
29 | createFuA(data, offset, size, timeUs);
30 | }
31 | }
32 |
33 | private void createSingleUnit(byte[] data, int offset, int size, long timeUs) throws IOException {
34 |
35 | //Log.i(TAG, "single nalu type:" + (data[offset] & 0x1f));
36 |
37 | addPacket(data, offset, size, timeUs);
38 | }
39 |
40 |
41 | private void createFuA(byte[] data, int offset, int size, long timeUs) throws IOException {
42 |
43 | byte originHeader = data[offset++];
44 | size -= 1;
45 |
46 | //Log.i(TAG, "FuA nalu type:" + (originHeader & 0x1f));
47 |
48 |
49 | int left = size;
50 | int read = 1400;
51 |
52 |
53 | for(;left > 0; left -= read, offset += read){
54 | byte indicator = (byte)( (originHeader & 0xe0) | 28);
55 | byte naluHeader = (byte)(originHeader & 0x1f);
56 |
57 | if(left < read){
58 | read = left;
59 | }
60 |
61 | if(left == size )
62 | naluHeader = (byte)(naluHeader | (1 << 7));
63 | else if(left == read)
64 | naluHeader = (byte)(naluHeader | (1 << 6));
65 |
66 |
67 | addPacket(new byte[]{indicator, naluHeader}, data, offset, read, timeUs);
68 | }
69 |
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/RtpSocket.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 |
5 | public interface RtpSocket {
6 |
7 | public void sendPacket(byte[] data, int offset, int size) throws IOException;
8 | public void close();
9 | }
10 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/RtpStream.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import com.optman.rtp.sender.RtpSocket;
7 |
8 | public class RtpStream {
9 |
10 | private int payloadType;
11 | private int sampleRate;
12 | private RtpSocket socket;
13 | private short sequenceNumber;
14 |
15 |
16 |
17 | public RtpStream(int pt, int sampleRate, RtpSocket socket){
18 | this.payloadType = pt;
19 | this.sampleRate = sampleRate;
20 | this.socket = socket;
21 | }
22 |
23 | public void addPacket(byte[] data, int offset, int size, long timeUs) throws IOException{
24 | addPacket(null, data, offset, size, timeUs);
25 | }
26 |
27 | public void addPacket(byte[] prefixData, byte[] data, int offset, int size, long timeUs) throws IOException{
28 |
29 | /*
30 | RTP packet header
31 | Bit offset[b] 0-1 2 3 4-7 8 9-15 16-31
32 | 0 Version P X CC M PT Sequence Number
33 | 32 Timestamp
34 | 64 SSRC identifier
35 | */
36 |
37 | ByteBuffer buffer = ByteBuffer.allocate(1500);
38 | buffer.put((byte)(2 << 6));
39 | buffer.put((byte)(payloadType));
40 | buffer.putShort(sequenceNumber++);
41 | buffer.putInt((int)(timeUs*sampleRate/1000000));
42 | buffer.putInt(12345678);
43 |
44 | if(prefixData != null)
45 | buffer.put(prefixData);
46 |
47 | buffer.put(data, offset, size);
48 |
49 | sendPacket(buffer, buffer.position());
50 | }
51 |
52 | protected void sendPacket(ByteBuffer buffer, int size) throws IOException{
53 | socket.sendPacket(buffer.array(), 0, size);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtp/sender/RtpUdp.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtp.sender;
2 |
3 | import java.io.IOException;
4 | import java.net.DatagramPacket;
5 | import java.net.DatagramSocket;
6 | import java.net.InetAddress;
7 | import java.net.SocketException;
8 | import java.net.UnknownHostException;
9 |
10 | import com.optman.rtp.sender.RtpSocket;
11 |
12 | public class RtpUdp implements RtpSocket {
13 |
14 | private DatagramSocket s;
15 |
16 | private InetAddress addr;
17 | private int port;
18 |
19 |
20 | public RtpUdp(String host, int port, boolean broadcast){
21 |
22 | try {
23 | this.addr = InetAddress.getByName(host);
24 | this.port = port;
25 |
26 | s = new DatagramSocket();
27 | s.setBroadcast(broadcast);
28 |
29 | } catch (SocketException e) {
30 | e.printStackTrace();
31 | }catch (UnknownHostException e) {
32 | e.printStackTrace();
33 | }
34 |
35 | }
36 |
37 | public void close(){
38 | s.close();
39 | }
40 |
41 | @Override
42 | public void sendPacket(byte[] data, int offset, int size) {
43 | try {
44 | DatagramPacket p;
45 | p = new DatagramPacket(data, offset, size, addr, port);
46 |
47 | /*
48 | long nowMs = System.nanoTime()/1000000;
49 |
50 | long deltaMs = nowMs - lastSentTimeMs;
51 |
52 | if(deltaMs < 1)
53 | try {
54 | Thread.sleep(1);
55 | } catch (InterruptedException e) {
56 | }
57 |
58 | lastSentTimeMs = nowMs;
59 | */
60 |
61 | s.send(p);
62 |
63 |
64 |
65 | } catch (UnknownHostException e) {
66 | e.printStackTrace();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/RtspClient.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.IOException;
5 | import java.io.PrintWriter;
6 | import java.net.Socket;
7 | import java.net.UnknownHostException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import com.optman.rtsp.RtspClientListener;
12 |
13 | import android.net.Uri;
14 | import android.util.Base64;
15 | import android.util.Log;
16 |
17 | public class RtspClient implements Runnable {
18 |
19 | private RtspClientListener listener;
20 |
21 | private Socket s;
22 | private String session;
23 | private String videoTrackUrl;
24 | private String audioTrackUrl;
25 |
26 | public int sampleRate;
27 | public List sps_pps;
28 | public byte[] audioConfig;
29 | public int channel;
30 |
31 | private String rtspUrl;
32 | private boolean playVideo;
33 | private int receivePort;
34 |
35 | private boolean overTcp;
36 |
37 | private DataInputStream reader;
38 |
39 | private PrintWriter writer;
40 |
41 | Thread thread;
42 |
43 |
44 | public RtspClient(String rtspUrl, boolean playVideo, int receivePort,RtspClientListener listener){
45 | this.rtspUrl = rtspUrl;
46 | this.playVideo = playVideo;
47 | this.receivePort = receivePort;
48 | this.listener = listener;
49 |
50 | this.overTcp = receivePort == 0;
51 | }
52 |
53 | public void start(){
54 | thread = new Thread(this);
55 | thread.start();
56 | }
57 |
58 | public void stop(){
59 | thread.interrupt();
60 | try {
61 | thread.join();
62 | } catch (InterruptedException e) {
63 | e.printStackTrace();
64 | }
65 | }
66 |
67 | @Override
68 | public void run() {
69 |
70 | try {
71 | rtspPlay();
72 | } catch (UnknownHostException e) {
73 | e.printStackTrace();
74 | } catch (IOException e) {
75 | e.printStackTrace();
76 | } catch (InterruptedException e) {
77 | e.printStackTrace();
78 | }
79 | }
80 |
81 | public static byte[] hexStringToByteArray(String s) {
82 | int len = s.length();
83 | byte[] data = new byte[len / 2];
84 | for (int i = 0; i < len; i += 2) {
85 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
86 | + Character.digit(s.charAt(i+1), 16));
87 | }
88 | return data;
89 | }
90 |
91 | @SuppressWarnings("deprecation")
92 | private void request(String content) throws UnknownHostException, IOException{
93 |
94 | Log.i("RTSP", content);
95 |
96 | writer.write(content);
97 | writer.flush();
98 |
99 |
100 | int contentLength = 0;
101 | String content_base = "";
102 |
103 | String line = reader.readLine();
104 | while(line != null){
105 |
106 | if(line.startsWith("Session")){
107 | int start = line.indexOf(':');
108 | int end = line.indexOf(';');
109 | if(end != -1)
110 | session = line.substring(start + 1, end);
111 | else
112 | session = line.substring(start + 1);
113 |
114 | session = session.trim();
115 | }
116 |
117 | if(line.startsWith("Content-Length") || line.startsWith("Content-length")){
118 | int start = line.indexOf(':');
119 | contentLength = Integer.parseInt(line.substring(start + 1).trim());
120 | }
121 |
122 | if(line.startsWith("Content-Base")){
123 | int start = line.indexOf(':');
124 | content_base = line.substring(start + 1);
125 | }
126 |
127 |
128 | Log.i("RTSP", line);
129 | if(line.length() == 0)
130 | break;
131 |
132 | line = reader.readLine();
133 | }
134 |
135 | if(contentLength > 0){
136 | byte[] buf = new byte[contentLength];
137 | int offset = 0;
138 | while(offset < contentLength){
139 | offset += reader.read(buf, offset, contentLength - offset);
140 | }
141 |
142 | String body = new String(buf);
143 | Log.i("RTSP", body);
144 |
145 |
146 | if(body.indexOf("m=video") > 0){
147 | int start = body.indexOf(":", body.indexOf("a=control", body.indexOf("m=video")));
148 | videoTrackUrl = body.substring(start + 1, body.indexOf("\n", start)).trim();
149 | if(!videoTrackUrl.startsWith("rtsp:"))
150 | videoTrackUrl = content_base + videoTrackUrl;
151 |
152 |
153 |
154 | start = body.indexOf("=", body.indexOf("sprop-parameter-sets"));
155 | int end = body.indexOf(",", start);
156 |
157 | sps_pps = new ArrayList();
158 |
159 | if(start > 0 && end > 0){
160 | sps_pps.add(Base64.decode(body.substring(start + 1, end), 0));
161 | int xend = body.indexOf(";", end);
162 | if( xend < 0)
163 | xend = body.indexOf("\n", end);
164 |
165 | sps_pps.add(Base64.decode(body.substring(end + 1, xend), 0));
166 |
167 | }
168 | }
169 |
170 | if(body.indexOf("m=audio") > 0){
171 | int start = body.indexOf(":", body.indexOf("a=control", body.indexOf("m=audio")));
172 | audioTrackUrl = body.substring(start + 1, body.indexOf("\n", start)).trim();
173 | if(!videoTrackUrl.startsWith("rtsp:"))
174 | audioTrackUrl = content_base + audioTrackUrl;
175 |
176 | start = body.indexOf("/", body.indexOf("rtpmap", body.indexOf("m=audio")));
177 |
178 | int end = body.indexOf("/", start + 1);
179 | if( end - start < 10){
180 | sampleRate = Integer.parseInt(body.substring(start + 1, end));
181 | channel = Integer.parseInt(body.substring(end + 1, body.indexOf("\r\n", end + 1)));
182 | }else{
183 | end = body.indexOf("\r\n", start + 1);
184 | sampleRate = Integer.parseInt(body.substring(start + 1, end));
185 | channel = 1;
186 | }
187 |
188 | start = body.indexOf("=", body.indexOf("config", body.indexOf("m=audio")));
189 | String config = body.substring(start + 1, body.indexOf(";", start));
190 |
191 | audioConfig = hexStringToByteArray(config);
192 |
193 | }
194 |
195 |
196 | }
197 |
198 | }
199 |
200 | private String getTrackUrl(){
201 | return playVideo ? videoTrackUrl : audioTrackUrl;
202 | }
203 |
204 | private void rtspPlay() throws UnknownHostException, IOException, InterruptedException {
205 |
206 | Uri uri = Uri.parse(rtspUrl);
207 |
208 | s = new Socket(uri.getHost(), uri.getPort());
209 | reader = new DataInputStream(s.getInputStream());
210 | writer = new PrintWriter(s.getOutputStream());
211 |
212 | int CSeq = 1;
213 |
214 | String content;
215 |
216 | content= "DESCRIBE " + rtspUrl + " RTSP/1.0\n"
217 | + "CSeq: " + CSeq++ + "\n"
218 | + "User-Agent: TestRtsp\n"
219 | + "\n";
220 |
221 | request(content);
222 |
223 | content = "SETUP " + getTrackUrl()+ " RTSP/1.0\n"
224 | + "CSeq: " + CSeq++ + "\n"
225 | + "User-Agent: TestRtsp\n";
226 | if(overTcp){
227 | content += "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\n";
228 | }else{
229 | content += "Transport: RTP/AVP;unicast;client_port=" + receivePort+ "-" + (receivePort + 1) + "\n";
230 | }
231 | content += "\n";
232 |
233 | request(content);
234 |
235 | content = "PLAY " + getTrackUrl() + " RTSP/1.0\n"
236 | + "CSeq: " + CSeq++ + "\n"
237 | + "User-Agent: TestRtsp\n"
238 | + "Session: " + session + "\n"
239 | + "\n";
240 |
241 | request(content);
242 |
243 | if(listener != null)
244 | listener.onReady(this);
245 |
246 | while(true){
247 |
248 | if(thread.isInterrupted())
249 | break;
250 |
251 | if(overTcp){
252 |
253 | int magicNum = reader.readByte();
254 | int channelNum = reader.readByte();
255 |
256 | if(magicNum != 36)
257 | throw new RuntimeException();
258 |
259 | int dataSize = reader.readShort();
260 | byte[] data = new byte[dataSize];
261 | reader.readFully(data);
262 |
263 | if(channelNum == 0){
264 | if(listener != null)
265 | listener.onRtpPacket(data, dataSize);
266 | }else{
267 | //Log.i("RTSP", new String(data));
268 | }
269 |
270 |
271 | }else{
272 | Thread.sleep(30*1000);
273 |
274 | content = "GET_PARAMETER " + getTrackUrl() + " RTSP/1.0\n"
275 | + "CSeq: " + CSeq++ + "\n"
276 | + "User-Agent: TestRtsp\n"
277 | + "Session: " + session + "\n"
278 | + "\n";
279 |
280 | request(content);
281 | }
282 | }
283 |
284 | //s.close();
285 |
286 | }
287 |
288 |
289 | }
290 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/RtspClientListener.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | import com.optman.rtsp.RtspClient;
4 |
5 | public interface RtspClientListener {
6 | public void onReady(RtspClient client);
7 | public void onRtpPacket(byte[] data, int dataSize);
8 | }
9 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/TcpChannel.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | import java.io.IOException;
4 |
5 | import com.optman.rtsp.TcpSender;
6 | import com.optman.rtp.sender.RtpSocket;
7 |
8 | public class TcpChannel implements RtpSocket{
9 | int channelNum;
10 | TcpSender sender;
11 | public TcpChannel(TcpSender sender, int channelNum){
12 | this.sender = sender;
13 | this.channelNum = channelNum;
14 | }
15 | @Override
16 | public void sendPacket(byte[] data, int offset, int size) throws IOException{
17 | sender.sendData(channelNum, data, offset, size);
18 | }
19 |
20 | public void close(){
21 |
22 | }
23 | }
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/TcpDataHandler.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | public interface TcpDataHandler{
4 | public void onData(int channel, byte[] data, int dataSize);
5 | };
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/TcpReceiver.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.IOException;
5 | import java.net.Socket;
6 |
7 | import com.optman.rtsp.TcpDataHandler;
8 |
9 | public class TcpReceiver{
10 | private Socket socket;
11 |
12 | public TcpReceiver(Socket socket){
13 | this.socket = socket;
14 | }
15 |
16 | public void run(TcpDataHandler handler) throws IOException{
17 |
18 | DataInputStream s = new DataInputStream(socket.getInputStream());
19 |
20 | while(true){
21 |
22 | if(36 != s.read())
23 | throw new IOException("invalid magic number");
24 |
25 | int channel = s.read();
26 | int dataSize = s.readShort();
27 | byte[] data = new byte[dataSize];
28 | s.readFully(data);
29 |
30 | handler.onData(channel, data, dataSize);
31 |
32 | }
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/libRtsp/src/com/optman/rtsp/TcpSender.java:
--------------------------------------------------------------------------------
1 | package com.optman.rtsp;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.net.Socket;
7 |
8 | import com.optman.rtp.sender.RtpSocket;
9 |
10 | public class TcpSender{
11 |
12 | private Socket socket;
13 |
14 | public TcpSender(Socket socket){
15 | this.socket = socket;
16 | }
17 |
18 | public synchronized void sendData(int channel, byte[] data, int offset, int size) throws IOException{
19 | DataOutputStream s = new DataOutputStream(socket.getOutputStream());
20 |
21 | s.write(36);
22 | s.write(channel);
23 | s.writeShort(size);
24 | s.write(data, offset, size);
25 | }
26 |
27 | public void close(){
28 | try {
29 | socket.close();
30 | } catch (IOException e) {
31 | e.printStackTrace();
32 | }
33 | }
34 | }
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------