├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── test
│ │ └── testh264player
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── test
│ │ │ └── testh264player
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── test
│ └── testh264player
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── video_play
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── test
│ └── video_play
│ └── ExampleInstrumentedTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── test
│ │ └── video_play
│ │ ├── ScreenRecordApi.java
│ │ ├── ScreenRecordController.java
│ │ ├── control
│ │ └── VideoPlayController.java
│ │ ├── decode
│ │ ├── DecodeThread.java
│ │ └── play
│ │ │ ├── AudioPlay.java
│ │ │ └── VideoPlay.java
│ │ ├── entity
│ │ ├── Frame.java
│ │ ├── ReceiveData.java
│ │ └── ReceiveHeader.java
│ │ ├── mediacodec
│ │ ├── AudioMediaCodec.java
│ │ └── VideoMediaCodec.java
│ │ ├── server
│ │ └── tcp
│ │ │ ├── AcceptStreamDataThread.java
│ │ │ ├── EncodeV1.java
│ │ │ ├── NormalPlayQueue.java
│ │ │ ├── TcpServer.java
│ │ │ └── interf
│ │ │ ├── OnAcceptBuffListener.java
│ │ │ └── OnServerStateChangeListener.java
│ │ └── utils
│ │ ├── AboutNetUtils.java
│ │ ├── AnalyticDataUtils.java
│ │ ├── ByteUtil.java
│ │ ├── DecodeUtils.java
│ │ ├── DensityUtil.java
│ │ └── WeakHandler.java
└── res
│ └── values
│ └── strings.xml
└── test
└── java
└── com
└── test
└── video_play
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | #Android.gitignore
2 | # Built application files
3 | *.apk
4 | *.ap_
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # JetBrains Rider
21 | .idea/
22 | *.sln.iml
23 |
24 | # Log Files
25 | *.log
26 |
27 | # Android Studio Navigation editor temp files
28 | .navigation/
29 |
30 | # Android Studio captures folder
31 | captures/
32 |
33 | # IntelliJ
34 | *.iml
35 | .idea/workspace.xml
36 | .idea/tasks.xml
37 | .idea/gradle.xml
38 | .idea/dictionaries
39 | .idea/libraries
40 |
41 | # Local configuration file (sdk path, etc)
42 | local.properties
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TestH264Player
2 | intial project
3 |
4 | http://blog.csdn.net/baidu_33546245/article/details/78670220
5 |
6 | https://github.com/textview10/TestH264Player 直播投屏数据服务,播放端
7 |
8 | https://github.com/textview10/TestH264Sender 直播投屏数据采集,发送端
9 |
10 | A demo to send h264 metadata from a Android device to another,
11 | (this is server and player:accpet data and analysis h264 to play);
12 |
13 | 2018 08/23 更新: 将播放端代码初步组件化,移到moudle中去,优化了部分代码.需要跟08/23的H264 Sender一块使用
14 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.test.testh264player"
7 | minSdkVersion 16
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(include: ['*.jar'], dir: 'libs')
23 | implementation 'com.android.support:appcompat-v7:26.1.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
28 | implementation project(':video_play')
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/test/testh264player/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.test.testh264player;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.zonekey.testh264player", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/testh264player/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.test.testh264player;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.SurfaceHolder;
7 | import android.view.SurfaceView;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 | import android.widget.RelativeLayout;
11 | import android.widget.TextView;
12 |
13 | import com.test.video_play.ScreenRecordController;
14 | import com.test.video_play.control.VideoPlayController;
15 | import com.test.video_play.server.tcp.interf.OnServerStateChangeListener;
16 |
17 | public class MainActivity extends AppCompatActivity {
18 | private static final String TAG = "MainActivity";
19 | private SurfaceView mSurface = null;
20 | private SurfaceHolder mSurfaceHolder;
21 |
22 | private VideoPlayController mController;
23 | private TextView tv_speednet;
24 | private RelativeLayout rl_detail;
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_main);
30 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
31 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
32 | setContentView(R.layout.activity_main);
33 | mSurface = findViewById(R.id.surfaceview);
34 | tv_speednet = findViewById(R.id.tv_main_speednet);
35 | rl_detail = findViewById(R.id.rl_content_detail);
36 | mController = new VideoPlayController();
37 |
38 | ScreenRecordController.getInstance()
39 | .init(getApplication())
40 | .setPort(11111) //设置端口号
41 | .startServer() //初始化,并开启server
42 | .setVideoPlayController(mController) //设置VideoController
43 | .setOnAcceptTcpStateChangeListener(mStateChangeListener); //设置回调
44 |
45 | mSurfaceHolder = mSurface.getHolder();
46 | mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
47 | @Override
48 | public void surfaceCreated(SurfaceHolder holder) {
49 | mController.surfaceCreate(holder);
50 | }
51 |
52 | @Override
53 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
54 |
55 | }
56 |
57 | @Override
58 | public void surfaceDestroyed(SurfaceHolder holder) {
59 | Log.e(TAG, "surface destory");
60 | mController.surfaceDestrory();
61 | }
62 | });
63 | }
64 |
65 | //客户端Tcp连接状态的回调...
66 | OnServerStateChangeListener mStateChangeListener = new OnServerStateChangeListener() {
67 |
68 | @Override
69 | public void acceptH264TcpConnect() {
70 | Log.e(TAG, "accept a tcp connect...");
71 | rl_detail.setVisibility(View.GONE);
72 | }
73 |
74 | @Override
75 | public void acceptH264TcpDisConnect(Exception e) {
76 | Log.e(TAG, "acceptTcpConnect exception = " + e.toString());
77 | rl_detail.setVisibility(View.VISIBLE);
78 | }
79 |
80 | @Override
81 | public void exception() {
82 |
83 | }
84 |
85 | @Override
86 | public void acceptH264TcpNetSpeed(String netSpeed) {
87 | super.acceptH264TcpNetSpeed(netSpeed);
88 | tv_speednet.setText(netSpeed);
89 | }
90 | };
91 |
92 |
93 | @Override
94 | protected void onDestroy() {
95 | super.onDestroy();
96 | if (mController != null) mController.stop();
97 | ScreenRecordController.getInstance().stopServer();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
19 |
20 |
28 |
29 |
30 |
33 |
34 |
38 |
39 |
44 |
45 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TestH264Player
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/test/testh264player/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.test.testh264player;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.1'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/textview10/TestH264Player/32b27d9494b47b929ebfbb3314dbc4e062547377/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Nov 29 09:35:18 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':video_play'
2 |
--------------------------------------------------------------------------------
/video_play/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/video_play/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 26
5 |
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'com.android.support:appcompat-v7:26.1.0'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
34 | }
35 |
--------------------------------------------------------------------------------
/video_play/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/video_play/src/androidTest/java/com/test/video_play/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.zonekey.video_play.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/video_play/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/ScreenRecordApi.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play;
2 |
3 | /**
4 | * Created by wt on 2018/6/11.
5 | * 设备常用配置
6 | */
7 | public class ScreenRecordApi {
8 | public static final byte encodeVersion1 = 0x00; //版本号1
9 |
10 | public class RECORD { //录屏指令
11 | public static final int MAIN_CMD = 1; //录屏主指令
12 | public static final int SEND_BUFF = 11;//发送声音的BUFF
13 | }
14 |
15 | public class SERVER {//服务端与客户端交互指令
16 | public static final int MAIN_CMD = 0xA0; //投屏回传主指令
17 | public static final int INITIAL_SUCCESS = 0x01;//服务端初始化成功
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/ScreenRecordController.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Handler;
6 |
7 | import com.test.video_play.control.VideoPlayController;
8 | import com.test.video_play.server.tcp.TcpServer;
9 | import com.test.video_play.server.tcp.interf.OnAcceptBuffListener;
10 | import com.test.video_play.server.tcp.interf.OnServerStateChangeListener;
11 |
12 | /**
13 | * Created by xu.wang
14 | * Date on 2018/08/22 14:47:47.
15 | *
16 | * @Desc 录屏控制类
17 | */
18 |
19 | public class ScreenRecordController {
20 | public static Handler mHandler;
21 | public static Context mContext;
22 | public static TcpServer tcpServer;
23 | public static int port = 11111;
24 | public static OnServerStateChangeListener mServerStateChangeListener;
25 | private OnAcceptBuffListener acceptBuffListener;
26 | private VideoPlayController mVideoPlayController;
27 |
28 |
29 | private static ScreenRecordController mController;
30 |
31 | private ScreenRecordController() {
32 | }
33 |
34 | public static ScreenRecordController getInstance() {
35 | synchronized (ScreenRecordController.class) {
36 | if (mController == null) {
37 | mController = new ScreenRecordController();
38 | }
39 | }
40 | return mController;
41 | }
42 |
43 |
44 | public ScreenRecordController init(Application application) {
45 | mHandler = new Handler(application.getMainLooper());
46 | mContext = application;
47 | return mController;
48 | }
49 |
50 | //开启server
51 | public ScreenRecordController startServer() {
52 | if (tcpServer == null) {
53 | tcpServer = new TcpServer();
54 | }
55 | tcpServer.startServer();
56 | if (acceptBuffListener != null) tcpServer.setOnAccepttBuffListener(acceptBuffListener);
57 | return mController;
58 | }
59 |
60 | public ScreenRecordController setPort(int port) {
61 | this.port = port;
62 | return mController;
63 | }
64 |
65 | public ScreenRecordController stopServer() {
66 | mVideoPlayController = null;
67 | acceptBuffListener = null;
68 | if (tcpServer != null) tcpServer.stopServer();
69 | tcpServer = null;
70 | return mController;
71 | }
72 |
73 | public void setOnAcceptTcpStateChangeListener(OnServerStateChangeListener listener) {
74 | this.mServerStateChangeListener = listener;
75 | }
76 |
77 | public ScreenRecordController setVideoPlayController(VideoPlayController videoPlayController) {
78 | mVideoPlayController = videoPlayController;
79 | acceptBuffListener = mVideoPlayController.getAcceptBuffListener();
80 | if (tcpServer != null) tcpServer.setOnAccepttBuffListener(acceptBuffListener);
81 | return mController;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/control/VideoPlayController.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.control;
2 |
3 | import android.util.Log;
4 | import android.view.SurfaceHolder;
5 |
6 | import com.test.video_play.ScreenRecordController;
7 | import com.test.video_play.decode.DecodeThread;
8 | import com.test.video_play.entity.Frame;
9 | import com.test.video_play.mediacodec.VideoMediaCodec;
10 | import com.test.video_play.server.tcp.NormalPlayQueue;
11 | import com.test.video_play.server.tcp.interf.OnAcceptBuffListener;
12 |
13 | /**
14 | * Created by xu.wang
15 | * Date on 2018/08/22 14:47:47.
16 | *
17 | * @Desc Surface绑定控制类
18 | */
19 |
20 | public class VideoPlayController {
21 | private static final String TAG = "VideoPlayController";
22 |
23 | private VideoMediaCodec videoMediaCodec;
24 | private DecodeThread mDecodeThread;
25 | private NormalPlayQueue mPlayQueue;
26 |
27 | public VideoPlayController() {
28 | mPlayQueue = new NormalPlayQueue();
29 |
30 | }
31 |
32 | public void surfaceCreate(SurfaceHolder holder) {
33 | //初始化解码器
34 | Log.i(TAG, "create surface, and initial play queue");
35 | videoMediaCodec = new VideoMediaCodec(holder);
36 | //开启解码线程
37 | mDecodeThread = new DecodeThread(videoMediaCodec.getCodec(), mPlayQueue);
38 | videoMediaCodec.start();
39 | mDecodeThread.start();
40 | }
41 |
42 |
43 | public void surfaceDestrory() {
44 | mPlayQueue.stop();
45 | mDecodeThread.shutdown();
46 | }
47 |
48 | public void stop() {
49 | mPlayQueue.stop();
50 | mPlayQueue = null;
51 | mDecodeThread.shutdown();
52 | }
53 |
54 | public OnAcceptBuffListener getAcceptBuffListener() {
55 | return mAcceptBuffListener;
56 | }
57 |
58 | private OnAcceptBuffListener mAcceptBuffListener = new OnAcceptBuffListener() {
59 | @Override
60 | public void acceptBuff(Frame frame) {
61 | if (mPlayQueue != null) mPlayQueue.putByte(frame);
62 | }
63 | };
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/decode/DecodeThread.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.decode;
2 |
3 | import android.media.MediaCodec;
4 | import android.os.Build;
5 | import android.os.SystemClock;
6 | import android.support.annotation.RequiresApi;
7 | import android.util.Log;
8 |
9 | import com.test.video_play.decode.play.AudioPlay;
10 | import com.test.video_play.decode.play.VideoPlay;
11 | import com.test.video_play.entity.Frame;
12 | import com.test.video_play.server.tcp.NormalPlayQueue;
13 |
14 | import java.nio.ByteBuffer;
15 |
16 | /**
17 | * Created by xu.wang
18 | * Date on 2017/11/29 14:04:57.
19 | *
20 | * @Desc 解码线程, 解码判断类型, 分开解析
21 | */
22 |
23 | public class DecodeThread extends Thread {
24 | private NormalPlayQueue playQueue;
25 | private String TAG = "DecodeThread";
26 | private boolean isPlaying = true;
27 | private final AudioPlay audioPlay;
28 | private final VideoPlay videoPlay;
29 |
30 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
31 | public DecodeThread(MediaCodec mediaCodec, NormalPlayQueue playQueue) {
32 | this.playQueue = playQueue;
33 | audioPlay = new AudioPlay();
34 | videoPlay = new VideoPlay(mediaCodec);
35 | }
36 |
37 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
38 | @Override
39 | public void run() {
40 | while (isPlaying) {
41 | Frame frame = playQueue.takeByte();
42 | if (frame == null) {
43 | SystemClock.sleep(1);
44 | continue;
45 | }
46 | switch (frame.getType()) {
47 | case Frame.KEY_FRAME:
48 | case Frame.NORMAL_FRAME:
49 | try {
50 | videoPlay.decodeH264(frame.getBytes());
51 | Log.i(TAG, "receive a frame count");
52 | } catch (Exception e) {
53 | Log.e(TAG, "frame Exception" + e.toString());
54 | }
55 | break;
56 | case Frame.SPSPPS:
57 | try {
58 | ByteBuffer bb = ByteBuffer.allocate(frame.getPps().length + frame.getSps().length);
59 | bb.put(frame.getSps());
60 | bb.put(frame.getPps());
61 | Log.e(TAG, "receive Sps pps");
62 | videoPlay.decodeH264(bb.array());
63 | } catch (Exception e) {
64 | Log.e(TAG, "sps pps Exception" + e.toString());
65 | }
66 | break;
67 | case Frame.AUDIO_FRAME:
68 | try {
69 | audioPlay.playAudio(frame.getBytes(), 0, frame.getBytes().length);
70 | } catch (Exception e) {
71 | Log.e(TAG, "audio Exception" + e.toString());
72 | }
73 | break;
74 | }
75 | }
76 | }
77 |
78 | public void shutdown() {
79 | Log.i(TAG, "DecodeThread shutdown");
80 | isPlaying = false;
81 | this.interrupt();
82 | if (audioPlay != null) audioPlay.release();
83 | if (videoPlay != null) videoPlay.release();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/decode/play/AudioPlay.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.decode.play;
2 |
3 | import android.media.AudioTrack;
4 | import android.media.MediaCodec;
5 | import android.os.Build;
6 | import android.support.annotation.RequiresApi;
7 | import android.util.Log;
8 |
9 | import com.test.video_play.mediacodec.AudioMediaCodec;
10 |
11 | import java.nio.ByteBuffer;
12 |
13 | /**
14 | * Created by xu.wang
15 | * Date on 2018/08/22 14:47:47.
16 | *
17 | * @Desc 使用MediaCodec解码AAC并播放.
18 | */
19 |
20 | public class AudioPlay {
21 | private static final String TAG = "AudioPlay";
22 | private MediaCodec mAudioMediaCodec;
23 | private AudioTrack mAudioTrack;
24 | //用来记录解码失败的帧数
25 | private int count = 0;
26 |
27 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
28 | public AudioPlay() {
29 | this.mAudioMediaCodec = AudioMediaCodec.getAudioMediaCodec();
30 | this.mAudioTrack = AudioMediaCodec.getAudioTrack();
31 | this.mAudioMediaCodec.start();
32 | this.mAudioTrack.play();
33 | }
34 |
35 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
36 | public void playAudio(byte[] buf, int offset, int length) {
37 | //输入ByteBuffer
38 | ByteBuffer[] codecInputBuffers = mAudioMediaCodec.getInputBuffers();
39 | //输出ByteBuffer
40 | ByteBuffer[] codecOutputBuffers = mAudioMediaCodec.getOutputBuffers();
41 | //等待时间,0->不等待,-1->一直等待
42 | long kTimeOutUs = 0;
43 | try {
44 | //返回一个包含有效数据的input buffer的index,-1->不存在
45 | int inputBufIndex = mAudioMediaCodec.dequeueInputBuffer(kTimeOutUs);
46 | if (inputBufIndex >= 0) {
47 | //获取当前的ByteBuffer
48 | ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
49 | //清空ByteBuffer
50 | dstBuf.clear();
51 | //填充数据
52 | dstBuf.put(buf, offset, length);
53 | //将指定index的input buffer提交给解码器
54 | mAudioMediaCodec.queueInputBuffer(inputBufIndex, 0, length, 0, 0);
55 | }
56 | //编解码器缓冲区
57 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
58 | //返回一个output buffer的index,-1->不存在
59 | int outputBufferIndex = mAudioMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
60 |
61 | if (outputBufferIndex < 0) {
62 | //记录解码失败的次数
63 | count++;
64 | }
65 |
66 | ByteBuffer outputBuffer;
67 | while (outputBufferIndex >= 0) {
68 | switch (outputBufferIndex) {
69 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
70 | Log.e(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
71 | break;
72 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
73 | Log.e(TAG, "INFO_OUTPUT_FORMAT_CHANGE");
74 | break;
75 | case MediaCodec.INFO_TRY_AGAIN_LATER:
76 | Log.e(TAG, "INFO_TRY_AGAIN_LATER");
77 | break;
78 | }
79 | //---------------------------------------------------------------
80 | //获取解码后的ByteBuffer
81 | outputBuffer = codecOutputBuffers[outputBufferIndex];
82 | //用来保存解码后的数据
83 | byte[] outData = new byte[info.size];
84 | outputBuffer.get(outData);
85 | //清空缓存
86 | outputBuffer.clear();
87 | //播放解码后的数据
88 | mAudioTrack.write(outData, 0, info.size);
89 | // Log.e("DecodeThread", "buff length = " + info.size);
90 | //释放已经解码的buffer
91 | mAudioMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
92 | //解码未解完的数据
93 | outputBufferIndex = mAudioMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
94 | //--------------------------------------------------------
95 | }
96 | } catch (Exception e) {
97 | Log.e(TAG, e.toString());
98 | e.printStackTrace();
99 | }
100 | }
101 |
102 | //返回解码失败的次数
103 | public int getCount() {
104 | return count;
105 | }
106 |
107 |
108 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
109 | public void release() {
110 | if (mAudioMediaCodec != null) {
111 | mAudioMediaCodec.stop();
112 | mAudioMediaCodec.release();
113 | mAudioMediaCodec = null;
114 | }
115 | if (mAudioTrack != null) {
116 | mAudioTrack.stop();
117 | mAudioTrack.release();
118 | mAudioTrack = null;
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/decode/play/VideoPlay.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.decode.play;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaFormat;
5 | import android.os.Build;
6 | import android.os.SystemClock;
7 | import android.support.annotation.RequiresApi;
8 | import android.util.Log;
9 |
10 | import java.io.ByteArrayInputStream;
11 | import java.io.ByteArrayOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.nio.ByteBuffer;
15 |
16 | /**
17 | * Created by xu.wang
18 | * Date on 2018/08/22 14:47:47.
19 | *
20 | * @Desc 使用MdeieCodec解析H264, 并显示到Surface
21 | */
22 |
23 | public class VideoPlay {
24 | private static final String TAG = "VideoPlay";
25 | private MediaCodec mVideoMediaCodec;
26 | // private OnFrameChangeListener mAcceptBuffListener;
27 |
28 | public VideoPlay(MediaCodec mediaCodec) {
29 | this.mVideoMediaCodec = mediaCodec;
30 | }
31 |
32 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
33 | public void decodeH264(byte[] buff) {
34 | boolean mStopFlag = false;
35 | //存放目标文件的数据
36 | ByteBuffer[] inputBuffers = mVideoMediaCodec.getInputBuffers();
37 | //解码后的数据,包含每一个buffer的元数据信息,例如偏差,在相关解码器中有效的数据大小
38 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
39 | long timeoutUs = 10000; //微秒
40 | byte[] marker0 = new byte[]{0, 0, 0, 1};
41 | byte[] dummyFrame = new byte[]{0x00, 0x00, 0x01, 0x20};
42 | byte[] streamBuffer = null;
43 | streamBuffer = buff;
44 | int bytes_cnt = 0;
45 | while (mStopFlag == false) {
46 | bytes_cnt = streamBuffer.length;
47 | if (bytes_cnt == 0) {
48 | streamBuffer = dummyFrame;
49 | }
50 |
51 | int startIndex = 0;
52 | int remaining = bytes_cnt;
53 | while (true) {
54 | if (remaining == 0 || startIndex >= remaining) {
55 | break;
56 | }
57 | int nextFrameStart = KMPMatch(marker0, streamBuffer, startIndex + 2, remaining);
58 | if (nextFrameStart == -1) {
59 | nextFrameStart = remaining;
60 | } else {
61 | }
62 |
63 | int inIndex = mVideoMediaCodec.dequeueInputBuffer(timeoutUs);
64 | if (inIndex >= 0) {
65 | ByteBuffer byteBuffer = inputBuffers[inIndex];
66 | byteBuffer.clear();
67 | byteBuffer.put(streamBuffer, startIndex, nextFrameStart - startIndex);
68 | //在给指定Index的inputbuffer[]填充数据后,调用这个函数把数据传给解码器
69 | mVideoMediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);
70 | startIndex = nextFrameStart;
71 | } else {
72 | continue;
73 | }
74 |
75 | int outIndex = mVideoMediaCodec.dequeueOutputBuffer(info, timeoutUs);
76 | // switch (outIndex) {
77 | // case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
78 | // MediaFormat outputFormat = mVideoMediaCodec.getOutputFormat();
79 | // Integer width = outputFormat.getInteger(MediaFormat.KEY_WIDTH);
80 | // Integer height = outputFormat.getInteger(MediaFormat.KEY_HEIGHT);
81 | // Log.d(TAG, "INFO_OUTPUT_FORMAT_CHANGED == width = " + width + "height = " + height);
82 | //// if (mAcceptBuffListener != null) mAcceptBuffListener.onFrameSize(width, height);
83 | // break;
84 | // }
85 |
86 | if (outIndex >= 0) {
87 | //帧控制是不在这种情况下工作,因为没有PTS H264是可用的
88 | // while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
89 | // try {
90 | // Thread.sleep(100);
91 | // Log.e(TAG, "sleep 100");
92 | // } catch (InterruptedException e) {
93 | // e.printStackTrace();
94 | // }
95 | // }
96 | SystemClock.sleep(1);
97 | // Log.e(TAG, "decode a frame time =" + (System.currentTimeMillis() - startMs));
98 | boolean doRender = (info.size != 0);
99 | //对outputbuffer的处理完后,调用这个函数把buffer重新返回给codec类。
100 | mVideoMediaCodec.releaseOutputBuffer(outIndex, doRender);
101 |
102 | } else {
103 | }
104 | }
105 | mStopFlag = true;
106 | }
107 | }
108 |
109 | // public void decodeH264new(Byte[] buff, boolean isKeyFrame) {
110 | // Log.e(TAG, "onFrame start");
111 | // // Get input buffer index
112 | // ByteBuffer[] inputBuffers = mVideoMediaCodec.getInputBuffers();
113 | // int inputBufferIndex = mVideoMediaCodec.dequeueInputBuffer(100);
114 | //
115 | // Log.e("Media", "onFrame index:" + inputBufferIndex);
116 | // if (inputBufferIndex >= 0) {
117 | // ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
118 | // inputBuffer.clear();
119 | // inputBuffer.put(buff, 0, buff.length);
120 | // mVideoMediaCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount
121 | // 14 * TIME_INTERNAL, 0);
122 | // mCount++;
123 | // } else {
124 | // return false;
125 | // }
126 | //
127 | // // Get output buffer index
128 | // MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
129 | // int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 100);
130 | // while (outputBufferIndex >= 0) {
131 | // mCodec.releaseOutputBuffer(outputBufferIndex, true);
132 | // outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0);
133 | // }
134 | // Log.e("Media", "onFrame end");
135 | // return true;
136 | // }
137 |
138 | private byte[] getBytes(InputStream is) throws IOException {
139 | int len;
140 | int size = 1024;
141 | byte[] buf;
142 | if (is instanceof ByteArrayInputStream) {
143 | size = is.available();
144 | buf = new byte[size];
145 | len = is.read(buf, 0, size);
146 | } else {
147 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
148 | buf = new byte[size];
149 | while ((len = is.read(buf, 0, size)) != -1)
150 | bos.write(buf, 0, len);
151 | buf = bos.toByteArray();
152 | }
153 | return buf;
154 | }
155 |
156 | private int KMPMatch(byte[] pattern, byte[] bytes, int start, int remain) {
157 | try {
158 | Thread.sleep(1);
159 | } catch (InterruptedException e) {
160 | e.printStackTrace();
161 | }
162 | int[] lsp = computeLspTable(pattern);
163 |
164 | int j = 0; // Number of chars matched in pattern
165 | for (int i = start; i < remain; i++) {
166 | while (j > 0 && bytes[i] != pattern[j]) {
167 | // Fall back in the pattern
168 | j = lsp[j - 1]; // Strictly decreasing
169 | }
170 | if (bytes[i] == pattern[j]) {
171 | // Next char matched, increment position
172 | j++;
173 | if (j == pattern.length)
174 | return i - (j - 1);
175 | }
176 | }
177 |
178 | return -1; // Not found
179 | }
180 |
181 | private int[] computeLspTable(byte[] pattern) {
182 | int[] lsp = new int[pattern.length];
183 | lsp[0] = 0; // Base case
184 | for (int i = 1; i < pattern.length; i++) {
185 | // Start by assuming we're extending the previous LSP
186 | int j = lsp[i - 1];
187 | while (j > 0 && pattern[i] != pattern[j])
188 | j = lsp[j - 1];
189 | if (pattern[i] == pattern[j])
190 | j++;
191 | lsp[i] = j;
192 | }
193 | return lsp;
194 | }
195 |
196 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
197 | public void release() {
198 | if (mVideoMediaCodec != null) {
199 | mVideoMediaCodec.stop();
200 | mVideoMediaCodec.release();
201 | mVideoMediaCodec = null;
202 | }
203 | }
204 |
205 | // public void setOnFrameChangeListener(OnFrameChangeListener listener) {
206 | // this.mAcceptBuffListener = listener;
207 | // }
208 |
209 | public interface OnFrameChangeListener {
210 | void onFrameSize(int width, int height);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/entity/Frame.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.entity;
2 |
3 | /**
4 | * Created by xu.wang
5 | * Date on 2018/08/22 14:47:47.
6 | *
7 | * @Desc
8 | */
9 |
10 | public class Frame {
11 | public static final int SPSPPS = 2;
12 | public static final int KEY_FRAME = 4;
13 | public static final int NORMAL_FRAME = 5;
14 | public static final int AUDIO_FRAME = 6;
15 | private byte[] bytes;
16 | private int type;
17 | private byte[] sps;
18 | private byte[] pps;
19 |
20 | public byte[] getBytes() {
21 | return bytes;
22 | }
23 |
24 | public void setBytes(byte[] bytes) {
25 | this.bytes = bytes;
26 | }
27 |
28 | public int getType() {
29 | return type;
30 | }
31 |
32 | public void setType(int type) {
33 | this.type = type;
34 | }
35 |
36 | public byte[] getSps() {
37 | return sps;
38 | }
39 |
40 | public void setSps(byte[] sps) {
41 | this.sps = sps;
42 | }
43 |
44 | public byte[] getPps() {
45 | return pps;
46 | }
47 |
48 | public void setPps(byte[] pps) {
49 | this.pps = pps;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/entity/ReceiveData.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.entity;
2 |
3 | /**
4 | * Created by xu.wang
5 | * Date on 2018/08/22 14:47:47.
6 | *
7 | * @Desc 返回一组解析后的数据
8 | */
9 |
10 | public class ReceiveData {
11 | private ReceiveHeader header;
12 | private byte[] buff;
13 |
14 | public ReceiveHeader getHeader() {
15 | return header;
16 | }
17 |
18 | public void setHeader(ReceiveHeader header) {
19 | this.header = header;
20 | }
21 |
22 | public byte[] getBuff() {
23 | return buff;
24 | }
25 |
26 | public void setBuff(byte[] buff) {
27 | this.buff = buff;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/entity/ReceiveHeader.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.entity;
2 |
3 | /**
4 | * Created by xu.wang
5 | * Date on 2018/08/22 14:47:47.
6 | *
7 | * @Desc 接收并解析出数据的头信息
8 | */
9 |
10 | public class ReceiveHeader {
11 | private int mainCmd;
12 | private int subCmd;
13 | private byte encodeVersion;
14 | private int stringBodylength;
15 | private int buffSize;
16 |
17 | public ReceiveHeader(int mainCmd, int subCmd, byte encodeVersion, int stringBodylength, int buffSize) {
18 | this.mainCmd = mainCmd;
19 | this.subCmd = subCmd;
20 | this.encodeVersion = encodeVersion;
21 | this.stringBodylength = stringBodylength;
22 | this.buffSize = buffSize;
23 | }
24 |
25 | public int getMainCmd() {
26 | return mainCmd;
27 | }
28 |
29 | public void setMainCmd(int mainCmd) {
30 | this.mainCmd = mainCmd;
31 | }
32 |
33 | public int getSubCmd() {
34 | return subCmd;
35 | }
36 |
37 | public void setSubCmd(int subCmd) {
38 | this.subCmd = subCmd;
39 | }
40 |
41 | public byte getEncodeVersion() {
42 | return encodeVersion;
43 | }
44 |
45 | public void setEncodeVersion(byte encodeVersion) {
46 | this.encodeVersion = encodeVersion;
47 | }
48 |
49 | public int getStringBodylength() {
50 | return stringBodylength;
51 | }
52 |
53 | public void setStringBodylength(int stringBodylength) {
54 | this.stringBodylength = stringBodylength;
55 | }
56 |
57 | public int getBuffSize() {
58 | return buffSize;
59 | }
60 |
61 | public void setBuffSize(int buffSize) {
62 | this.buffSize = buffSize;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/mediacodec/AudioMediaCodec.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.mediacodec;
2 |
3 | import android.media.AudioFormat;
4 | import android.media.AudioManager;
5 | import android.media.AudioTrack;
6 | import android.media.MediaCodec;
7 | import android.media.MediaCodecInfo;
8 | import android.media.MediaFormat;
9 | import android.os.Build;
10 | import android.support.annotation.RequiresApi;
11 | import android.util.Log;
12 |
13 | import java.io.IOException;
14 | import java.nio.ByteBuffer;
15 |
16 | /**
17 | * Created by xu.wang
18 | * Date on 2018/08/22 14:47:47.
19 | *
20 | * @Desc
21 | */
22 |
23 | public class AudioMediaCodec {
24 | private static final String TAG = "AudioMediaCodec";
25 | public static final int DEFAULT_FREQUENCY = 44100;
26 | public static final int DEFAULT_MAX_BPS = 64;
27 | public static final int DEFAULT_MIN_BPS = 32;
28 | public static final int DEFAULT_ADTS = 1;
29 | public static final String DEFAULT_MIME = "audio/mp4a-latm";
30 | public static final int DEFAULT_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
31 | public static final int DEFAULT_AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC;
32 | public static final int DEFAULT_CHANNEL_COUNT = 2;
33 | public static final boolean DEFAULT_AEC = true;
34 |
35 |
36 | public static AudioTrack getAudioTrack() {
37 | int minBuffSize = AudioTrack.getMinBufferSize(DEFAULT_FREQUENCY, DEFAULT_CHANNEL_COUNT, DEFAULT_AUDIO_ENCODING);
38 | if (minBuffSize == AudioTrack.ERROR_BAD_VALUE) {
39 | Log.e(TAG, "Invalid parameter !");
40 | }
41 | AudioTrack mPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, DEFAULT_FREQUENCY, AudioFormat.CHANNEL_IN_STEREO,
42 | AudioFormat.ENCODING_PCM_16BIT, 2048, AudioTrack.MODE_STREAM);//
43 | if (mPlayer.getState() == AudioTrack.STATE_UNINITIALIZED) {
44 | Log.e(TAG, "AudioTrack initialize fail !");
45 | }
46 | return mPlayer;
47 | }
48 |
49 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
50 | public static MediaCodec getAudioMediaCodec() {
51 | try {
52 | //需要解码数据的类型
53 | MediaCodec mDecoder = MediaCodec.createDecoderByType(DEFAULT_MIME);
54 | //初始化解码器
55 | //MediaFormat用于描述音视频数据的相关参数
56 | MediaFormat mediaFormat = new MediaFormat();
57 | //数据类型
58 | mediaFormat.setString(MediaFormat.KEY_MIME, DEFAULT_MIME);
59 | mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, DEFAULT_AUDIO_ENCODING);
60 | //声道个数
61 | mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
62 | //采样率
63 | mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, DEFAULT_FREQUENCY);
64 | //比特率
65 | mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, DEFAULT_MAX_BPS * 1024);
66 | //用来标记AAC是否有adts头,1->有
67 | mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, DEFAULT_ADTS);
68 | //用来标记aac的类型
69 | mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, DEFAULT_AAC_PROFILE);
70 | //ByteBuffer key(暂时不了解该参数的含义,但必须设置)
71 | byte[] data = new byte[]{(byte) 0x11, (byte) 0x90};
72 | ByteBuffer csd_0 = ByteBuffer.wrap(data);
73 | mediaFormat.setByteBuffer("csd-0", csd_0);
74 | //解码器配置
75 | mDecoder.configure(mediaFormat, null, null, 0);
76 | return mDecoder;
77 | } catch (IOException e) {
78 | e.printStackTrace();
79 | Log.e(TAG, "AudioMediaCodec initial error...");
80 | }
81 |
82 | return null;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/mediacodec/VideoMediaCodec.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.mediacodec;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaFormat;
5 | import android.os.Build;
6 | import android.support.annotation.RequiresApi;
7 | import android.view.SurfaceHolder;
8 |
9 | import java.io.IOException;
10 | import java.nio.ByteBuffer;
11 |
12 | /**
13 | * Created by xu.wang
14 | * Date on 2018/08/22 14:47:47.
15 | *
16 | * @Desc
17 | */
18 |
19 | public class VideoMediaCodec {
20 | private MediaCodec mCodec;
21 | private static final int VIDEO_WIDTH = 360;
22 | private static final int VIDEO_HEIGHT = 640;
23 | private int FrameRate = 30;
24 | private boolean useSpsPPs = false;
25 | private SurfaceHolder mHolder;
26 | byte[] header_sps = {0, 0, 0, 1, 103, 66, 0, 42, (byte) 149, (byte) 168, 30, 0, (byte) 137, (byte) 249, 102, (byte) 224, 32, 32, 32, 64};
27 | byte[] header_pps = {0, 0, 0, 1, 104, (byte) 206, 60, (byte) 128, 0, 0, 0, 1, 6, (byte) 229, 1, (byte) 151, (byte) 128};
28 |
29 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
30 | public VideoMediaCodec(SurfaceHolder holder) {
31 | this.mHolder = holder;
32 | initialCodec();
33 | }
34 |
35 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
36 | private void initialCodec() {
37 | try {
38 | //通过多媒体格式名创建一个可用的解码器
39 | mCodec = MediaCodec.createDecoderByType("video/avc");
40 | } catch (IOException e) {
41 | e.printStackTrace();
42 | }
43 | //初始化编码器
44 | final MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", VIDEO_WIDTH, VIDEO_HEIGHT);
45 | //获取h264中的pps及sps数据
46 | if (useSpsPPs) {
47 | mediaformat.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps));
48 | mediaformat.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps));
49 | }
50 | //设置帧率
51 | mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, FrameRate);
52 | //设置配置参数,参数介绍 :
53 | // format 如果为解码器,此处表示输入数据的格式;如果为编码器,此处表示输出数据的格式。
54 | //surface 指定一个surface,可用作decode的输出渲染。
55 | //crypto 如果需要给媒体数据加密,此处指定一个crypto类.
56 | // flags 如果正在配置的对象是用作编码器,此处加上CONFIGURE_FLAG_ENCODE 标签。
57 | mCodec.configure(mediaformat, mHolder.getSurface(), null, 0);
58 | }
59 |
60 | public MediaCodec getCodec() {
61 | return mCodec;
62 | }
63 |
64 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
65 | public void start() {
66 | mCodec.start();
67 | }
68 |
69 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
70 | public void release() {
71 | mCodec.stop();
72 | mCodec.release();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/AcceptStreamDataThread.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp;
2 |
3 | import android.os.SystemClock;
4 | import android.util.Log;
5 |
6 | import com.test.video_play.ScreenRecordApi;
7 | import com.test.video_play.entity.Frame;
8 | import com.test.video_play.entity.ReceiveData;
9 | import com.test.video_play.entity.ReceiveHeader;
10 | import com.test.video_play.server.tcp.interf.OnAcceptBuffListener;
11 | import com.test.video_play.utils.AnalyticDataUtils;
12 | import com.test.video_play.utils.DecodeUtils;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.net.Socket;
18 |
19 | /**
20 | * Created by xu.wang
21 | * Date on 2018/08/22 14:47:47.
22 | *
23 | * @Desc 接收消息, 执行操作线程
24 | */
25 |
26 | public class AcceptStreamDataThread extends Thread implements AnalyticDataUtils.OnAnalyticDataListener {
27 | private InputStream InputStream;
28 | private OutputStream outputStream;
29 | private Socket socket;
30 | private volatile boolean startFlag = true;
31 | private OnAcceptBuffListener listener;
32 | private OnTcpChangeListener mTcpListener;
33 |
34 | private DecodeUtils mDecoderUtils;
35 | private AnalyticDataUtils mAnalyticDataUtils;
36 |
37 | //当前投屏线程
38 | private String TAG = "AcceptStreamDataThread";
39 |
40 | public AcceptStreamDataThread(Socket socket, OnAcceptBuffListener
41 | listener, OnTcpChangeListener tcpListener) {
42 | this.socket = socket;
43 | try {
44 | this.InputStream = socket.getInputStream();
45 | this.outputStream = socket.getOutputStream();
46 | } catch (Exception e) {
47 | Log.e(TAG,"get InputStream and OutputStream exception" + e.toString());
48 | }
49 | this.listener = listener;
50 | this.mTcpListener = tcpListener;
51 | mDecoderUtils = new DecodeUtils();
52 | mAnalyticDataUtils = new AnalyticDataUtils();
53 | mAnalyticDataUtils.setOnAnalyticDataListener(this);
54 | startFlag = true;
55 | mDecoderUtils.setOnVideoListener(new DecodeUtils.OnVideoListener() {
56 | @Override
57 | public void onSpsPps(byte[] sps, byte[] pps) {
58 | Frame spsPpsFrame = new Frame();
59 | spsPpsFrame.setType(Frame.SPSPPS);
60 | spsPpsFrame.setSps(sps);
61 | spsPpsFrame.setPps(pps);
62 | Log.d("AcceptH264MsgThread", "sps pps ...");
63 | AcceptStreamDataThread.this.listener.acceptBuff(spsPpsFrame);
64 | }
65 |
66 | @Override
67 | public void onVideo(byte[] video, int type) {
68 | Frame frame = new Frame();
69 | switch (type) {
70 | case Frame.KEY_FRAME:
71 | frame.setType(Frame.KEY_FRAME);
72 | frame.setBytes(video);
73 | Log.d("AcceptH264MsgThread", "key frame ...");
74 | AcceptStreamDataThread.this.listener.acceptBuff(frame);
75 | break;
76 | case Frame.NORMAL_FRAME:
77 | frame.setType(Frame.NORMAL_FRAME);
78 | frame.setBytes(video);
79 | Log.d("AcceptH264MsgThread", "normal frame ...");
80 | AcceptStreamDataThread.this.listener.acceptBuff(frame);
81 | break;
82 | case Frame.AUDIO_FRAME:
83 | frame.setType(Frame.AUDIO_FRAME);
84 | frame.setBytes(video);
85 | Log.d("AcceptH264MsgThread", "audio frame ...");
86 | AcceptStreamDataThread.this.listener.acceptBuff(frame);
87 | break;
88 | default:
89 | Log.e("AcceptH264MsgThread", "other video...");
90 | break;
91 | }
92 |
93 | }
94 | });
95 | }
96 |
97 | public void sendStartMessage() {
98 | //告诉客户端,可以开始投屏了
99 | try {
100 | outputStream.write("OK".getBytes());
101 | Log.i(TAG, "send start message");
102 | } catch (IOException e) {
103 | if (mTcpListener != null) {
104 | mTcpListener.disconnect(e);
105 | }
106 | }
107 | }
108 |
109 | @Override
110 | public void run() {
111 | super.run();
112 | if (mTcpListener != null) {
113 | mTcpListener.connect();
114 | }
115 | sendStartMessage();
116 | mAnalyticDataUtils.startNetSpeedCalculate();
117 | readMessage();
118 | mAnalyticDataUtils.stop();
119 | }
120 |
121 | //读取数据
122 | private void readMessage() {
123 | try {
124 | while (startFlag) {
125 | //开始接收客户端发过来的数据
126 | byte[] header = mAnalyticDataUtils.readByte(InputStream, 17);
127 | //数据如果为空,则休眠,防止cpu空转, 0.0 不可能会出现的,会一直阻塞在之前
128 | if (header == null || header.length == 0) {
129 | SystemClock.sleep(1);
130 | continue;
131 | }
132 | // 根据协议分析数据头
133 | ReceiveHeader receiveHeader = mAnalyticDataUtils.analysisHeader(header);
134 | if (receiveHeader.getStringBodylength() == 0 && receiveHeader.getBuffSize() == 0) {
135 | SystemClock.sleep(1);
136 | continue;
137 | }
138 | if (receiveHeader.getEncodeVersion() != ScreenRecordApi.encodeVersion1) {
139 | Log.e(TAG, "接收到的数据格式不对...");
140 | continue;
141 | }
142 | ReceiveData receiveData = mAnalyticDataUtils.analyticData(InputStream, receiveHeader);
143 | if (receiveData == null || receiveData.getBuff() == null) {
144 | continue;
145 | }
146 | //区分音视频
147 | mDecoderUtils.isCategory(receiveData.getBuff());
148 | }
149 | } catch (Exception e) {
150 | if (mTcpListener != null) {
151 | Log.e(TAG, "readMessage Exception = " + e.toString());
152 | mTcpListener.disconnect(e);
153 | }
154 | } finally {
155 | startFlag = false;
156 | try {
157 | socket.close();
158 | } catch (IOException e) {
159 | mTcpListener.disconnect(e);
160 | }
161 | }
162 | }
163 |
164 | public void shutdown() {
165 | try {
166 | socket.close();
167 | } catch (IOException e) {
168 | e.printStackTrace();
169 | }
170 | startFlag = false;
171 | if (mAnalyticDataUtils != null) mAnalyticDataUtils.stop();
172 | this.interrupt();
173 | }
174 |
175 | @Override
176 | public void netSpeed(String msg) {
177 | if (mTcpListener != null) mTcpListener.netSpeed(msg);
178 | }
179 |
180 | public interface OnTcpChangeListener {
181 | void disconnect(Exception e);
182 |
183 | void connect();
184 |
185 | void netSpeed(String netSpeed);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/EncodeV1.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.test.video_play.ScreenRecordApi;
6 | import com.test.video_play.utils.ByteUtil;
7 |
8 | import java.nio.ByteBuffer;
9 |
10 | /**
11 | * Created by xu.wang
12 | * Date on 2018/08/22 14:47:47.
13 | *
14 | * @Desc 传输数据格式
15 | */
16 |
17 | public class EncodeV1 {
18 | private int mainCmd;
19 | private int subCmd;
20 | private String sendBody;
21 | private byte[] sendBuffer; //要发送的内容
22 |
23 | /**
24 | * by wt
25 | *
26 | * @param mainCmd 主指令
27 | * @param subCmd 子指令
28 | * @param sendBody 文本内容
29 | * @param sendBuffer 音视频内容
30 | */
31 | public EncodeV1(int mainCmd, int subCmd, String sendBody, byte[] sendBuffer) {
32 | this.mainCmd = mainCmd;
33 | this.subCmd = subCmd;
34 | this.sendBody = sendBody;
35 | this.sendBuffer = sendBuffer;
36 | }
37 | public byte[] buildSendContent() {
38 | int bodyLength = 0;
39 | int bodyByte = 0;
40 | ByteBuffer bb = null;
41 | //文本数据
42 | if (!TextUtils.isEmpty(sendBody)) {
43 | bodyLength = sendBody.getBytes().length;
44 | }
45 | //音视频数据
46 | if (sendBuffer.length != 0) {
47 | bodyByte = sendBuffer.length;
48 | }
49 | //创建内存缓冲区
50 | bb = ByteBuffer.allocate(18 + bodyLength + bodyByte);
51 | bb.put(ScreenRecordApi.encodeVersion1); //0-1编码版本
52 | bb.put(ByteUtil.int2Bytes(mainCmd)); //1-5 主指令
53 | bb.put(ByteUtil.int2Bytes(subCmd)); //5-9 子指令
54 | bb.put(ByteUtil.int2Bytes(bodyLength)); //9-13位,文本数据长度
55 | bb.put(ByteUtil.int2Bytes(bodyByte)); //13-17位,音视频数据长度
56 | byte[] tempb = bb.array();
57 | bb.put(ByteUtil.getCheckCode(tempb));
58 | //数据字节数组
59 | if (bodyLength != 0) {
60 | bb.put(sendBody.getBytes());
61 | }
62 | if (sendBuffer.length != 0) {
63 | bb.put(sendBuffer);
64 | }
65 | return bb.array();
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/NormalPlayQueue.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.video_play.entity.Frame;
6 |
7 | import java.util.ArrayList;
8 | import java.util.concurrent.ArrayBlockingQueue;
9 | import java.util.concurrent.atomic.AtomicInteger;
10 |
11 | /**
12 | * Created by xu.wang
13 | * Date on 2018/08/22 14:47:47.
14 | *
15 | * @Desc 缓存区
16 | */
17 |
18 | public class NormalPlayQueue {
19 | private ArrayBlockingQueue mPlayQueue;
20 | private String TAG = "NormalPlayQueue";
21 | private static final int SCAN_MAX_TIME = 5; //仲裁次数,每循环SCAN_MAX_TIME 次,每次sleep(DEFAULT_SLEEP_TIME),会执行一次检查网速的代码
22 | private static final int DEFAULT_SLEEP_TIME = 200; //
23 | private static final int DEFAULT_NEGATIVE_COUNT = 3; //循环SCAN_MAX_TIME 次,有 DEFAULT_NEGATIVE_COUNT 次输入queue的帧小于取走的帧
24 |
25 | private static final int NORMAL_FRAME_BUFFER_SIZE = 800; //缓存区大小
26 | private int mFullQueueCount = NORMAL_FRAME_BUFFER_SIZE;
27 | private AtomicInteger mTotalFrameCount = new AtomicInteger(0); //总个数
28 | private AtomicInteger mGiveUpFrameCount = new AtomicInteger(0); //总个数
29 | private AtomicInteger mKeyFrameCount = new AtomicInteger(0); //队列里Key帧的总个数...
30 |
31 | private AtomicInteger mInFrameCount = new AtomicInteger(0); //进入总个数
32 | private AtomicInteger mOutFrameCount = new AtomicInteger(0); //输出总个数
33 |
34 | private ScanThread mScanThread;
35 | private volatile boolean mScanFlag;
36 | private boolean isDebug = false;
37 |
38 | public NormalPlayQueue() {
39 | mScanFlag = true;
40 | mScanThread = new ScanThread();
41 | mScanThread.start();
42 | mPlayQueue = new ArrayBlockingQueue(NORMAL_FRAME_BUFFER_SIZE, true);
43 | }
44 |
45 |
46 | public Frame takeByte() {
47 | try {
48 | Frame frame = mPlayQueue.take();
49 | showLog("playqueue count = " + mPlayQueue.size());
50 | if (frame.getType() == Frame.KEY_FRAME) mKeyFrameCount.getAndDecrement();
51 | mOutFrameCount.getAndIncrement();
52 | mTotalFrameCount.getAndDecrement();
53 | return frame;
54 | } catch (InterruptedException e) {
55 | showLog("take bytes exception" + e.toString());
56 | return null;
57 | }
58 | }
59 |
60 | public void putByte(Frame frame) {
61 | if (frame.getType() == Frame.KEY_FRAME) mKeyFrameCount.getAndIncrement();
62 | abandonData();
63 | try {
64 | mPlayQueue.put(frame);
65 | mInFrameCount.getAndIncrement();
66 | mTotalFrameCount.getAndIncrement();
67 | } catch (InterruptedException e) {
68 | showLog("put bytes exception" + e.toString());
69 | }
70 | }
71 |
72 | public void stop() {
73 | mScanFlag = false;
74 | mTotalFrameCount.set(0);
75 | mGiveUpFrameCount.set(0);
76 | if (mPlayQueue != null) {
77 | mPlayQueue.clear();
78 | }
79 | }
80 |
81 | private void abandonData() {
82 | if (mTotalFrameCount.get() >= (mFullQueueCount / 3)) {
83 | showLog("队列里的帧数太多,开始丢帧..");
84 | //从队列头部开始搜索,删除最早发现的连续P帧
85 | boolean pFrameDelete = false;
86 | boolean start = false;
87 | for (Frame frame : mPlayQueue) {
88 | if (!start) showLog("丢掉了下一个KEY_FRAME前的所有INTER_FRAME..");
89 | if (frame.getType() == Frame.NORMAL_FRAME) {
90 | start = true;
91 | }
92 | if (start) {
93 | if (frame.getType() == Frame.NORMAL_FRAME) {
94 | mPlayQueue.remove(frame);
95 | mTotalFrameCount.getAndDecrement();
96 | mGiveUpFrameCount.getAndIncrement();
97 | pFrameDelete = true;
98 | } else if (frame.getType() == Frame.KEY_FRAME) {
99 | if (mKeyFrameCount.get() > 5) {
100 | Log.d(TAG, "丢掉了一个关键帧.. total" + mKeyFrameCount.get());
101 | mPlayQueue.remove(frame);
102 | mKeyFrameCount.getAndDecrement();
103 | continue;
104 | }
105 | break;
106 | }
107 | }
108 | }
109 | boolean kFrameDelete = false;
110 | //从队列头部开始搜索,删除最早发现的I帧
111 | if (!pFrameDelete) {
112 | for (Frame frame : mPlayQueue) {
113 | if (frame.getType() == Frame.KEY_FRAME) {
114 | mPlayQueue.remove(frame);
115 | Log.d(TAG, "丢掉了一个关键帧..");
116 | mTotalFrameCount.getAndDecrement();
117 | mGiveUpFrameCount.getAndIncrement();
118 | mKeyFrameCount.getAndDecrement();
119 | kFrameDelete = true;
120 | break;
121 | }
122 | }
123 | }
124 | //从队列头部开始搜索,删除音频
125 | if (!pFrameDelete && !kFrameDelete) {
126 | for (Frame frame : mPlayQueue) {
127 | if (frame.getType() == Frame.AUDIO_FRAME) {
128 | mPlayQueue.remove(frame);
129 | mTotalFrameCount.getAndDecrement();
130 | mGiveUpFrameCount.getAndIncrement();
131 | break;
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | private class ScanThread extends Thread {
139 |
140 | private int mCurrentScanTime = 0;
141 | private ArrayList mScanSnapShotList = new ArrayList<>();
142 |
143 | @Override
144 | public void run() {
145 | while (mScanFlag) {
146 | //达到仲裁次数了
147 | if (mCurrentScanTime == SCAN_MAX_TIME) {
148 | int averageDif = 0;
149 | int negativeCounter = 0;
150 | String strLog = "";
151 | for (int i = 0; i < SCAN_MAX_TIME; i++) {
152 | int dif = mScanSnapShotList.get(i).outCount - mScanSnapShotList.get(i).inCount;
153 | if (dif < 0) {
154 | negativeCounter++;
155 | }
156 | averageDif += dif;
157 | strLog = strLog + String.format("n%d:%d ", i, dif);
158 | }
159 | Log.d(TAG, strLog);
160 | if (negativeCounter >= DEFAULT_NEGATIVE_COUNT || averageDif < -100) {
161 | //坏
162 | showLog("Bad Send Speed.");
163 |
164 | } else {
165 | //好
166 | showLog("Good Send Speed.");
167 |
168 | }
169 | //清空
170 | mScanSnapShotList.clear();
171 |
172 | }
173 | mScanSnapShotList.add(new ScanSnapShot(mInFrameCount.get(), mOutFrameCount.get()));
174 | mInFrameCount.set(0);
175 | mOutFrameCount.set(0);
176 | mCurrentScanTime++;
177 | try {
178 | Thread.sleep(DEFAULT_SLEEP_TIME);
179 | } catch (InterruptedException e) {
180 | e.printStackTrace();
181 | }
182 | }
183 | }
184 | }
185 |
186 | private class ScanSnapShot {
187 | public int inCount;
188 | public int outCount;
189 |
190 | public ScanSnapShot(int inCount, int outCount) {
191 | this.inCount = inCount;
192 | this.outCount = outCount;
193 | }
194 | }
195 |
196 | private void showLog(String msg) {
197 | if (isDebug) Log.i("NormalSendQueue", "" + msg);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/TcpServer.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.video_play.ScreenRecordApi;
6 | import com.test.video_play.ScreenRecordController;
7 | import com.test.video_play.entity.ReceiveHeader;
8 | import com.test.video_play.server.tcp.interf.OnAcceptBuffListener;
9 | import com.test.video_play.utils.AnalyticDataUtils;
10 |
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.net.InetSocketAddress;
14 | import java.net.ServerSocket;
15 | import java.net.Socket;
16 |
17 | /**
18 | * Created by xu.wang
19 | * Date on 2018/08/22 14:47:47.
20 | *
21 | * @Desc 创建监听
22 | */
23 |
24 |
25 | public class TcpServer implements AcceptStreamDataThread.OnTcpChangeListener {
26 | private static final String TAG = "TcpServer";
27 | private ServerSocket serverSocket;
28 | private boolean isAccept = true;
29 | private OnAcceptBuffListener mListener;
30 | //把线程给添加进来
31 | private AnalyticDataUtils mAnalyticUtils;
32 | private AcceptStreamDataThread acceptStreamDataThread;
33 |
34 | public TcpServer() {
35 | mAnalyticUtils = new AnalyticDataUtils();
36 | }
37 |
38 | public void startServer() {
39 | new Thread() {
40 | @Override
41 | public void run() {
42 | super.run();
43 | try {
44 | serverSocket = new ServerSocket();
45 | serverSocket.setReuseAddress(true);
46 | InetSocketAddress socketAddress = new InetSocketAddress(ScreenRecordController.port);
47 | serverSocket.bind(socketAddress);
48 | Log.i(TAG, "ServerSocket start bind port " + ScreenRecordController.port);
49 | while (isAccept) {
50 | //服务端接收客户端的连接请求
51 | try {
52 | Socket socket = serverSocket.accept();
53 | InputStream inputStream = socket.getInputStream();
54 | byte[] temp = mAnalyticUtils.readByte(inputStream, 17);
55 | ReceiveHeader receiveHeader = mAnalyticUtils.analysisHeader(temp);
56 | if (receiveHeader.getMainCmd() == ScreenRecordApi.RECORD.MAIN_CMD) {//投屏请求
57 | if (acceptStreamDataThread != null) {
58 | acceptStreamDataThread.shutdown();
59 | acceptStreamDataThread = null;
60 | }
61 | acceptStreamDataThread = new AcceptStreamDataThread(socket, mListener, TcpServer.this);
62 | acceptStreamDataThread.start();
63 | } else {
64 | Log.e(TAG, "accept other connect and close socket");
65 | socket.close();
66 | }
67 | } catch (Exception e) {
68 | Log.e(TAG, "connect has Exception = " + e.toString());
69 | }
70 | }
71 | } catch (Exception e) {
72 | Log.e(TAG, "exception close." + e.toString());
73 | } finally {
74 | Log.e(TAG, "TcpServer: thread close");
75 | try {
76 | serverSocket.close();
77 | } catch (IOException e) {
78 | e.printStackTrace();
79 | }
80 | }
81 | }
82 | }.start();
83 | }
84 |
85 | public void setOnAccepttBuffListener(OnAcceptBuffListener listener) {
86 | this.mListener = listener;
87 | }
88 |
89 | public void stopServer() {
90 | Log.e(TAG, "stopServer: stop server");
91 | this.mListener = null;
92 | isAccept = false;
93 | new Thread() {
94 | @Override
95 | public void run() {
96 | super.run();
97 | try {
98 | if (acceptStreamDataThread != null) {
99 | acceptStreamDataThread.shutdown();
100 | acceptStreamDataThread = null;
101 | }
102 | if (serverSocket != null) {
103 | serverSocket.close();
104 | }
105 | } catch (IOException e) {
106 | e.printStackTrace();
107 | }
108 | }
109 | }.start();
110 | }
111 |
112 | @Override
113 | public void connect() {
114 | if (ScreenRecordController.mServerStateChangeListener != null) {
115 | if (ScreenRecordController.mServerStateChangeListener != null) {
116 | ScreenRecordController.mHandler.post(new Runnable() {
117 | @Override
118 | public void run() {
119 | ScreenRecordController.mServerStateChangeListener.acceptH264TcpConnect();
120 | }
121 | });
122 | }
123 | }
124 | }
125 |
126 | @Override
127 | public void disconnect(final Exception e) {
128 | if (ScreenRecordController.mServerStateChangeListener != null) {
129 | ScreenRecordController.mHandler.post(new Runnable() {
130 | @Override
131 | public void run() {
132 | ScreenRecordController.mServerStateChangeListener.acceptH264TcpDisConnect(e);
133 | }
134 | });
135 | }
136 | }
137 |
138 | @Override
139 | public void netSpeed(final String netSpeed) {
140 | if (ScreenRecordController.mServerStateChangeListener != null) {
141 | ScreenRecordController.mHandler.post(new Runnable() {
142 | @Override
143 | public void run() {
144 | ScreenRecordController.mServerStateChangeListener.acceptH264TcpNetSpeed(netSpeed);
145 | }
146 | });
147 | }
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/interf/OnAcceptBuffListener.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp.interf;
2 |
3 |
4 | import com.test.video_play.entity.Frame;
5 |
6 | /**
7 | * Created by xu.wang
8 | * Date on 2018/08/22 14:47:47.
9 | *
10 | * @Desc 关于帧类型回调
11 | */
12 |
13 | public interface OnAcceptBuffListener {
14 | void acceptBuff(Frame frame);
15 | }
16 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/server/tcp/interf/OnServerStateChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.server.tcp.interf;
2 |
3 | /**
4 | * Created by xu.wang
5 | * Date on 2018/08/22 14:47:47.
6 | *
7 | * @Desc 监听连接回调
8 | */
9 |
10 | public abstract class OnServerStateChangeListener {
11 | //接收到客户端的Tcp连接
12 | public abstract void acceptH264TcpConnect();
13 |
14 | /**
15 | * by wt
16 | * 接收到客户端的Tcp断开连接
17 | *
18 | * @param e 异常提示
19 | */
20 | public abstract void acceptH264TcpDisConnect(Exception e);
21 |
22 | //读数据的时间
23 | public void acceptH264TcpNetSpeed(String netSpeed) {
24 |
25 | }
26 |
27 | public abstract void exception();
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/AboutNetUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 | import android.net.wifi.WifiInfo;
7 | import android.net.wifi.WifiManager;
8 | import android.os.Build;
9 | import android.text.TextUtils;
10 | import android.util.Log;
11 |
12 | import java.net.Inet4Address;
13 | import java.net.InetAddress;
14 | import java.net.NetworkInterface;
15 | import java.net.SocketException;
16 | import java.util.Enumeration;
17 |
18 | /**
19 | * Created by wt on 2018/6/15.
20 | * 获取IP工具类
21 | */
22 | public class AboutNetUtils {
23 | public static String getIPAddress(Context context) {
24 | NetworkInfo info = ((ConnectivityManager) context
25 | .getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
26 | if (info != null && info.isConnected()) {
27 | if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
28 | //当前使用2G/3G/4G网络
29 | try {
30 | //Enumeration en=NetworkInterface.getNetworkInterfaces();
31 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
32 | NetworkInterface intf = en.nextElement();
33 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
34 | InetAddress inetAddress = enumIpAddr.nextElement();
35 | if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
36 | return inetAddress.getHostAddress();
37 | }
38 | }
39 | }
40 | } catch (SocketException e) {
41 | e.printStackTrace();
42 | }
43 |
44 | } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//当前使用无线网络
45 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
46 | WifiInfo wifiInfo = wifiManager.getConnectionInfo();
47 | String ipAddress = intIP2StringIP(wifiInfo.getIpAddress());//得到IPV4地址
48 | return ipAddress;
49 | }
50 | } else {
51 | //当前无网络连接,请在设置中打开网络
52 | return null;
53 | }
54 | return null;
55 | }
56 |
57 | /**
58 | * 将得到的int类型的IP转换为String类型
59 | *
60 | * @param ip
61 | * @return
62 | */
63 | public static String intIP2StringIP(int ip) {
64 | return (ip & 0xFF) + "." +
65 | ((ip >> 8) & 0xFF) + "." +
66 | ((ip >> 16) & 0xFF) + "." +
67 | (ip >> 24 & 0xFF);
68 | }
69 |
70 | // TODO: 2018/6/28 获取设备名称
71 | public static String getDeviceModel() {
72 | return Build.MODEL;
73 | }
74 |
75 | // TODO: 2018/6/28 获取无线网名称
76 | // public static String getWinfeName(Context context) {
77 | // WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
78 | // int wifiState = wifiMgr.getWifiState();
79 | // WifiInfo info = wifiMgr.getConnectionInfo();
80 | // if (info==null|| TextUtils.isEmpty(info.getSSID())){
81 | // return null;
82 | // }
83 | // //获取Android版本号
84 | // int sdkInt = Build.VERSION.SDK_INT;
85 | // if (sdkInt >= 17) {
86 | // if (info.getSSID().startsWith("\"") && info.getSSID().endsWith("\"")) {
87 | //
88 | // return info.getSSID().substring(1, info.getSSID().length() - 1);
89 | // }
90 | // }
91 | // return info != null ? info.getSSID() : null;
92 | // }
93 |
94 | public static boolean isNetWorkConnected(Context context) {
95 | // TODO Auto-generated method stub
96 | try{
97 | ConnectivityManager connectivity = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
98 | if(connectivity != null){
99 | NetworkInfo netWorkinfo = connectivity.getActiveNetworkInfo();
100 | if(netWorkinfo != null && netWorkinfo.isAvailable()){
101 | if(netWorkinfo.getState() == NetworkInfo.State.CONNECTED){
102 | return true;
103 | }
104 | }
105 | }
106 | }catch(Exception e){
107 | Log.e("UdpService : ",e.toString());
108 | return false;
109 | }
110 | return false;
111 | }
112 |
113 | // TODO: 2018/7/12 获取本地所有ip地址
114 | public static String getLocalIpAddress() {
115 | String address = null;
116 | try {
117 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
118 | NetworkInterface intf = en.nextElement();
119 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
120 | InetAddress inetAddress = enumIpAddr.nextElement();
121 | if (!inetAddress.isLoopbackAddress()) {
122 | address = inetAddress.getHostAddress().toString();
123 | //ipV6
124 | if (!address.contains("::")) {
125 | return address;
126 | }
127 | }
128 | }
129 | }
130 | } catch (SocketException ex) {
131 | Log.e("getIpAddress Exception", ex.toString());
132 | }
133 | return null;
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/AnalyticDataUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.video_play.entity.ReceiveData;
6 | import com.test.video_play.entity.ReceiveHeader;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.util.Timer;
12 | import java.util.TimerTask;
13 |
14 | /**
15 | * Created by xu.wang
16 | * Date on 2018/08/22 14:47:47.
17 | *
18 | * @Desc 根据协议解析数据
19 | */
20 |
21 | public class AnalyticDataUtils {
22 | private OnAnalyticDataListener mListener;
23 | private volatile int readLength = 0;
24 | private Timer timer;
25 | private boolean isCalculate = false;
26 |
27 | /**
28 | * 分析头部数据
29 | */
30 | public ReceiveHeader analysisHeader(byte[] header) {
31 | //实现数组之间的复制
32 | //bytes:源数组
33 | //srcPos:源数组要复制的起始位置
34 | //dest:目的数组
35 | //destPos:目的数组放置的起始位置
36 | //length:复制的长度
37 | byte[] buff = new byte[4];
38 | System.arraycopy(header, 1, buff, 0, 4);
39 | final int mainCmd = ByteUtil.bytesToInt(buff); //主指令 1`4
40 | buff = new byte[4];
41 | System.arraycopy(header, 5, buff, 0, 4);
42 | final int subCmd = ByteUtil.bytesToInt(buff); //子指令 5`8
43 | buff = new byte[4];
44 | System.arraycopy(header, 9, buff, 0, 4);
45 | int stringBodyLength = ByteUtil.bytesToInt(buff);//文本数据 9 ~ 12;
46 | buff = new byte[4];
47 | System.arraycopy(header, 13, buff, 0, 4);
48 | int byteBodySize = ByteUtil.bytesToInt(buff);//byte数据 13^16
49 | return new ReceiveHeader(mainCmd, subCmd, header[0], stringBodyLength, byteBodySize);
50 | }
51 |
52 |
53 | /**
54 | * 解析数据
55 | *
56 | * @param is
57 | * @param receiveHeader
58 | * @return
59 | * @throws IOException
60 | */
61 | public ReceiveData analyticData(InputStream is, ReceiveHeader receiveHeader) throws IOException {
62 | byte[] sendBody = null;
63 | byte[] buff = null;
64 | //文本长度
65 | if (receiveHeader.getStringBodylength() != 0) {
66 | sendBody = readByte(is, receiveHeader.getStringBodylength());
67 | }
68 | //音视频长度
69 | if (receiveHeader.getBuffSize() != 0) {
70 | buff = readByte(is, receiveHeader.getBuffSize());
71 | }
72 | ReceiveData data = new ReceiveData();
73 | data.setHeader(receiveHeader);
74 | // data.setSendBody(sendBody == null ? "" : new String(sendBody));
75 | data.setBuff(buff);
76 | return data;
77 | }
78 |
79 | /**
80 | * 保证从流里读到指定长度数据
81 | *
82 | * @param is
83 | * @param readSize
84 | * @return
85 | * @throws Exception
86 | */
87 | public byte[] readByte(InputStream is, int readSize) throws IOException {
88 | byte[] buff = new byte[readSize];
89 | int len = 0;
90 | int eachLen = 0;
91 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
92 | while (len < readSize) {
93 | eachLen = is.read(buff);
94 | if (eachLen != -1) {
95 | if (isCalculate) readLength += eachLen;
96 | len += eachLen;
97 | baos.write(buff, 0, eachLen);
98 | } else {
99 | baos.close();
100 | throw new IOException();
101 | }
102 | if (len < readSize) {
103 | buff = new byte[readSize - len];
104 | }
105 | }
106 | byte[] b = baos.toByteArray();
107 | baos.close();
108 | return b;
109 | }
110 |
111 |
112 | public interface OnAnalyticDataListener {
113 | // void onSuccess(ReceiveData data);
114 |
115 | void netSpeed(String msg);
116 |
117 | }
118 |
119 | public void setOnAnalyticDataListener(OnAnalyticDataListener listener) {
120 | this.mListener = listener;
121 | }
122 |
123 | public void startNetSpeedCalculate() {
124 | stop();
125 | readLength = 0;
126 | isCalculate = true;
127 | timer = new Timer();
128 | timer.schedule(new TimerTask() {
129 | @Override
130 | public void run() {
131 | if (mListener != null) {
132 | mListener.netSpeed((readLength / 1024) + " kb/s");
133 | readLength = 0;
134 | }
135 | }
136 | }, 1000, 1000);
137 | }
138 |
139 | public void stop() {
140 | isCalculate = false;
141 | try {
142 | if (timer != null) timer.cancel();
143 | } catch (Exception e) {
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/ByteUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | /**
4 | * Created by xu.wang
5 | * Date on 2018/08/22 14:47:47.
6 | *
7 | * @Desc
8 | */
9 | public class ByteUtil {
10 | /**
11 | * 将int转为长度为4的byte数组
12 | *
13 | * @param length
14 | * @return
15 | */
16 | public static byte[] int2Bytes(int length) {
17 | byte[] result = new byte[4];
18 | result[0] = (byte) length;
19 | result[1] = (byte) (length >> 8);
20 | result[2] = (byte) (length >> 16);
21 | result[3] = (byte) (length >> 24);
22 | return result;
23 | }
24 |
25 | //转成2个字节
26 | public static byte[] short2Bytes(short size) {
27 | byte[] result = new byte[2];
28 | result[0] = (byte) size;
29 | result[1] = (byte) (size >> 8);
30 | return result;
31 | }
32 |
33 | /**
34 | * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用
35 | *
36 | * @param src byte数组
37 | * @return int数值
38 | */
39 | public static int bytesToInt(byte[] src) {
40 | int value;
41 | value = (int) ((src[0] & 0xFF)
42 | | ((src[1] & 0xFF) << 8)
43 | | ((src[2] & 0xFF) << 16)
44 | | ((src[3] & 0xFF) << 24));
45 | return value;
46 | }
47 |
48 | // TODO: 2018/6/11 wt byte转short
49 | public static short bytesToShort(byte[] src) {
50 | short value;
51 | value = (short) ((src[0] & 0xFF)
52 | | ((src[1] & 0xFF) << 8));
53 | return value;
54 | }
55 |
56 |
57 | /**
58 | * 获得校验码
59 | *
60 | * @param bytes 根据通讯协议的前12个字节
61 | * @return
62 | */
63 | public static byte getCheckCode(byte[] bytes) {
64 | byte b = 0x00;
65 | for (int i = 0; i < bytes.length; i++) {
66 | b ^= bytes[i];
67 | }
68 | return b;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/DecodeUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.video_play.entity.Frame;
6 |
7 | /**
8 | * Created by xu.wang
9 | * Date on 2018/08/22 14:47:47.
10 | *
11 | * @Desc 解析H264和AAC的Decoder
12 | */
13 | public class DecodeUtils {
14 | private static final String TAG = "H264AacDecoder";
15 | // Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( )
16 | public final static int NonIDR = 1;
17 | // Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( )
18 | public final static int IDR = 5;
19 | // Supplemental enhancement information (SEI) sei_rbsp( )
20 | public final static int SEI = 6;
21 | // Sequence parameter set seq_parameter_set_rbsp( )
22 | public final static int SPS = 7;
23 | // Picture parameter set pic_parameter_set_rbsp( )
24 | public final static int PPS = 8;
25 | // Access unit delimiter access_unit_delimiter_rbsp( )
26 | public final static int AccessUnitDelimiter = 9;
27 |
28 | //
29 | public final static int AUDIO = -2;
30 |
31 | private byte[] mPps;
32 | private byte[] mSps;
33 |
34 |
35 | public void isCategory(byte[] frame) {
36 | boolean isKeyFrame = false;
37 | if (frame == null) {
38 | Log.e(TAG, "annexb not match.");
39 | return;
40 | }
41 | // ignore the nalu type aud(9)
42 | if (isAccessUnitDelimiter(frame)) {
43 | return;
44 | }
45 | // for pps
46 | if (isPps(frame)) {
47 | mPps = frame;
48 | if (mPps != null && mSps != null) {
49 | mListener.onSpsPps(mSps, mPps);
50 | }
51 | return;
52 | }
53 | // for sps
54 | if (isSps(frame)) {
55 | mSps = frame;
56 | if (mPps != null && mSps != null) {
57 | mListener.onSpsPps(mSps, mPps);
58 | }
59 | return;
60 | }
61 | if (isAudio(frame)) {
62 | byte[] temp = new byte[frame.length - 4];
63 | System.arraycopy(frame, 4, temp, 0, frame.length - 4);
64 | mListener.onVideo(temp, Frame.AUDIO_FRAME);
65 | return;
66 | }
67 | // for IDR frame
68 | if (isKeyFrame(frame)) {
69 | isKeyFrame = true;
70 | } else {
71 | isKeyFrame = false;
72 | }
73 | mListener.onVideo(frame, isKeyFrame ? Frame.KEY_FRAME : Frame.NORMAL_FRAME);
74 | }
75 |
76 | private boolean isAudio(byte[] frame) {
77 | if (frame.length < 5) {
78 | return false;
79 | }
80 | return frame[4] == ((byte) 0xFF) && frame[5] == ((byte) 0xF9);
81 | }
82 |
83 | private boolean isSps(byte[] frame) {
84 | if (frame.length < 5) {
85 | return false;
86 | }
87 | // 5bits, 7.3.1 NAL unit syntax,
88 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
89 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
90 | int nal_unit_type = (frame[4] & 0x1f);
91 | return nal_unit_type == SPS;
92 | }
93 |
94 | private boolean isPps(byte[] frame) {
95 | if (frame.length < 5) {
96 | return false;
97 | }
98 | // 5bits, 7.3.1 NAL unit syntax,
99 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
100 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
101 | int nal_unit_type = (frame[4] & 0x1f);
102 | return nal_unit_type == PPS;
103 | }
104 |
105 | private boolean isKeyFrame(byte[] frame) {
106 | if (frame.length < 5) {
107 | return false;
108 | }
109 | // 5bits, 7.3.1 NAL unit syntax,
110 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
111 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
112 | int nal_unit_type = (frame[4] & 0x1f);
113 | return nal_unit_type == IDR;
114 | }
115 |
116 | private static boolean isAccessUnitDelimiter(byte[] frame) {
117 | if (frame.length < 5) {
118 | return false;
119 | }
120 | // 5bits, 7.3.1 NAL unit syntax,
121 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
122 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
123 | int nal_unit_type = (frame[4] & 0x1f);
124 | return nal_unit_type == AccessUnitDelimiter;
125 | }
126 |
127 | // TODO: 2018/6/7 自定义回调接口
128 | public OnVideoListener mListener;
129 |
130 | public void setOnVideoListener(OnVideoListener listener) {
131 | this.mListener = listener;
132 | }
133 |
134 | public interface OnVideoListener {
135 | void onSpsPps(byte[] sps, byte[] pps);
136 |
137 | void onVideo(byte[] video, int type);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/DensityUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.util.DisplayMetrics;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.RelativeLayout;
10 |
11 | public class DensityUtil {
12 |
13 | // 根据手机的分辨率将dp的单位转成px(像素)
14 | public static int dip2px(Context context, float dpValue) {
15 | final float scale = context.getResources().getDisplayMetrics().density;
16 | return (int) (dpValue * scale + 0.5f);
17 | }
18 |
19 | // 根据手机的分辨率将px(像素)的单位转成dp
20 | public static int px2dip(Context context, float pxValue) {
21 | final float scale = context.getResources().getDisplayMetrics().density;
22 | return (int) (pxValue / scale + 0.5f);
23 | }
24 |
25 | // 将px值转换为sp值
26 | public static int px2sp(Context context, float pxValue) {
27 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
28 | return (int) (pxValue / fontScale + 0.5f);
29 | }
30 |
31 | // 将sp值转换为px值
32 | public static int sp2px(Context context, float spValue) {
33 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
34 | Log.e("123", "sp2px: " + fontScale);
35 | return (int) (spValue * fontScale + 0.5f);
36 | }
37 |
38 | // 屏幕宽度(像素)
39 | public static int getWindowWidth(Activity context) {
40 | DisplayMetrics metric = new DisplayMetrics();
41 | context.getWindowManager().getDefaultDisplay().getMetrics(metric);
42 | return metric.widthPixels;
43 | }
44 |
45 | // 屏幕高度(像素)
46 | public static int getWindowHeight(Activity activity) {
47 | DisplayMetrics metric = new DisplayMetrics();
48 | activity.getWindowManager().getDefaultDisplay().getMetrics(metric);
49 | return metric.heightPixels;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/video_play/src/main/java/com/test/video_play/utils/WeakHandler.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play.utils;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.os.Message;
6 |
7 | import java.lang.ref.WeakReference;
8 | import java.util.concurrent.locks.Lock;
9 | import java.util.concurrent.locks.ReentrantLock;
10 |
11 | /**
12 | * 关于内存处理类
13 | * 在执行队列中,处理程序的原始实现始终保持对处理程序的硬引用。
14 | * 如果您创建匿名处理程序并将延迟消息插入其中,它将保持所有父类在记忆中的那个时间,即使它可以被清理。
15 | * 此实现更为棘手,它将对运行信息和消息保持弱引用,和GC可以收集它们一旦弱处理程序实例不再被引用。
16 | */
17 | @SuppressWarnings("unused")
18 | public class WeakHandler {
19 | private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory
20 | private final ExecHandler mExec;
21 | private Lock mLock = new ReentrantLock();
22 | @SuppressWarnings("ConstantConditions")
23 | final ChainedRef mRunnables = new ChainedRef(mLock, null);
24 |
25 | /**
26 | * 如果该线程没有一个活套,这个处理程序将无法接收消息
27 | * 抛出异常。
28 | */
29 | public WeakHandler() {
30 | mCallback = null;
31 | mExec = new ExecHandler();
32 | }
33 |
34 | /**
35 | * 当前线程并采用回调接口,在该接口中可以处理消息
36 | * @param callback 回调接口,用于处理消息或空.
37 | */
38 | public WeakHandler(Handler.Callback callback) {
39 | mCallback = callback; // Hard referencing body
40 | mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler
41 | }
42 |
43 | /**
44 | * 使用所提供的{Link Looper-}而不是默认的。
45 | *
46 | * @param looper 套环,不能为空。
47 | */
48 | public WeakHandler(Looper looper) {
49 | mCallback = null;
50 | mExec = new ExecHandler(looper);
51 | }
52 |
53 | /**
54 | * 调用回调处理消息的接口
55 | *
56 | * @param looper
57 | * @param callback 回调接口,用于处理消息或空 .
58 | */
59 | public WeakHandler(Looper looper, Handler.Callback callback) {
60 | mCallback = callback;
61 | mExec = new ExecHandler(looper, new WeakReference<>(callback));
62 | }
63 |
64 | /**
65 | * 使可运行R添加到消息队列中。
66 | * @param r
67 | * @return RealRealTrue如果RunnLabess成功放置到消息队列。在失败时返回false,
68 | * 通常是因为活套处理消息队列正在退出。
69 | */
70 | public final boolean post(Runnable r) {
71 | return mExec.post(wrapRunnable(r));
72 | }
73 |
74 | /**
75 | * 回调应该运行的绝对时间,使用时基,RealRealTrue如果RunnLabess成功放置到消息队列。
76 | * 在失败时返回false,通常是因为
77 | * 活套处理消息队列正在退出。注意真的结果并不意味着可运行的将被处理,
78 | * 在消息传递时间之前退出套接字发生,然后消息将被删除。
79 | */
80 | public final boolean postAtTime(Runnable r, long uptimeMillis) {
81 | return mExec.postAtTime(wrapRunnable(r), uptimeMillis);
82 | }
83 |
84 |
85 | public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
86 | return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis);
87 | }
88 |
89 |
90 | public final boolean postDelayed(Runnable r, long delayMillis) {
91 | return mExec.postDelayed(wrapRunnable(r), delayMillis);
92 | }
93 |
94 |
95 | public final boolean postAtFrontOfQueue(Runnable r) {
96 | return mExec.postAtFrontOfQueue(wrapRunnable(r));
97 | }
98 |
99 | /**
100 | * 删除消息队列中的RunnR R的任何挂起的帖子
101 | */
102 | public final void removeCallbacks(Runnable r) {
103 | final WeakRunnable runnable = mRunnables.remove(r);
104 | if (runnable != null) {
105 | mExec.removeCallbacks(runnable);
106 | }
107 | }
108 |
109 |
110 | public final void removeCallbacks(Runnable r, Object token) {
111 | final WeakRunnable runnable = mRunnables.remove(r);
112 | if (runnable != null) {
113 | mExec.removeCallbacks(runnable, token);
114 | }
115 | }
116 |
117 |
118 | public final boolean sendMessage(Message msg) {
119 | return mExec.sendMessage(msg);
120 | }
121 |
122 |
123 | public final boolean sendEmptyMessage(int what) {
124 | return mExec.sendEmptyMessage(what);
125 | }
126 |
127 |
128 | public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
129 | return mExec.sendEmptyMessageDelayed(what, delayMillis);
130 | }
131 |
132 | public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
133 | return mExec.sendEmptyMessageAtTime(what, uptimeMillis);
134 | }
135 |
136 |
137 | public final boolean sendMessageDelayed(Message msg, long delayMillis) {
138 | return mExec.sendMessageDelayed(msg, delayMillis);
139 | }
140 |
141 |
142 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
143 | return mExec.sendMessageAtTime(msg, uptimeMillis);
144 | }
145 |
146 |
147 | public final boolean sendMessageAtFrontOfQueue(Message msg) {
148 | return mExec.sendMessageAtFrontOfQueue(msg);
149 | }
150 |
151 |
152 | public final void removeMessages(int what) {
153 | mExec.removeMessages(what);
154 | }
155 |
156 |
157 | public final void removeMessages(int what, Object object) {
158 | mExec.removeMessages(what, object);
159 | }
160 |
161 |
162 | public final void removeCallbacksAndMessages(Object token) {
163 | mExec.removeCallbacksAndMessages(token);
164 | }
165 |
166 |
167 | public final boolean hasMessages(int what) {
168 | return mExec.hasMessages(what);
169 | }
170 |
171 | /**
172 | *检查是否有任何挂起邮件的帖子“代码”和“什么”,
173 | * 其对象是消息队列中的“对象”
174 | */
175 | public final boolean hasMessages(int what, Object object) {
176 | return mExec.hasMessages(what, object);
177 | }
178 |
179 | public final Looper getLooper() {
180 | return mExec.getLooper();
181 | }
182 |
183 | private WeakRunnable wrapRunnable(Runnable r) {
184 | //noinspection ConstantConditions
185 | if (r == null) {
186 | throw new NullPointerException("Runnable can't be null");
187 | }
188 | final ChainedRef hardRef = new ChainedRef(mLock, r);
189 | mRunnables.insertAfter(hardRef);
190 | return hardRef.wrapper;
191 | }
192 |
193 | private static class ExecHandler extends Handler {
194 | private final WeakReference mCallback;
195 |
196 | ExecHandler() {
197 | mCallback = null;
198 | }
199 |
200 | ExecHandler(WeakReference callback) {
201 | mCallback = callback;
202 | }
203 |
204 | ExecHandler(Looper looper) {
205 | super(looper);
206 | mCallback = null;
207 | }
208 |
209 | ExecHandler(Looper looper, WeakReference callback) {
210 | super(looper);
211 | mCallback = callback;
212 | }
213 |
214 | @Override
215 | public void handleMessage(Message msg) {
216 | if (mCallback == null) {
217 | return;
218 | }
219 | final Callback callback = mCallback.get();
220 | if (callback == null) { // Already disposed
221 | return;
222 | }
223 | callback.handleMessage(msg);
224 | }
225 | }
226 |
227 | static class WeakRunnable implements Runnable {
228 | private final WeakReference mDelegate;
229 | private final WeakReference mReference;
230 |
231 | WeakRunnable(WeakReference delegate, WeakReference reference) {
232 | mDelegate = delegate;
233 | mReference = reference;
234 | }
235 |
236 | @Override
237 | public void run() {
238 | final Runnable delegate = mDelegate.get();
239 | final ChainedRef reference = mReference.get();
240 | if (reference != null) {
241 | reference.remove();
242 | }
243 | if (delegate != null) {
244 | delegate.run();
245 | }
246 | }
247 | }
248 |
249 | static class ChainedRef {
250 | ChainedRef next;
251 | ChainedRef prev;
252 | final Runnable runnable;
253 | final WeakRunnable wrapper;
254 |
255 | Lock lock;
256 |
257 | public ChainedRef(Lock lock, Runnable r) {
258 | this.runnable = r;
259 | this.lock = lock;
260 | this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this));
261 | }
262 |
263 | public WeakRunnable remove() {
264 | lock.lock();
265 | try {
266 | if (prev != null) {
267 | prev.next = next;
268 | }
269 | if (next != null) {
270 | next.prev = prev;
271 | }
272 | prev = null;
273 | next = null;
274 | } finally {
275 | lock.unlock();
276 | }
277 | return wrapper;
278 | }
279 |
280 | public void insertAfter(ChainedRef candidate) {
281 | lock.lock();
282 | try {
283 | if (this.next != null) {
284 | this.next.prev = candidate;
285 | }
286 |
287 | candidate.next = this.next;
288 | this.next = candidate;
289 | candidate.prev = this;
290 | } finally {
291 | lock.unlock();
292 | }
293 | }
294 |
295 | public WeakRunnable remove(Runnable obj) {
296 | lock.lock();
297 | try {
298 | ChainedRef curr = this.next; // Skipping head
299 | while (curr != null) {
300 | if (curr.runnable == obj) { // We do comparison exactly how Handler does inside
301 | return curr.remove();
302 | }
303 | curr = curr.next;
304 | }
305 | } finally {
306 | lock.unlock();
307 | }
308 | return null;
309 | }
310 | }
311 | }
--------------------------------------------------------------------------------
/video_play/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | video_play
3 |
4 |
--------------------------------------------------------------------------------
/video_play/src/test/java/com/test/video_play/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.test.video_play;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------