├── .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 | } --------------------------------------------------------------------------------