├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
└── misc.xml
├── README
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── test
│ │ └── screenimage
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── test
│ │ │ └── screenimage
│ │ │ ├── MyApplication.java
│ │ │ ├── audio
│ │ │ ├── AudioEncoder.java
│ │ │ ├── AudioProcessor.java
│ │ │ └── OnAudioEncodeListener.java
│ │ │ ├── configuration
│ │ │ ├── AudioConfiguration.java
│ │ │ └── VideoConfiguration.java
│ │ │ ├── constant
│ │ │ ├── Constants.java
│ │ │ └── ScreenImageApi.java
│ │ │ ├── controller
│ │ │ ├── StreamController.java
│ │ │ ├── audio
│ │ │ │ ├── IAudioController.java
│ │ │ │ └── NormalAudioController.java
│ │ │ └── video
│ │ │ │ ├── IVideoController.java
│ │ │ │ └── ScreenVideoController.java
│ │ │ ├── core
│ │ │ └── BaseActivity.java
│ │ │ ├── entity
│ │ │ ├── Frame.java
│ │ │ ├── ReceiveData.java
│ │ │ └── ReceiveHeader.java
│ │ │ ├── mediacodec
│ │ │ ├── AudioMediaCodec.java
│ │ │ └── VideoMediaCodec.java
│ │ │ ├── net
│ │ │ ├── OnTcpSendMessageListner.java
│ │ │ ├── RequestTcp.java
│ │ │ ├── TcpUtil.java
│ │ │ └── boastcast
│ │ │ │ └── NetWorkStateReceiver.java
│ │ │ ├── screen
│ │ │ └── ScreenRecordEncoder.java
│ │ │ ├── service
│ │ │ └── ScreenImageService.java
│ │ │ ├── stream
│ │ │ ├── packer
│ │ │ │ ├── AnnexbHelper.java
│ │ │ │ ├── Packer.java
│ │ │ │ └── TcpPacker.java
│ │ │ └── sender
│ │ │ │ ├── OnSenderListener.java
│ │ │ │ ├── Sender.java
│ │ │ │ ├── sendqueue
│ │ │ │ ├── ISendQueue.java
│ │ │ │ ├── NormalSendQueue.java
│ │ │ │ └── SendQueueListener.java
│ │ │ │ ├── tcp
│ │ │ │ ├── EncodeV1.java
│ │ │ │ ├── TcpConnection.java
│ │ │ │ ├── TcpReadThread.java
│ │ │ │ ├── TcpSender.java
│ │ │ │ ├── TcpWriteThread.java
│ │ │ │ └── interf
│ │ │ │ │ ├── OnTcpReadListener.java
│ │ │ │ │ ├── OnTcpWriteListener.java
│ │ │ │ │ └── TcpConnectListener.java
│ │ │ │ └── udp
│ │ │ │ ├── UDPClientThread.java
│ │ │ │ └── interf
│ │ │ │ └── OnUdpConnectListener.java
│ │ │ ├── ui
│ │ │ └── ScreenImageActivity.java
│ │ │ ├── utils
│ │ │ ├── AnalyticDataUtils.java
│ │ │ ├── AudioUtils.java
│ │ │ ├── BatteryUtils.java
│ │ │ ├── BlackListHelper.java
│ │ │ ├── ByteUtil.java
│ │ │ ├── DialogUtils.java
│ │ │ ├── NetWorkUtils.java
│ │ │ ├── PreferenceUtils.java
│ │ │ ├── SopCastLog.java
│ │ │ ├── SopCastUtils.java
│ │ │ ├── StatusBarUtil.java
│ │ │ ├── SupportMultipleScreensUtil.java
│ │ │ ├── ToastUtils.java
│ │ │ └── WeakHandler.java
│ │ │ ├── video
│ │ │ └── OnVideoEncodeListener.java
│ │ │ └── widget
│ │ │ ├── CustomDialog.java
│ │ │ └── LoadingDialog.java
│ └── res
│ │ ├── drawable-nodpi
│ │ ├── ic_client.png
│ │ ├── ic_lamp_close.png
│ │ ├── ic_lamp_open.png
│ │ ├── ic_lamp_press.png
│ │ ├── ic_loading.png
│ │ ├── ic_network.png
│ │ └── ic_server.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── bg_recommend.xml
│ │ ├── dialog_bg.xml
│ │ ├── f_new_image_progress_popbox.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── scan_image.png
│ │ └── select_lamp_icon.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_scan_qr_code.xml
│ │ ├── activity_screen_image.xml
│ │ ├── custom_camera.xml
│ │ ├── loadingdialog.xml
│ │ └── view_alertdialog.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
│ └── screenimage
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.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 |
44 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/README
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ScreenImage
2 | 一对多投屏采集端
3 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | defaultConfig {
6 | applicationId "com.test.screenimage"
7 | minSdkVersion 15
8 | targetSdkVersion 27
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation fileTree(dir: 'libs', include: ['*.jar'])
27 | implementation 'com.android.support:appcompat-v7:27.1.1'
28 | implementation 'com.jakewharton:butterknife:8.8.1'
29 | implementation 'cn.yipianfengye.android:zxing-library:2.2'
30 | implementation 'com.android.support.constraint:constraint-layout:1.1.0'
31 | implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
32 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
33 | implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
34 | implementation 'com.github.skydoves:elasticviews:2.0.0'
35 | implementation 'org.greenrobot:eventbus:3.1.1'
36 | testImplementation 'junit:junit:4.12'
37 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
38 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
39 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
40 | }
41 |
--------------------------------------------------------------------------------
/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/screenimage/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage;
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() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.kedacom.screenimage", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage;
2 |
3 | import android.app.Application;
4 | import android.os.Handler;
5 |
6 | import com.test.screenimage.utils.SupportMultipleScreensUtil;
7 |
8 |
9 | /**
10 | * Created by wt on 2018/5/28.
11 | */
12 |
13 | public class MyApplication extends Application {
14 | public static Handler mHandler;
15 |
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 | //让代码执行在主线程
20 | mHandler = new Handler(getMainLooper());
21 | SupportMultipleScreensUtil.init(this);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/audio/AudioEncoder.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.audio;
2 |
3 | import android.annotation.TargetApi;
4 | import android.media.MediaCodec;
5 |
6 |
7 | import com.test.screenimage.configuration.AudioConfiguration;
8 | import com.test.screenimage.mediacodec.AudioMediaCodec;
9 |
10 | import java.nio.ByteBuffer;
11 |
12 |
13 | @TargetApi(18)
14 | public class AudioEncoder {
15 | private MediaCodec mMediaCodec;
16 | private OnAudioEncodeListener mListener;
17 | private AudioConfiguration mAudioConfiguration;
18 | MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
19 |
20 | public void setOnAudioEncodeListener(OnAudioEncodeListener listener) {
21 | mListener = listener;
22 | }
23 |
24 | public AudioEncoder(AudioConfiguration audioConfiguration) {
25 | mAudioConfiguration = audioConfiguration;
26 | }
27 |
28 | void prepareEncoder() {
29 | mMediaCodec = AudioMediaCodec.getAudioMediaCodec(mAudioConfiguration);
30 | //开始编码
31 | mMediaCodec.start();
32 | }
33 |
34 | synchronized public void stop() {
35 | if (mMediaCodec != null) {
36 | mMediaCodec.stop();
37 | mMediaCodec.release();
38 | mMediaCodec = null;
39 | }
40 | }
41 |
42 | synchronized void offerEncoder(byte[] input) {
43 | if(mMediaCodec == null) {
44 | return;
45 | }
46 | ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
47 | ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
48 | int inputBufferIndex = mMediaCodec.dequeueInputBuffer(12000);
49 | if (inputBufferIndex >= 0) {
50 | ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
51 | inputBuffer.clear();
52 | inputBuffer.put(input);
53 | mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
54 | }
55 |
56 | int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 12000);
57 | while (outputBufferIndex >= 0) {
58 | ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
59 | if(mListener != null) {
60 | mListener.onAudioEncode(outputBuffer, mBufferInfo);
61 | }
62 | mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
63 | outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/audio/AudioProcessor.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.audio;
2 |
3 | import android.media.AudioRecord;
4 |
5 | import com.test.screenimage.configuration.AudioConfiguration;
6 | import com.test.screenimage.utils.AudioUtils;
7 |
8 |
9 | import java.util.Arrays;
10 |
11 |
12 | public class AudioProcessor extends Thread {
13 | private volatile boolean mPauseFlag;
14 | private volatile boolean mStopFlag;
15 | private volatile boolean mMute;
16 | private AudioRecord mAudioRecord;
17 | private AudioEncoder mAudioEncoder;
18 | private byte[] mRecordBuffer;
19 | private int mRecordBufferSize;
20 |
21 | public AudioProcessor(AudioRecord audioRecord, AudioConfiguration audioConfiguration) {
22 | mRecordBufferSize = AudioUtils.getRecordBufferSize(audioConfiguration);
23 | mRecordBuffer = new byte[mRecordBufferSize];
24 | mAudioRecord = audioRecord;
25 | mAudioEncoder = new AudioEncoder(audioConfiguration);
26 | mAudioEncoder.prepareEncoder();
27 | }
28 |
29 | public void setAudioHEncodeListener(OnAudioEncodeListener listener) {
30 | mAudioEncoder.setOnAudioEncodeListener(listener);
31 | }
32 |
33 | public void stopEncode() {
34 | mStopFlag = true;
35 | if(mAudioEncoder != null) {
36 | mAudioEncoder.stop();
37 | mAudioEncoder = null;
38 | }
39 | }
40 |
41 | public void pauseEncode(boolean pause) {
42 | mPauseFlag = pause;
43 | }
44 |
45 | public void setMute(boolean mute) {
46 | mMute = mute;
47 | }
48 |
49 | public void run() {
50 | while (!mStopFlag) {
51 | while (mPauseFlag) {
52 | try {
53 | Thread.sleep(1000);
54 | } catch (InterruptedException e) {
55 | e.printStackTrace();
56 | }
57 | }
58 | int readLen = mAudioRecord.read(mRecordBuffer, 0, mRecordBufferSize);
59 | if (readLen > 0) {
60 | if (mMute) {
61 | byte clearM = 0;
62 | Arrays.fill(mRecordBuffer, clearM);
63 | }
64 | if(mAudioEncoder != null) {
65 | mAudioEncoder.offerEncoder(mRecordBuffer);
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/audio/OnAudioEncodeListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.audio;
2 |
3 | import android.media.MediaCodec;
4 |
5 | import java.nio.ByteBuffer;
6 |
7 |
8 | public interface OnAudioEncodeListener {
9 | void onAudioEncode(ByteBuffer bb, MediaCodec.BufferInfo bi);
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/configuration/AudioConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.configuration;
2 |
3 | import android.media.AudioFormat;
4 | import android.media.AudioRecord;
5 | import android.media.MediaCodecInfo;
6 | import android.util.Log;
7 |
8 | public final class AudioConfiguration {
9 | //采样率
10 | public static final int DEFAULT_FREQUENCY = 44100;
11 | // public static final int DEFAULT_FREQUENCY = 8000;
12 | public static final int DEFAULT_MAX_BPS = 64;
13 | public static final int DEFAULT_MIN_BPS = 32;
14 | public static final int DEFAULT_ADTS = 0;
15 | public static final String DEFAULT_MIME = "audio/mp4a-latm";
16 | //采样位数(定义音频编码(16位))
17 | //设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实
18 | public static final int DEFAULT_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
19 | public static final int DEFAULT_AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC;
20 | public static final int DEFAULT_CHANNEL_COUNT = 2;
21 | public static final boolean DEFAULT_AEC = true;
22 |
23 | public final int minBps;
24 | public final int maxBps;
25 | public final int frequency;
26 | public final int encoding;
27 | public final int channelCount;
28 | public final int adts;
29 | public final int aacProfile;
30 | public final String mime;
31 | public final boolean aec;
32 |
33 | private AudioConfiguration(final Builder builder) {
34 | minBps = builder.minBps;
35 | maxBps = builder.maxBps;
36 | frequency = builder.frequency;
37 | encoding = builder.encoding;
38 | channelCount = builder.channelCount;
39 | adts = builder.adts;
40 | mime = builder.mime;
41 | aacProfile = builder.aacProfile;
42 | aec = builder.aec;
43 | Log.e("wtt", "AudioConfiguration: "+frequency );
44 | }
45 |
46 | public static AudioConfiguration createDefault() {
47 | return new Builder().build();
48 | }
49 |
50 | public static class Builder {
51 | private int minBps = DEFAULT_MIN_BPS;
52 | private int maxBps = DEFAULT_MAX_BPS;
53 | private int frequency = DEFAULT_FREQUENCY;
54 | private int encoding = DEFAULT_AUDIO_ENCODING;
55 | private int channelCount = DEFAULT_CHANNEL_COUNT;
56 | private int adts = DEFAULT_ADTS;
57 | private String mime = DEFAULT_MIME;
58 | private int aacProfile = DEFAULT_AAC_PROFILE;
59 | private boolean aec = DEFAULT_AEC;
60 |
61 | public Builder setBps(int minBps, int maxBps) {
62 | this.minBps = minBps;
63 | this.maxBps = maxBps;
64 | return this;
65 | }
66 |
67 | public Builder setFrequency(int frequency) {
68 | this.frequency = frequency;
69 | return this;
70 | }
71 |
72 | public Builder setEncoding(int encoding) {
73 | this.encoding = encoding;
74 | return this;
75 | }
76 |
77 | public Builder setChannelCount(int channelCount) {
78 | this.channelCount = channelCount;
79 | return this;
80 | }
81 |
82 | public Builder setAdts(int adts) {
83 | this.adts = adts;
84 | return this;
85 | }
86 |
87 | public Builder setAacProfile(int aacProfile) {
88 | this.aacProfile = aacProfile;
89 | return this;
90 | }
91 |
92 | public Builder setMime(String mime) {
93 | this.mime = mime;
94 | return this;
95 | }
96 |
97 | public Builder setAec(boolean aec) {
98 | this.aec = aec;
99 | return this;
100 | }
101 |
102 | public AudioConfiguration build() {
103 | return new AudioConfiguration(this);
104 | }
105 |
106 | }
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/configuration/VideoConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.configuration;
2 |
3 |
4 | public final class VideoConfiguration {
5 | // TODO modify by xu.wang
6 | //--------------------默认标清时的设置参数--------------------------------
7 | public static final int DEFAULT_HEIGHT = 640;
8 | public static final int DEFAULT_WIDTH = 360;
9 | public static final int DEFAULT_FPS = 24;
10 | public static final int DEFAULT_MAX_BPS = 1500;
11 | public static final int DEFAULT_MIN_BPS = 400;
12 | public static final int DEFAULT_IFI = 1; //关键帧间隔时间 单位s
13 | public static final String DEFAULT_MIME = "video/avc";
14 | // //--------------------默认标清时的设置参数--------------------------------
15 | // public static final int SECOND_HEIGHT = 640;
16 | // public static final int SECOND_WIDTH = 360;
17 | // public static final int SECOND_FPS = 10;
18 | // public static final int SECOND_MAX_BPS = 600;
19 | // public static final int SECOND_MIN_BPS = 300;
20 | // public static final int SECOND_IFI = 3;
21 | // public static final String SECOND_MIME = "video/avc";
22 |
23 | public int height;
24 | public int width;
25 | public final int minBps;
26 | public int maxBps;
27 | public int fps;
28 | public int ifi;
29 | public final String mime;
30 |
31 | private VideoConfiguration(final Builder builder) {
32 | height = builder.height;
33 | width = builder.width;
34 | minBps = builder.minBps;
35 | maxBps = builder.maxBps;
36 | fps = builder.fps;
37 | ifi = builder.ifi;
38 | mime = builder.mime;
39 | }
40 |
41 | // private VideoConfiguration(final SecondBuilder builder) {
42 | // height = builder.height;
43 | // width = builder.width;
44 | // minBps = builder.minBps;
45 | // maxBps = builder.maxBps;
46 | // fps = builder.fps;
47 | // ifi = builder.ifi;
48 | // mime = builder.mime;
49 | // }
50 |
51 | public static VideoConfiguration createDefault() {
52 | return new Builder().build();
53 | }
54 |
55 |
56 | public static class Builder {
57 | private int height = DEFAULT_HEIGHT;
58 | private int width = DEFAULT_WIDTH;
59 | private int minBps = DEFAULT_MIN_BPS;
60 | private int maxBps = DEFAULT_MAX_BPS;
61 | private int fps = DEFAULT_FPS;
62 | private int ifi = DEFAULT_IFI;
63 | private String mime = DEFAULT_MIME;
64 |
65 | public Builder setSize(int width, int height) {
66 | this.width = width;
67 | this.height = height;
68 | return this;
69 | }
70 |
71 | public Builder setBps(int minBps, int maxBps) {
72 | this.minBps = minBps;
73 | this.maxBps = maxBps;
74 | return this;
75 | }
76 |
77 | public Builder setFps(int fps) {
78 | this.fps = fps;
79 | return this;
80 | }
81 |
82 | public Builder setIfi(int ifi) {
83 | this.ifi = ifi;
84 | return this;
85 | }
86 |
87 | public Builder setMime(String mime) {
88 | this.mime = mime;
89 | return this;
90 | }
91 |
92 | public VideoConfiguration build() {
93 | return new VideoConfiguration(this);
94 | }
95 | }
96 | //
97 | // public static class SecondBuilder {
98 | // private int height = SECOND_HEIGHT;
99 | // private int width = SECOND_WIDTH;
100 | // private int minBps = SECOND_MIN_BPS;
101 | // private int maxBps = SECOND_MAX_BPS;
102 | // private int fps = SECOND_FPS;
103 | // private int ifi = SECOND_IFI;
104 | // private String mime = SECOND_MIME;
105 | //
106 | // public VideoConfiguration build() {
107 | // return new VideoConfiguration(this);
108 | // }
109 | // }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/constant/Constants.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.constant;
2 |
3 | /**
4 | * Created by wt on 2018/5/28.
5 | */
6 | public class Constants {
7 | public static final String TAG = "wtt";
8 | public static String PHONEIP = "phoneIp";
9 | public static String PCIP = "pcIp";
10 | public static long MASK = 0;
11 | public static int WIDTH=360;
12 | public static int HEIGHT=640;
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/constant/ScreenImageApi.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.constant;
2 |
3 | /**
4 | * Created by wt on 2018/6/11.
5 | * 设备常用配置
6 | */
7 | public class ScreenImageApi {
8 |
9 | public static final byte encodeVersion1 = 0x00; //版本号1
10 |
11 | public class RECORD { //录屏指令
12 | public static final int MAIN_CMD = 0xA2; //录屏主指令
13 | public static final short RECORDER_REQUEST_START = 0x01; //投屏,请求开始
14 | public static final int Command_HostUpdated = 0x1C; //{IP}:{Name}|{IP}:{Name}…投屏者信息
15 | public static final int DATE_PALY = 0x01;//音视频解析播放
16 | }
17 |
18 | public class SERVER {//服务端与客户端交互指令
19 | public static final int MAIN_CMD = 0xA0; //投屏回传主指令
20 | public static final int INITIAL_SUCCESS = 0x01;//服务器初始化成功
21 | }
22 |
23 | public class LOGIC_REQUEST { //客户端向服务器请求的指令
24 | public static final int MAIN_CMD = 112;
25 | public static final int GET_START_INFO = 1; //传输开始投屏需要的关键信息 例 1920,1080(长,宽)
26 | }
27 |
28 | public class LOGIC_REPONSE { //返回给客户端的指令
29 | public static final int MAIN_CMD = 111;
30 |
31 | public static final int GET_START_INFO = 1; //传输开始投屏需要的关键信息 例 1920,1080(长,宽)
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/controller/StreamController.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.controller;
2 |
3 | import android.media.MediaCodec;
4 |
5 | import com.test.screenimage.audio.OnAudioEncodeListener;
6 | import com.test.screenimage.configuration.AudioConfiguration;
7 | import com.test.screenimage.configuration.VideoConfiguration;
8 | import com.test.screenimage.controller.audio.IAudioController;
9 | import com.test.screenimage.controller.video.IVideoController;
10 | import com.test.screenimage.stream.packer.Packer;
11 | import com.test.screenimage.stream.sender.Sender;
12 | import com.test.screenimage.stream.sender.tcp.TcpSender;
13 | import com.test.screenimage.utils.SopCastUtils;
14 | import com.test.screenimage.video.OnVideoEncodeListener;
15 |
16 | import java.nio.ByteBuffer;
17 |
18 | public class StreamController implements OnAudioEncodeListener, OnVideoEncodeListener, Packer.OnPacketListener {
19 | private Packer mPacker;
20 | private Sender mSender;
21 | private IVideoController mVideoController;
22 | private IAudioController mAudioController;
23 |
24 | public StreamController(IVideoController videoProcessor, IAudioController audioProcessor) {
25 | mAudioController = audioProcessor;
26 | mVideoController = videoProcessor;
27 | }
28 |
29 | public void setVideoConfiguration(VideoConfiguration videoConfiguration) {
30 | mVideoController.setVideoConfiguration(videoConfiguration);
31 | }
32 |
33 |
34 | public void setPacker(Packer packer) {
35 | mPacker = packer;
36 | //设置打包监听器
37 | mPacker.setPacketListener(this);
38 | }
39 |
40 | public void setSender(Sender sender) {
41 | mSender = sender;
42 | }
43 |
44 | public synchronized void start() {
45 | SopCastUtils.processNotUI(new SopCastUtils.INotUIProcessor() {
46 | @Override
47 | public void process() {
48 | if (mPacker == null) {
49 | return;
50 | }
51 | if (mSender == null) {
52 | return;
53 | }
54 | //开始打包
55 | mPacker.start();
56 | //开始发送
57 | mSender.start();
58 | mVideoController.setVideoEncoderListener(StreamController.this);
59 | mVideoController.start();
60 | if (mAudioController != null) {
61 | mAudioController.setAudioEncodeListener(StreamController.this);
62 | mAudioController.start();
63 | }
64 | }
65 | });
66 | }
67 |
68 | public synchronized void stop() {
69 | SopCastUtils.processNotUI(new SopCastUtils.INotUIProcessor() {
70 | @Override
71 | public void process() {
72 | if (mAudioController != null) {
73 | mAudioController.setAudioEncodeListener(null);
74 | mAudioController.stop();
75 | }
76 | if (mVideoController != null) {
77 | mVideoController.setVideoEncoderListener(null);
78 | mVideoController.stop();
79 | }
80 | if (mSender != null) {
81 | mSender.stop();
82 | }
83 | if (mPacker != null) {
84 | mPacker.stop();
85 | }
86 | }
87 | });
88 | }
89 |
90 | public synchronized void pause() {
91 | SopCastUtils.processNotUI(new SopCastUtils.INotUIProcessor() {
92 | @Override
93 | public void process() {
94 | if (mAudioController != null) mAudioController.pause();
95 | mVideoController.pause();
96 | }
97 | });
98 | }
99 |
100 | public synchronized void resume() {
101 | SopCastUtils.processNotUI(new SopCastUtils.INotUIProcessor() {
102 | @Override
103 | public void process() {
104 | if (mAudioController != null) mAudioController.resume();
105 | mVideoController.resume();
106 | }
107 | });
108 | }
109 |
110 | public void mute(boolean mute) {
111 | if (mAudioController != null) mAudioController.mute(mute);
112 | }
113 |
114 | public int getSessionId() {
115 | return mAudioController == null ? 0 : mAudioController.getSessionId();
116 | }
117 |
118 | public boolean setVideoBps(int bps) {
119 | return mVideoController.setVideoBps(bps);
120 | }
121 |
122 | @Override
123 | public void onAudioEncode(ByteBuffer bb, MediaCodec.BufferInfo bi) {
124 | if (mPacker != null) {
125 | mPacker.onAudioData(bb, bi);
126 | }
127 | }
128 |
129 | @Override
130 | public void onVideoEncode(ByteBuffer bb, MediaCodec.BufferInfo bi) {
131 | if (mPacker != null) {
132 | mPacker.onVideoData(bb, bi);
133 | }
134 | }
135 |
136 | @Override
137 | public void onPacket(byte[] data, int packetType) {
138 | if (mSender != null) {
139 | mSender.onData(data, packetType);
140 | }
141 | }
142 |
143 | @Override
144 | public void onSpsPps(byte[] mSpsPps) {
145 | if (mSender != null && mSender instanceof TcpSender) {
146 | ((TcpSender) mSender).setSpsPps(mSpsPps);
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/controller/audio/IAudioController.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.controller.audio;
2 |
3 | import com.test.screenimage.audio.OnAudioEncodeListener;
4 | import com.test.screenimage.configuration.AudioConfiguration;
5 |
6 | public interface IAudioController {
7 | void start();
8 | void stop();
9 | void pause();
10 | void resume();
11 | void mute(boolean mute);
12 | int getSessionId();
13 | void setAudioEncodeListener(OnAudioEncodeListener listener);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/controller/audio/NormalAudioController.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.controller.audio;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.media.AudioManager;
6 | import android.media.AudioRecord;
7 | import android.util.Log;
8 |
9 | import com.test.screenimage.audio.AudioProcessor;
10 | import com.test.screenimage.audio.OnAudioEncodeListener;
11 | import com.test.screenimage.configuration.AudioConfiguration;
12 | import com.test.screenimage.constant.Constants;
13 | import com.test.screenimage.utils.AudioUtils;
14 | import com.test.screenimage.utils.SopCastLog;
15 |
16 | public class NormalAudioController implements IAudioController {
17 | private OnAudioEncodeListener mListener;
18 | private AudioRecord mAudioRecord;
19 | private AudioProcessor mAudioProcessor;
20 | // TODO: 2018/6/5 增加上下文
21 | private Context mContext;
22 | private boolean mMute;
23 | private AudioConfiguration mAudioConfiguration;
24 |
25 | public NormalAudioController(Context context) {
26 | this.mContext=context;
27 | mAudioConfiguration = AudioConfiguration.createDefault();
28 | }
29 |
30 | public void setAudioConfiguration(AudioConfiguration audioConfiguration) {
31 | mAudioConfiguration = audioConfiguration;
32 | }
33 |
34 | public void setAudioEncodeListener(OnAudioEncodeListener listener) {
35 | mListener = listener;
36 | }
37 |
38 | public void start() {
39 | SopCastLog.d(Constants.TAG, "Audio Recording start");
40 | mAudioRecord = AudioUtils.getAudioRecord(mAudioConfiguration,mContext);
41 | try {
42 | //音频录制
43 | mAudioRecord.startRecording();
44 | } catch (Exception e) {
45 | e.printStackTrace();
46 | }
47 | mAudioProcessor = new AudioProcessor(mAudioRecord, mAudioConfiguration);
48 | mAudioProcessor.setAudioHEncodeListener(mListener);
49 | mAudioProcessor.start();
50 | mAudioProcessor.setMute(mMute);
51 | }
52 |
53 | public void stop() {
54 | SopCastLog.d(Constants.TAG, "Audio Recording stop");
55 | if(mAudioProcessor != null) {
56 | mAudioProcessor.stopEncode();
57 | }
58 | if(mAudioRecord != null) {
59 | try {
60 | mAudioRecord.stop();
61 | mAudioRecord.release();
62 | mAudioRecord = null;
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 | }
66 | }
67 | }
68 |
69 | public void pause() {
70 | SopCastLog.d(Constants.TAG, "Audio Recording pause");
71 | try {
72 | if (mAudioRecord != null) {
73 | mAudioRecord.stop();
74 | }
75 | if (mAudioProcessor != null) {
76 | mAudioProcessor.pauseEncode(true);
77 | }
78 | }catch (Exception e){
79 | Log.e("NormalAudioController","" + e.toString());
80 | }
81 | }
82 |
83 | public void resume() {
84 | SopCastLog.d(Constants.TAG, "Audio Recording resume");
85 | if(mAudioRecord != null) {
86 | mAudioRecord.startRecording();
87 | }
88 | if (mAudioProcessor != null) {
89 | mAudioProcessor.pauseEncode(false);
90 | }
91 | }
92 |
93 | public void mute(boolean mute) {
94 | SopCastLog.d(Constants.TAG, "Audio Recording mute: " + mute);
95 | mMute = mute;
96 | if(mAudioProcessor != null) {
97 | mAudioProcessor.setMute(mMute);
98 | }
99 | }
100 |
101 | @Override
102 | @TargetApi(16)
103 | public int getSessionId() {
104 | if(mAudioRecord != null) {
105 | return mAudioRecord.getAudioSessionId();
106 | } else {
107 | return -1;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/controller/video/IVideoController.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.controller.video;
2 |
3 | import com.test.screenimage.configuration.VideoConfiguration;
4 | import com.test.screenimage.video.OnVideoEncodeListener;
5 |
6 | public interface IVideoController {
7 | void start();
8 | void stop();
9 | void pause();
10 | void resume();
11 | boolean setVideoBps(int bps);
12 | void setVideoEncoderListener(OnVideoEncodeListener listener);
13 | void setVideoConfiguration(VideoConfiguration configuration);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/controller/video/ScreenVideoController.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.controller.video;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Intent;
5 | import android.hardware.display.DisplayManager;
6 | import android.hardware.display.VirtualDisplay;
7 | import android.media.projection.MediaProjection;
8 | import android.media.projection.MediaProjectionManager;
9 | import android.os.Build;
10 | import android.view.Surface;
11 |
12 | import com.test.screenimage.configuration.VideoConfiguration;
13 | import com.test.screenimage.constant.Constants;
14 | import com.test.screenimage.mediacodec.VideoMediaCodec;
15 | import com.test.screenimage.screen.ScreenRecordEncoder;
16 | import com.test.screenimage.utils.SopCastLog;
17 | import com.test.screenimage.video.OnVideoEncodeListener;
18 |
19 |
20 | import static android.os.Build.VERSION_CODES.LOLLIPOP;
21 |
22 |
23 | @TargetApi(LOLLIPOP)
24 | public class ScreenVideoController implements IVideoController {
25 | private MediaProjectionManager mManager;
26 | private int resultCode;
27 | private Intent resultData;
28 | private VirtualDisplay mVirtualDisplay;
29 | private MediaProjection mMediaProjection;
30 | private VideoConfiguration mVideoConfiguration = VideoConfiguration.createDefault();
31 | private ScreenRecordEncoder mEncoder;
32 | private OnVideoEncodeListener mListener;
33 |
34 | public ScreenVideoController(MediaProjectionManager manager, int resultCode, Intent resultData) {
35 | mManager = manager;
36 | this.resultCode = resultCode;
37 | this.resultData = resultData;
38 | }
39 |
40 | @Override
41 | public void start() {
42 | //关于屏幕采集
43 | mEncoder = new ScreenRecordEncoder(mVideoConfiguration);
44 | final Surface surface = mEncoder.getSurface();
45 | mEncoder.start();
46 | mEncoder.setOnVideoEncodeListener(mListener);
47 | mMediaProjection = mManager.getMediaProjection(resultCode, resultData);
48 | int width = VideoMediaCodec.getVideoSize(mVideoConfiguration.width);
49 | int height = VideoMediaCodec.getVideoSize(mVideoConfiguration.height);
50 | //实例化VirtualDisplay,这个类的主要作用是用来获取屏幕信息并保存在里。
51 | mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenRecoder",
52 | width, height, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface,
53 | null, null);
54 | }
55 |
56 | @Override
57 | public void stop() {
58 | if (mEncoder != null) {
59 | mEncoder.setOnVideoEncodeListener(null);
60 | mEncoder.stop();
61 | mEncoder = null;
62 | }
63 | if (mMediaProjection != null) {
64 | mMediaProjection.stop();
65 | mMediaProjection = null;
66 | }
67 | if (mVirtualDisplay != null) {
68 | mVirtualDisplay.release();
69 | mVirtualDisplay = null;
70 | }
71 | }
72 |
73 | @Override
74 | public void pause() {
75 | if (mEncoder != null) {
76 | mEncoder.setPause(true);
77 | }
78 | }
79 |
80 | @Override
81 | public void resume() {
82 | if (mEncoder != null) {
83 | mEncoder.setPause(false);
84 | }
85 | }
86 |
87 | @Override
88 | public boolean setVideoBps(int bps) {
89 | //重新设置硬编bps,在低于19的版本需要重启编码器
90 | boolean result = false;
91 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
92 | //由于重启硬编编码器效果不好,此次不做处理
93 | SopCastLog.d(Constants.TAG, "Bps need change, but MediaCodec do not support.");
94 | } else {
95 | if (mEncoder != null) {
96 | SopCastLog.d(Constants.TAG, "Bps change, current bps: " + bps);
97 | mEncoder.setRecorderBps(bps);
98 | result = true;
99 | }
100 | }
101 | return result;
102 | }
103 |
104 | @Override
105 | public void setVideoEncoderListener(OnVideoEncodeListener listener) {
106 | mListener = listener;
107 | }
108 |
109 | @Override
110 | public void setVideoConfiguration(VideoConfiguration configuration) {
111 | mVideoConfiguration = configuration;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/core/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.core;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 |
8 | import com.test.screenimage.utils.StatusBarUtil;
9 | import com.test.screenimage.utils.SupportMultipleScreensUtil;
10 | import com.test.screenimage.utils.ToastUtils;
11 |
12 | import butterknife.ButterKnife;
13 |
14 |
15 | /**
16 | * Created by wt on 2018/5/28.
17 | */
18 | public abstract class BaseActivity extends AppCompatActivity implements View.OnClickListener {
19 | private static long lastTimeStamp = 0l;
20 |
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | if (getLayoutId() != 0) {
26 | StatusBarUtil.transparencyBar(this);
27 | StatusBarUtil.StatusBarLightMode(this);
28 | setContentView(getLayoutId());
29 | View rootView = findViewById(android.R.id.content);
30 | SupportMultipleScreensUtil.scale(rootView);
31 | ButterKnife.bind(this);
32 | }
33 | initView();
34 | initData();
35 | }
36 |
37 | protected abstract void initView();
38 |
39 | protected abstract void initData();
40 |
41 | protected int getLayoutId() {
42 | return 0;
43 | }
44 |
45 |
46 | @Override
47 | protected void onDestroy() {
48 | super.onDestroy();
49 | }
50 |
51 | @Override
52 | public void onClick(View v) {
53 |
54 | }
55 |
56 | /**
57 | * 退出程序.
58 | *
59 | * @param context
60 | */
61 | public static void exitApplication(Activity context) {
62 | long currentTimeStamp = System.currentTimeMillis();
63 | if (currentTimeStamp - lastTimeStamp > 1350L) {
64 | ToastUtils.showToast(context, "再按一次退出");
65 | } else {
66 | context.finish();
67 | }
68 | lastTimeStamp = currentTimeStamp;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/entity/Frame.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.entity;
2 |
3 | // TODO: 2018/5/29 wt修改Frame
4 | public class Frame {
5 | public static final int FRAME_TYPE_AUDIO = 1;
6 | public static final int FRAME_TYPE_KEY_FRAME = 2;
7 | public static final int FRAME_TYPE_INTER_FRAME = 3;
8 | public static final int FRAME_TYPE_CONFIGURATION = 4;
9 |
10 | // public T data;
11 | public byte[] data;
12 | public int packetType;
13 | public int frameType;
14 |
15 | public Frame(byte[] data, int packetType, int frameType) {
16 | this.data = data;
17 | this.packetType = packetType;
18 | this.frameType = frameType;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/entity/ReceiveData.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.entity;
2 |
3 | /**
4 | * Created by wt on 2018/6/12.
5 | * 解析后的一组数据
6 | */
7 | public class ReceiveData {
8 | private ReceiveHeader header;
9 | private String sendBody;
10 | private byte[] buff;
11 |
12 | public ReceiveData() {
13 | }
14 |
15 | /**
16 | * @param header 协议头
17 | * @param sendBody 文本内容
18 | * @param buff 数组内容
19 | */
20 | public ReceiveData(ReceiveHeader header, String sendBody, byte[] buff) {
21 | this.header = header;
22 | this.sendBody = sendBody;
23 | this.buff = buff;
24 | }
25 |
26 | public ReceiveHeader getHeader() {
27 | return header;
28 | }
29 |
30 | public void setHeader(ReceiveHeader header) {
31 | this.header = header;
32 | }
33 |
34 | public String getSendBody() {
35 | return sendBody;
36 | }
37 |
38 | public void setSendBody(String sendBody) {
39 | this.sendBody = sendBody;
40 | }
41 |
42 | public byte[] getBuff() {
43 | return buff;
44 | }
45 |
46 | public void setBuff(byte[] buff) {
47 | this.buff = buff;
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/entity/ReceiveHeader.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.entity;
2 |
3 | /**
4 | * Created by wt on 2018/6/12.
5 | * 解析出协议数据头格式
6 | * mainCmd 主指令
7 | * subCmd 子指令
8 | * encodeVersion 编码版本
9 | * stringBodylength 文本消息内容长度
10 | * buffSize 音视频内容长度
11 | */
12 | public class ReceiveHeader {
13 | private int mainCmd;
14 | private int subCmd;
15 | private byte encodeVersion;
16 | private int stringBodylength;
17 | private int buffSize;
18 |
19 | public ReceiveHeader(int mainCmd, int subCmd, byte encodeVersion, int stringBodylength, int buffSize) {
20 | this.mainCmd = mainCmd;
21 | this.subCmd = subCmd;
22 | this.encodeVersion = encodeVersion;
23 | this.stringBodylength = stringBodylength;
24 | this.buffSize = buffSize;
25 | }
26 |
27 | public int getMainCmd() {
28 | return mainCmd;
29 | }
30 |
31 | public void setMainCmd(int mainCmd) {
32 | this.mainCmd = mainCmd;
33 | }
34 |
35 | public int getSubCmd() {
36 | return subCmd;
37 | }
38 |
39 | public void setSubCmd(int subCmd) {
40 | this.subCmd = subCmd;
41 | }
42 |
43 | public byte getEncodeVersion() {
44 | return encodeVersion;
45 | }
46 |
47 | public void setEncodeVersion(byte encodeVersion) {
48 | this.encodeVersion = encodeVersion;
49 | }
50 |
51 | public int getStringBodylength() {
52 | return stringBodylength;
53 | }
54 |
55 | public void setStringBodylength(int stringBodylength) {
56 | this.stringBodylength = stringBodylength;
57 | }
58 |
59 | public int getBuffSize() {
60 | return buffSize;
61 | }
62 |
63 | public void setBuffSize(int buffSize) {
64 | this.buffSize = buffSize;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/mediacodec/AudioMediaCodec.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.mediacodec;
2 |
3 | import android.annotation.TargetApi;
4 | import android.media.MediaCodec;
5 | import android.media.MediaFormat;
6 |
7 | import com.test.screenimage.configuration.AudioConfiguration;
8 | import com.test.screenimage.utils.AudioUtils;
9 |
10 |
11 | @TargetApi(18)
12 | public class AudioMediaCodec {
13 | //pcm转成aac
14 | public static MediaCodec getAudioMediaCodec(AudioConfiguration configuration) {
15 | //设置音频编码格式,获取编码器实例
16 | //初始化 此格式使用的音频编码技术、音频采样率、使用此格式的音频信道数(单声道为 1,立体声为 2)
17 | MediaFormat format = MediaFormat.createAudioFormat(configuration.mime,
18 | configuration.frequency, configuration.channelCount);
19 | if (configuration.mime.equals(AudioConfiguration.DEFAULT_MIME)) {
20 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, configuration.aacProfile);
21 | }
22 | //比特率 声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量
23 | // 是间接衡量音频质量的一个指标
24 | format.setInteger(MediaFormat.KEY_BIT_RATE, configuration.maxBps * 1024);
25 | //设置采样率
26 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, configuration.frequency);
27 | int maxInputSize = AudioUtils.getRecordBufferSize(configuration);
28 | //传入的数据大小
29 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
30 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, configuration.channelCount);
31 |
32 | MediaCodec mediaCodec = null;
33 | try {
34 | mediaCodec = MediaCodec.createEncoderByType(configuration.mime);
35 | //设置相关参数
36 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | if (mediaCodec != null) {
40 | mediaCodec.stop();
41 | mediaCodec.release();
42 | mediaCodec = null;
43 | }
44 | }
45 | return mediaCodec;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/mediacodec/VideoMediaCodec.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.mediacodec;
2 |
3 | import android.annotation.TargetApi;
4 | import android.media.MediaCodec;
5 | import android.media.MediaCodecInfo;
6 | import android.media.MediaFormat;
7 | import android.os.Build;
8 | import android.util.Log;
9 |
10 | import com.test.screenimage.configuration.VideoConfiguration;
11 | import com.test.screenimage.constant.Constants;
12 | import com.test.screenimage.utils.BlackListHelper;
13 | import com.test.screenimage.utils.SopCastLog;
14 |
15 |
16 | @TargetApi(18)
17 | public class VideoMediaCodec {
18 |
19 | public static MediaCodec getVideoMediaCodec(VideoConfiguration videoConfiguration) {
20 | int videoWidth = getVideoSize(videoConfiguration.width);
21 | int videoHeight = getVideoSize(videoConfiguration.height);
22 | // if (Build.MANUFACTURER.equalsIgnoreCase("XIAOMI")) {
23 | // videoConfiguration.maxBps = 1500;
24 | // videoConfiguration.fps = 10;
25 | // videoConfiguration.ifi = 3;
26 | // }
27 | MediaFormat format = MediaFormat.createVideoFormat(videoConfiguration.mime, videoWidth, videoHeight);
28 | //设置颜色格式
29 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
30 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
31 | //设置比特率(设置码率,通常码率越高,视频越清晰)
32 | format.setInteger(MediaFormat.KEY_BIT_RATE, videoConfiguration.maxBps * 1024);
33 | int fps = videoConfiguration.fps;
34 | //设置摄像头预览帧率
35 | if (BlackListHelper.deviceInFpsBlacklisted()) {
36 | SopCastLog.d(Constants.TAG, "Device in fps setting black list, so set mediacodec fps 15");
37 | fps = 15;
38 | }
39 | //设置帧率
40 | format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
41 | //关键帧间隔时间,通常情况下,你设置成多少问题都不大。
42 | //比如你设置成10,那就是10秒一个关键帧。但是,如果你有需求要做视频的预览,那你最好设置成1
43 | //因为如果你设置成10,那你会发现,10秒内的预览都是一个截图
44 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, videoConfiguration.ifi);
45 | // -----------------ADD BY XU.WANG 当画面静止时,重复最后一帧--------------------------------------------------------
46 | format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 45);
47 | //------------------MODIFY BY XU.WANG 为解决MIUI9.5花屏而增加...-------------------------------
48 | if (Build.MANUFACTURER.equalsIgnoreCase("XIAOMI")) {
49 | //设置编码模式
50 | format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);
51 | } else {
52 | format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
53 | }
54 | //设置复用模式
55 | format.setInteger(MediaFormat.KEY_COMPLEXITY, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
56 | MediaCodec mediaCodec = null;
57 |
58 | try {
59 | mediaCodec = MediaCodec.createEncoderByType(videoConfiguration.mime);
60 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
61 | } catch (Exception e) {
62 | e.printStackTrace();
63 | if (mediaCodec != null) {
64 | mediaCodec.stop();
65 | mediaCodec.release();
66 | mediaCodec = null;
67 | }
68 | }
69 | return mediaCodec;
70 | }
71 |
72 | // We avoid the device-specific limitations on width and height by using values that
73 | // are multiples of 16, which all tested devices seem to be able to handle.
74 | public static int getVideoSize(int size) {
75 | int multiple = (int) Math.ceil(size / 16.0);
76 | return multiple * 16;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/net/OnTcpSendMessageListner.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.net;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * Created by wt
7 | * Date on 2018/7/4
8 | */
9 | public interface OnTcpSendMessageListner {
10 | void success(int mainCmd, int subCmd, String body, byte[] bytes);
11 | void error(Exception e);
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/net/RequestTcp.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.net;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.screenimage.MyApplication;
6 | import com.test.screenimage.entity.ReceiveData;
7 | import com.test.screenimage.entity.ReceiveHeader;
8 | import com.test.screenimage.stream.sender.tcp.EncodeV1;
9 | import com.test.screenimage.utils.AnalyticDataUtils;
10 |
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.io.OutputStream;
14 | import java.net.InetSocketAddress;
15 | import java.net.Socket;
16 | import java.net.SocketAddress;
17 | import java.net.SocketException;
18 |
19 | /**
20 | * Created by wt
21 | * Date on 2018/7/4
22 | *
23 | * @Desc
24 | */
25 |
26 | public class RequestTcp extends Thread {
27 | private int mainCmd;
28 | private int subCmd;
29 | private byte[] bytes;
30 | private String ip;
31 | private int port;
32 | private String sendBody;
33 | private Socket mSocket;
34 | private AnalyticDataUtils mAnalyticDataUtils;
35 | private OnTcpSendMessageListner mListener;
36 | private int connectSoTime;
37 | private OutputStream outputStream;
38 | private InputStream inputStream;
39 |
40 | public RequestTcp(String ip, int port, int mainCmd, int subCmd, String sendBody, byte[] bytes,
41 | int connectSoTime, OnTcpSendMessageListner listener) {
42 | this.ip = ip;
43 | this.port = port;
44 | this.mainCmd = mainCmd;
45 | this.subCmd = subCmd;
46 | this.sendBody = sendBody;
47 | this.mListener = listener;
48 | this.bytes = bytes;
49 | this.connectSoTime = connectSoTime;
50 | }
51 |
52 | @Override
53 | public void run() {
54 | try {
55 | initialSendMessage(mainCmd, subCmd, sendBody, bytes, connectSoTime);
56 | } catch (final Exception e) {
57 | Log.e("wtt", e.toString());
58 | if (MyApplication.mHandler != null) {
59 | MyApplication.mHandler.post(new Runnable() {
60 | @Override
61 | public void run() {
62 | if (mListener != null) {
63 | mListener.error(e);
64 | }
65 | }
66 | });
67 | }
68 | }
69 | }
70 |
71 | /**
72 | * 发送消息
73 | *
74 | * @param mainCmd 主命令
75 | * @param subCmd 子命令
76 | * @param sendBody 发送消息
77 | * @param connectSoTime 超时时间
78 | * @throws Exception throws Exception
79 | */
80 | private void initialSendMessage(int mainCmd, int subCmd, String sendBody,
81 | byte[] bytes, int connectSoTime) throws Exception {
82 | if (mAnalyticDataUtils == null) {
83 | mAnalyticDataUtils = new AnalyticDataUtils();
84 | }
85 | mSocket = new Socket();
86 | mSocket.setReuseAddress(true);
87 | SocketAddress socketAddress = new InetSocketAddress(ip, port);
88 | mSocket.connect(socketAddress, connectSoTime);
89 | mSocket.setSoTimeout(60000); //此方法意为tcp连接成功后is.read阻塞多长时间
90 | outputStream = mSocket.getOutputStream();
91 | EncodeV1 encodeV1 = new EncodeV1(mainCmd, subCmd, sendBody, bytes);
92 | outputStream.write(encodeV1.buildSendContent());
93 | outputStream.flush();
94 | inputStream = mSocket.getInputStream();
95 | byte[] tempBytes = mAnalyticDataUtils.readByte(inputStream, 18);
96 | ReceiveHeader receiveHeader = mAnalyticDataUtils.analysisHeader(tempBytes);
97 | ReceiveData receiveData = mAnalyticDataUtils.synchAnalyticData(inputStream, receiveHeader);
98 | if (mListener != null) {
99 | MyApplication.mHandler.post(new Runnable() {
100 | @Override
101 | public void run() {
102 | mListener.success(receiveData.getHeader().getMainCmd(), receiveData.getHeader().getSubCmd(),
103 | receiveData.getSendBody(), receiveData.getBuff());
104 | }
105 | });
106 | }
107 | mSocket.close();
108 |
109 |
110 | }
111 |
112 | // TODO: 2018/7/25 关闭
113 | public void shutdown() {
114 | try {
115 | if (outputStream != null) outputStream.close();
116 | if (inputStream != null) inputStream.close();
117 | } catch (IOException e) {
118 | e.printStackTrace();
119 | }
120 | clearSocket();
121 | this.interrupt();
122 | }
123 |
124 | private void clearSocket() {
125 | if (mSocket != null && (!mSocket.isClosed())) {
126 | try {
127 | mSocket.close();
128 | mSocket = null;
129 | } catch (IOException e) {
130 | e.printStackTrace();
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/net/TcpUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.net;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * Created by wt
7 | * Date on 2018/7/4 09:48
8 | * tcp发送消息和文件,并接受回调的方法.
9 | */
10 | public class TcpUtil {
11 | private static TcpUtil instance;
12 | private String ip;
13 | private int port;
14 | private int connectTime = 20000;
15 | private Type mType;
16 |
17 | private boolean isCancel = true;
18 | private RequestTcp requestTcp;
19 |
20 | private enum Type {
21 | DEFAULT, SETTING //向pc发送,向设置ip发送
22 | }
23 |
24 | private TcpUtil() {
25 | mType = Type.DEFAULT;
26 | }
27 |
28 |
29 | public TcpUtil(String ip, int port) {
30 | mType = Type.SETTING;
31 | this.ip = ip;
32 | this.port = port;
33 | }
34 |
35 | private static TcpUtil getInstance() {
36 | instance = new TcpUtil();
37 | return instance;
38 | }
39 |
40 | public void sendMessage(int mainCmd, int subCmd, OnTcpSendMessageListner listner) {
41 | sendMessage(mainCmd, subCmd, null, listner);
42 | }
43 |
44 | public void sendMessage(int mainCmd, int subCmd, final String sendBody, OnTcpSendMessageListner listner) {
45 | sendMessage(mainCmd, subCmd, sendBody, new byte[0], connectTime, listner);
46 | }
47 |
48 | // TODO: 2018/7/16 wt 增加投屏者信息
49 | public void sendMessage(final int mainCmd, final int subCmd, final String sendBody, byte[] bytes, final int connectSoTime, final OnTcpSendMessageListner listner) {
50 | requestTcp = new RequestTcp(ip, port,mainCmd, subCmd, sendBody, bytes, connectSoTime, listner);
51 | requestTcp.start();
52 | }
53 |
54 | public void sendTimeOut(int timeOut) {
55 | this.connectTime = timeOut;
56 | }
57 |
58 |
59 | public void cancel() {
60 | if (requestTcp != null) {
61 | requestTcp.shutdown();
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/net/boastcast/NetWorkStateReceiver.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.net.boastcast;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.ConnectivityManager;
7 | import android.net.Network;
8 | import android.net.NetworkInfo;
9 | import android.os.Build;
10 | import android.support.annotation.RequiresApi;
11 | import android.util.Log;
12 |
13 | import com.test.screenimage.utils.NetWorkUtils;
14 | import com.test.screenimage.utils.ToastUtils;
15 |
16 | import org.greenrobot.eventbus.EventBus;
17 |
18 | /**
19 | * Created by wt on 2018/7/2.
20 | * 监听网络变化
21 | */
22 | public class NetWorkStateReceiver extends BroadcastReceiver {
23 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
24 | @Override
25 | public void onReceive(Context context, Intent intent) {
26 | System.out.println("网络状态发生变化");
27 | //检测API是不是小于23,因为到了API23之后getNetworkInfo(int networkType)方法被弃用
28 | if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
29 | //获得ConnectivityManager对象
30 | ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
31 | //获取ConnectivityManager对象对应的NetworkInfo对象
32 | // 获取WIFI连接的信息
33 | NetworkInfo wifiNetworkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
34 | //获取移动数据连接的信息
35 | NetworkInfo ethNetInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
36 | if (wifiNetworkInfo == null && ethNetInfo == null) {
37 | EventBus.getDefault().post("");
38 | ToastUtils.showShort(context, "请先连接网络!!");
39 | } else if (wifiNetworkInfo != null && wifiNetworkInfo.isConnected()) {
40 | EventBus.getDefault().post(NetWorkUtils.getLocalIpAddress());
41 | ToastUtils.showShort(context, "已连接无线网!!");
42 | } else if (ethNetInfo != null && ethNetInfo.isConnected()) {
43 | EventBus.getDefault().post(NetWorkUtils.getLocalIpAddress());
44 | ToastUtils.showShort(context, "已连接有线网!!");
45 | } else {
46 | EventBus.getDefault().post("");
47 | ToastUtils.showShort(context, "请先连接网络!!");
48 | }
49 | return;
50 | }
51 | //API大于23时使用下面的方式进行网络监听
52 | //获得ConnectivityManager对象
53 | ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
54 | //获取所有网络连接的信息
55 | Network[] networks = connMgr.getAllNetworks();
56 | //用于存放网络连接信息
57 | StringBuilder sb = new StringBuilder();
58 | //通过循环将网络信息逐个取出来
59 | if (networks.length == 0) {
60 | EventBus.getDefault().post("");
61 | ToastUtils.showShort(context, "请先连接网络!!");
62 | return;
63 | }
64 | for (int i = 0; i < networks.length; i++) {
65 | //获取ConnectivityManager对象对应的NetworkInfo对象
66 | NetworkInfo networkInfo = connMgr.getNetworkInfo(networks[i]);
67 | if (networkInfo.isConnected()) {
68 | EventBus.getDefault().post(NetWorkUtils.getLocalIpAddress());
69 | } else {
70 | EventBus.getDefault().post("");
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/screen/ScreenRecordEncoder.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.screen;
2 |
3 | import android.annotation.TargetApi;
4 | import android.media.MediaCodec;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.HandlerThread;
9 | import android.util.Log;
10 | import android.view.Surface;
11 |
12 | import com.test.screenimage.configuration.VideoConfiguration;
13 | import com.test.screenimage.constant.Constants;
14 | import com.test.screenimage.mediacodec.VideoMediaCodec;
15 | import com.test.screenimage.utils.SopCastLog;
16 | import com.test.screenimage.video.OnVideoEncodeListener;
17 |
18 |
19 | import java.nio.ByteBuffer;
20 | import java.util.concurrent.locks.ReentrantLock;
21 |
22 | import static android.os.Build.VERSION_CODES.LOLLIPOP;
23 |
24 |
25 | @TargetApi(LOLLIPOP)
26 | public class ScreenRecordEncoder {
27 | private MediaCodec mMediaCodec;
28 | private OnVideoEncodeListener mListener;
29 | private boolean mPause;
30 | private HandlerThread mHandlerThread;
31 | private Handler mEncoderHandler;
32 | private VideoConfiguration mConfiguration;
33 | private MediaCodec.BufferInfo mBufferInfo;
34 | private volatile boolean isStarted;
35 | private ReentrantLock encodeLock = new ReentrantLock();
36 |
37 | public ScreenRecordEncoder(VideoConfiguration configuration) {
38 | mConfiguration = configuration;
39 | mMediaCodec = VideoMediaCodec.getVideoMediaCodec(mConfiguration);
40 | }
41 |
42 | public void setOnVideoEncodeListener(OnVideoEncodeListener listener) {
43 | mListener = listener;
44 | }
45 |
46 | public void setPause(boolean pause) {
47 | mPause = pause;
48 | }
49 |
50 | public void start() {
51 | mHandlerThread = new HandlerThread("LFEncode");
52 | mHandlerThread.start();
53 | mEncoderHandler = new Handler(mHandlerThread.getLooper());
54 | mBufferInfo = new MediaCodec.BufferInfo();
55 | mMediaCodec.start();
56 | mEncoderHandler.post(swapDataRunnable);
57 | isStarted = true;
58 | }
59 |
60 | public Surface getSurface() {
61 | if(mMediaCodec != null) {
62 | return mMediaCodec.createInputSurface();
63 | } else {
64 | return null;
65 | }
66 | }
67 |
68 | private Runnable swapDataRunnable = new Runnable() {
69 | @Override
70 | public void run() {
71 | drainEncoder();
72 | }
73 | };
74 |
75 | public void stop() {
76 | try {
77 | isStarted = false;
78 | mEncoderHandler.removeCallbacks(null);
79 | mHandlerThread.quit();
80 | encodeLock.lock();
81 | mMediaCodec.signalEndOfInputStream();
82 | releaseEncoder();
83 | encodeLock.unlock();
84 | }catch (Exception e){
85 | Log.e("ScreenRecordEncoder","92" + e.toString());
86 | }
87 | }
88 |
89 | private void releaseEncoder() {
90 | if (mMediaCodec != null) {
91 | mMediaCodec.stop();
92 | mMediaCodec.release();
93 | mMediaCodec = null;
94 | }
95 | }
96 |
97 | @TargetApi(Build.VERSION_CODES.KITKAT)
98 | public void setRecorderBps(int bps) {
99 | if (mMediaCodec == null) {
100 | return;
101 | }
102 | SopCastLog.d(Constants.TAG, "bps :" + bps * 1024);
103 | // TODO: 2018/9/10 动态调整目标码率
104 | Bundle bitrate = new Bundle();
105 | bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bps * 1024);
106 | mMediaCodec.setParameters(bitrate);
107 | }
108 |
109 | private void drainEncoder() {
110 | while (isStarted) {
111 | encodeLock.lock();
112 | if(mMediaCodec != null) {
113 | int outBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 12000);
114 | if (outBufferIndex >= 0) {
115 | ByteBuffer bb = mMediaCodec.getOutputBuffer(outBufferIndex);
116 | if (mListener != null && !mPause) {
117 | mListener.onVideoEncode(bb, mBufferInfo);
118 | }
119 | mMediaCodec.releaseOutputBuffer(outBufferIndex, false);
120 | } else {
121 | try {
122 | // wait 10ms
123 | Thread.sleep(10);
124 | } catch (InterruptedException e) {
125 | e.printStackTrace();
126 | }
127 | }
128 | encodeLock.unlock();
129 | } else {
130 | encodeLock.unlock();
131 | break;
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/service/ScreenImageService.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.service;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.app.Service;
8 | import android.content.Intent;
9 | import android.content.ServiceConnection;
10 | import android.media.projection.MediaProjectionManager;
11 | import android.os.Binder;
12 | import android.os.Build;
13 | import android.os.IBinder;
14 | import android.support.annotation.Nullable;
15 | import android.support.v4.app.NotificationCompat;
16 | import android.util.Log;
17 | import android.widget.Toast;
18 |
19 | import com.test.screenimage.R;
20 | import com.test.screenimage.configuration.VideoConfiguration;
21 | import com.test.screenimage.constant.ScreenImageApi;
22 | import com.test.screenimage.controller.StreamController;
23 | import com.test.screenimage.controller.audio.NormalAudioController;
24 | import com.test.screenimage.controller.video.ScreenVideoController;
25 | import com.test.screenimage.stream.packer.TcpPacker;
26 | import com.test.screenimage.stream.sender.OnSenderListener;
27 | import com.test.screenimage.stream.sender.tcp.TcpSender;
28 | import com.test.screenimage.ui.ScreenImageActivity;
29 | import com.test.screenimage.utils.ToastUtils;
30 |
31 | /**
32 | * Created by wt
33 | * Date on 2018/7/20 16:14:02.
34 | *
35 | * @Desc
36 | */
37 |
38 | public class ScreenImageService extends Service implements OnSenderListener {
39 | private static final String TAG = "ScreenRecorderService";
40 |
41 | private TcpSender tcpSender;
42 | private VideoConfiguration mVideoConfiguration;
43 | private StreamController mStreamController;
44 | private OnSenderListener mListener;
45 | private int mCurrentBps;
46 | private int netBodCount = 0;
47 |
48 | private static final int foregroundId = 1234;
49 | NotificationManager notificationManager;
50 | Notification notification;
51 | String id = "screen_image_channel";
52 | String name = "ScreenImage";
53 |
54 | @Nullable
55 | @Override
56 | public IBinder onBind(Intent intent) {
57 | return new ScreenImageBinder();
58 | }
59 |
60 | @Override
61 | public void onCreate() {
62 | super.onCreate();
63 | notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
65 | NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
66 | notificationManager.createNotificationChannel(mChannel);
67 | }
68 | createNotification("....");
69 |
70 | }
71 |
72 | private void createNotification(String text) {
73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
74 | notification = new Notification.Builder(this)
75 | .setChannelId(id)
76 | .setContentTitle("ScreenImage 正在投屏中")
77 | .setContentText(text)
78 | .setContentIntent(getDefalutIntent(Notification.FLAG_FOREGROUND_SERVICE))
79 | .setSmallIcon(R.mipmap.ic_launcher).build();
80 | } else {
81 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
82 | .setContentTitle("ScreenImage 正在投屏中")
83 | .setContentText(text)
84 | .setSmallIcon(R.mipmap.ic_launcher)
85 | .setContentIntent(getDefalutIntent(Notification.FLAG_FOREGROUND_SERVICE))
86 | .setOngoing(true);
87 | notification = notificationBuilder.build();
88 | }
89 | notificationManager.notify(foregroundId, notification);
90 | }
91 |
92 | private PendingIntent getDefalutIntent(int flags) {
93 | Intent intent = new Intent(this, ScreenImageActivity.class);
94 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, flags);
95 | return pendingIntent;
96 | }
97 |
98 | @Override
99 | public int onStartCommand(Intent intent, int flags, int startId) {
100 | startForeground(foregroundId, notification);
101 | return START_NOT_STICKY;
102 |
103 | }
104 |
105 | @Override
106 | public void onConnecting() {
107 | if (mListener != null) mListener.onConnecting();
108 | }
109 |
110 | @Override
111 | public void onConnected() {
112 | mCurrentBps = mVideoConfiguration.maxBps;
113 | if (mListener != null) mListener.onConnected();
114 | }
115 |
116 | @Override
117 | public void onDisConnected(String message) {
118 | if (mListener != null) mListener.onDisConnected(message);
119 | }
120 |
121 | @Override
122 | public void onConnectFail(String message) {
123 | if (mListener != null) mListener.onConnectFail(message);
124 | }
125 |
126 | @Override
127 | public void onPublishFail() {
128 | if (mListener != null) mListener.onPublishFail();
129 | }
130 |
131 | @Override
132 | public void onNetGood() {
133 | //网络好
134 | netBodCount = 0; //
135 | // LogUtil.e(TAG, "onNetGood Current Bps: " + mCurrentBps);
136 | if (mCurrentBps == mVideoConfiguration.maxBps) {
137 | return;
138 | }
139 | int bps;
140 | if (mCurrentBps + 100 <= mVideoConfiguration.maxBps) {
141 | bps = mCurrentBps + 100;
142 | } else {
143 | bps = mVideoConfiguration.maxBps;
144 | }
145 | boolean result = mStreamController.setVideoBps(bps);
146 | if (result) {
147 | mCurrentBps = bps;
148 | }
149 | if (mListener != null) mListener.onNetGood();
150 | }
151 |
152 | @Override
153 | public void onNetBad() {
154 | if (mCurrentBps == mVideoConfiguration.minBps) {
155 | netBodCount++;
156 | if (netBodCount >= 2) {
157 | netBodCount = 0;
158 | }
159 | return;
160 | }
161 | int bps;
162 | if (mCurrentBps - 550 >= mVideoConfiguration.minBps) {
163 | bps = mCurrentBps - 550;
164 | } else {
165 | bps = mVideoConfiguration.minBps;
166 | }
167 | boolean result = mStreamController.setVideoBps(bps);
168 | if (result) {
169 | mCurrentBps = bps;
170 | }
171 | if (mListener != null) mListener.onNetBad();
172 | }
173 |
174 | @Override
175 | public void shutDown() {
176 | if (mListener != null) {
177 | mListener.shutDown();
178 | }
179 | }
180 |
181 | @Override
182 | public void netSpeedChange(String netSpeedMsg) {
183 | createNotification(netSpeedMsg);
184 | if (mListener != null) mListener.netSpeedChange(netSpeedMsg);
185 | }
186 |
187 | public class ScreenImageBinder extends Binder {
188 |
189 | public ScreenImageService getService() {
190 | return ScreenImageService.this;
191 | }
192 | }
193 |
194 | /**
195 | * 开始录制屏幕
196 | * @param mMediaProjectionManage
197 | * @param resultCode
198 | * @param data
199 | * @param listener
200 | * @param ip
201 | * @param port
202 | */
203 | public void startController(MediaProjectionManager mMediaProjectionManage, int resultCode,
204 | Intent data, OnSenderListener listener, String ip, int port) {
205 | ScreenVideoController screenVideoController = new ScreenVideoController(mMediaProjectionManage, resultCode, data);
206 | NormalAudioController audioController = new NormalAudioController(this);
207 | mStreamController = new StreamController(screenVideoController, audioController);
208 | TcpPacker packer = new TcpPacker();
209 | mListener = listener;
210 | tcpSender = new TcpSender(ip, port);
211 | tcpSender.setSenderListener(this);
212 | tcpSender.setMianCmd(ScreenImageApi.RECORD.MAIN_CMD);
213 | tcpSender.setSubCmd(ScreenImageApi.RECORD.RECORDER_REQUEST_START);
214 | tcpSender.setSendBody(Build.MODEL);
215 | mVideoConfiguration = new VideoConfiguration.Builder().setSize(1080, 1920).build();
216 | mStreamController.setVideoConfiguration(mVideoConfiguration);
217 | mStreamController.setPacker(packer);
218 | mStreamController.setSender(tcpSender);
219 | mStreamController.start();
220 | tcpSender.openConnect();
221 | }
222 |
223 | // @Override
224 | // public void unbindService(ServiceConnection conn) {
225 | // Log.e("ttt", "unbindService: zzz" );
226 | // super.unbindService(conn);
227 | // if (mStreamController != null) {
228 | // mStreamController.stop();
229 | // }
230 | // if (tcpSender != null) {
231 | // tcpSender.stop();
232 | // }
233 | // }
234 |
235 |
236 | @Override
237 | public void onRebind(Intent intent) {
238 | super.onRebind(intent);
239 | }
240 |
241 | @Override
242 | public boolean onUnbind(Intent intent) {
243 | stopRecording();
244 | return true;
245 |
246 |
247 | }
248 |
249 | private void stopRecording() {
250 | Log.e("ttt", "stopRecording: zzz");
251 | if (mStreamController != null) {
252 | mStreamController.stop();
253 | }
254 | shutDown();
255 | stopForeground(true);
256 | }
257 |
258 | @Override
259 | public void onDestroy() {
260 | ToastUtils.show(this, "投屏服务停止了", Toast.LENGTH_SHORT);
261 | stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
262 | super.onDestroy();
263 | }
264 |
265 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/packer/AnnexbHelper.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.packer;
2 |
3 | import android.media.MediaCodec;
4 |
5 |
6 | import com.test.screenimage.constant.Constants;
7 | import com.test.screenimage.utils.SopCastLog;
8 |
9 | import java.nio.ByteBuffer;
10 | import java.util.ArrayList;
11 |
12 | public class AnnexbHelper {
13 |
14 | // Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( )
15 | public final static int NonIDR = 1;
16 | // Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( )
17 | public final static int IDR = 5;
18 | // Supplemental enhancement information (SEI) sei_rbsp( )
19 | public final static int SEI = 6;
20 | // Sequence parameter set seq_parameter_set_rbsp( )
21 | public final static int SPS = 7;
22 | // Picture parameter set pic_parameter_set_rbsp( )
23 | public final static int PPS = 8;
24 | // Access unit delimiter access_unit_delimiter_rbsp( )
25 | public final static int AccessUnitDelimiter = 9;
26 |
27 | private AnnexbNaluListener mListener;
28 |
29 | private byte[] mPps;
30 | private byte[] mSps;
31 | private boolean mUploadPpsSps = true;
32 |
33 |
34 | /**
35 | * the search result for annexb.
36 | */
37 | class AnnexbSearch {
38 | public int startCode = 0;
39 | public boolean match = false;
40 | }
41 |
42 | public interface AnnexbNaluListener {
43 | void onSpsPps(byte[] sps, byte[] pps);
44 |
45 | void onVideo(byte[] data, boolean isKeyFrame);
46 | }
47 |
48 | public void setAnnexbNaluListener(AnnexbNaluListener listener) {
49 | mListener = listener;
50 | }
51 |
52 |
53 | public void stop() {
54 | mListener = null;
55 | mPps = null;
56 | mSps = null;
57 | mUploadPpsSps = true;
58 | }
59 |
60 | // TODO: 2018/5/28 wt 处理h264数据,确保视频最开始是sps,pps
61 | private byte[] header = {0x00, 0x00, 0x00, 0x01};
62 | public void analyseVideoDataonlyH264(ByteBuffer bb, MediaCodec.BufferInfo bi) {
63 | bb.position(bi.offset);
64 | bb.limit(bi.offset + bi.size);
65 | ArrayList frames = new ArrayList<>();
66 | boolean isKeyFrame = false;
67 |
68 | while (bb.position() < bi.offset + bi.size) {
69 | byte[] frame = annexbDemux(bb, bi);
70 | if (frame == null) {
71 | SopCastLog.e(Constants.TAG, "annexb not match.");
72 | break;
73 | }
74 | // ignore the nalu type aud(9)
75 | if (isAccessUnitDelimiter(frame)) {
76 | continue;
77 | }
78 | // for pps
79 | if (isPps(frame)) {
80 | mPps = frame;
81 | continue;
82 | }
83 | // for sps
84 | if (isSps(frame)) {
85 | mSps = frame;
86 | continue;
87 | }
88 | // for IDR frame
89 | if (isKeyFrame(frame)) {
90 | isKeyFrame = true;
91 | } else {
92 | isKeyFrame = false;
93 | }
94 | //加上h.264的头
95 | frames.add(header);
96 | frames.add(frame);
97 | }
98 | if (mPps != null && mSps != null && mListener != null && mUploadPpsSps) {
99 | if (mListener != null) {
100 | mListener.onSpsPps(mSps, mPps);
101 | }
102 | mUploadPpsSps = false;
103 | }
104 | if (frames.size() == 0 || mListener == null) {
105 | return;
106 | }
107 | int size = 0;
108 | for (int i = 0; i < frames.size(); i++) {
109 | byte[] frame = frames.get(i);
110 | size += frame.length;
111 | }
112 | byte[] data = new byte[size];
113 | int currentSize = 0;
114 | for (int i = 0; i < frames.size(); i++) {
115 | byte[] frame = frames.get(i);
116 | System.arraycopy(frame, 0, data, currentSize, frame.length);
117 | currentSize += frame.length;
118 | }
119 | if (mListener != null) {
120 | mListener.onVideo(data, isKeyFrame);
121 | }
122 | }
123 |
124 | /**
125 | * 从硬编出来的数据取出一帧nal
126 | *
127 | * @param bb
128 | * @param bi
129 | * @return
130 | */
131 | private byte[] annexbDemux(ByteBuffer bb, MediaCodec.BufferInfo bi) {
132 | AnnexbSearch annexbSearch = new AnnexbSearch();
133 | avcStartWithAnnexb(annexbSearch, bb, bi);
134 |
135 | if (!annexbSearch.match || annexbSearch.startCode < 3) {
136 | return null;
137 | }
138 |
139 | for (int i = 0; i < annexbSearch.startCode; i++) {
140 | bb.get();
141 | }
142 |
143 | ByteBuffer frameBuffer = bb.slice();
144 | int pos = bb.position();
145 | while (bb.position() < bi.offset + bi.size) {
146 | avcStartWithAnnexb(annexbSearch, bb, bi);
147 | if (annexbSearch.match) {
148 | break;
149 | }
150 | bb.get();
151 | }
152 |
153 | int size = bb.position() - pos;
154 | byte[] frameBytes = new byte[size];
155 | frameBuffer.get(frameBytes);
156 | return frameBytes;
157 | }
158 |
159 |
160 | /**
161 | * 从硬编出来的byteBuffer中查找nal
162 | *
163 | * @param as
164 | * @param bb
165 | * @param bi
166 | */
167 | private void avcStartWithAnnexb(AnnexbSearch as, ByteBuffer bb, MediaCodec.BufferInfo bi) {
168 | as.match = false;
169 | as.startCode = 0;
170 | int pos = bb.position();
171 | while (pos < bi.offset + bi.size - 3) {
172 | // not match.
173 | if (bb.get(pos) != 0x00 || bb.get(pos + 1) != 0x00) {
174 | break;
175 | }
176 |
177 | // match N[00] 00 00 01, where N>=0
178 | if (bb.get(pos + 2) == 0x01) {
179 | as.match = true;
180 | as.startCode = pos + 3 - bb.position();
181 | break;
182 | }
183 | pos++;
184 | }
185 | }
186 |
187 | private byte[] buildNaluHeader(int length) {
188 | ByteBuffer buffer = ByteBuffer.allocate(4);
189 | buffer.putInt(length);
190 | return buffer.array();
191 | }
192 |
193 |
194 | private boolean isSps(byte[] frame) {
195 | if (frame.length < 1) {
196 | return false;
197 | }
198 | // 5bits, 7.3.1 NAL unit syntax,
199 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
200 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
201 | int nal_unit_type = (frame[0] & 0x1f);
202 | return nal_unit_type == SPS;
203 | }
204 |
205 | private boolean isPps(byte[] frame) {
206 | if (frame.length < 1) {
207 | return false;
208 | }
209 | // 5bits, 7.3.1 NAL unit syntax,
210 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
211 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
212 | int nal_unit_type = (frame[0] & 0x1f);
213 | return nal_unit_type == PPS;
214 | }
215 |
216 | private boolean isKeyFrame(byte[] frame) {
217 | if (frame.length < 1) {
218 | return false;
219 | }
220 | // 5bits, 7.3.1 NAL unit syntax,
221 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
222 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
223 | int nal_unit_type = (frame[0] & 0x1f);
224 | return nal_unit_type == IDR;
225 | }
226 |
227 | private static boolean isAccessUnitDelimiter(byte[] frame) {
228 | if (frame.length < 1) {
229 | return false;
230 | }
231 | // 5bits, 7.3.1 NAL unit syntax,
232 | // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
233 | // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame
234 | int nal_unit_type = (frame[0] & 0x1f);
235 | return nal_unit_type == AccessUnitDelimiter;
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/packer/Packer.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.packer;
2 |
3 | import android.media.MediaCodec;
4 |
5 | import java.nio.ByteBuffer;
6 |
7 | public interface Packer {
8 | interface OnPacketListener {
9 | //第一个参数为打包后的数据,第二个为自定义打包后的类型
10 | void onPacket(byte[] data, int packetType);
11 |
12 | //回调sps和pps
13 | void onSpsPps(byte[] mSpsPps);
14 | }
15 |
16 | //设置打包监听器
17 | void setPacketListener(OnPacketListener listener);
18 |
19 | //处理视频硬编编码器输出的数据
20 | void onVideoData(ByteBuffer bb, MediaCodec.BufferInfo bi);
21 |
22 | //处理音频硬编编码器输出的数据
23 | void onAudioData(ByteBuffer bb, MediaCodec.BufferInfo bi);
24 |
25 | //开始打包,一般进行打包的预处理
26 | void start();
27 |
28 | //结束打包,一般进行打包器的状态恢复
29 | void stop();
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/packer/TcpPacker.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.packer;
2 |
3 | import android.media.MediaCodec;
4 | import android.util.Log;
5 |
6 |
7 | import com.test.screenimage.configuration.AudioConfiguration;
8 |
9 | import java.nio.ByteBuffer;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | /**
14 | * Created by wt
15 | * Date on 2018/5/28
16 | *
17 | * @Desc add
18 | */
19 |
20 | public class TcpPacker implements Packer, AnnexbHelper.AnnexbNaluListener {
21 | // private static final String TAG = "TcpPacker";
22 | // public static final int HEADER = 0;
23 | // public static final int METADATA = 1;
24 | public static final int FIRST_VIDEO = 2;
25 | public static final int AUDIO = 4;
26 | public static final int KEY_FRAME = 5;
27 | public static final int INTER_FRAME = 6;
28 |
29 | private OnPacketListener packetListener;
30 | private boolean isHeaderWrite;
31 | private boolean isKeyFrameWrite;
32 |
33 | private int mAudioSampleRate, mAudioSampleSize;
34 | private boolean mIsStereo;
35 | private boolean mSendAudio = false;
36 |
37 | private AnnexbHelper mAnnexbHelper;
38 | // TODO: 2018/5/28 wt 增添h,264数据头
39 | private byte[] mSpsPps;
40 | private byte[] header = {0x00, 0x00, 0x00, 0x01}; //H264的头文件
41 |
42 |
43 | public TcpPacker() {
44 | mAnnexbHelper = new AnnexbHelper();
45 | }
46 |
47 | public void setPacketListener(OnPacketListener listener) {
48 | packetListener = listener;
49 | }
50 |
51 |
52 | @Override
53 | public void start() {
54 | mAnnexbHelper.setAnnexbNaluListener(this);
55 | }
56 |
57 | @Override
58 | public void onVideoData(ByteBuffer bb, MediaCodec.BufferInfo bi) {
59 | mAnnexbHelper.analyseVideoDataonlyH264(bb, bi);
60 | }
61 |
62 | @Override
63 | public void onAudioData(ByteBuffer bb, MediaCodec.BufferInfo bi) {
64 | if (packetListener == null) {
65 | return;
66 | }
67 | if (!mSendAudio) {
68 | return;
69 | }
70 | bb.position(bi.offset);
71 | bb.limit(bi.offset + bi.size);
72 | byte[] audio = new byte[bi.size];
73 | bb.get(audio);
74 | //一般第一帧都是2个字节
75 | int length = 7 + audio.length;
76 | ByteBuffer tempBb = ByteBuffer.allocate(length + 4);
77 | tempBb.put(header);
78 | tempBb.put(getADTSHeader(length));
79 | tempBb.put(audio);
80 | packetListener.onPacket(tempBb.array(), AUDIO);
81 | }
82 |
83 | @Override
84 | public void stop() {
85 | isHeaderWrite = false;
86 | isKeyFrameWrite = false;
87 | mAnnexbHelper.stop();
88 | }
89 |
90 |
91 | // TODO: 2018/6/4 wt h.264裸流
92 | @Override
93 | public void onSpsPps(byte[] sps, byte[] pps) {
94 | ByteBuffer byteBuffer = ByteBuffer.allocate(sps.length + 4);
95 | byteBuffer.put(header);
96 | byteBuffer.put(sps);
97 | mSpsPps = byteBuffer.array();
98 |
99 | packetListener.onPacket(mSpsPps, FIRST_VIDEO);
100 | ByteBuffer byteBuffer1 = ByteBuffer.allocate(pps.length + 4);
101 | byteBuffer1.put(header);
102 | byteBuffer1.put(pps);
103 | packetListener.onPacket(byteBuffer1.array(), FIRST_VIDEO);
104 | isHeaderWrite = true;
105 | }
106 |
107 |
108 | @Override
109 | public void onVideo(byte[] video, boolean isKeyFrame) {
110 | if (packetListener == null || !isHeaderWrite) {
111 | return;
112 | }
113 | int packetType = INTER_FRAME;
114 | if (isKeyFrame) {
115 | isKeyFrameWrite = true;
116 | packetType = KEY_FRAME;
117 | }
118 | //确保第一帧是关键帧,避免一开始出现灰色模糊界面
119 | if (!isKeyFrameWrite) {
120 | return;
121 | }
122 | ByteBuffer bb;
123 | if (isKeyFrame) {
124 | bb = ByteBuffer.allocate(video.length);
125 | bb.put(video);
126 | } else {
127 | bb = ByteBuffer.allocate(video.length);
128 | bb.put(video);
129 | }
130 | packetListener.onPacket(bb.array(), packetType);
131 | }
132 |
133 | public void initAudioParams(int sampleRate, int sampleSize, boolean isStereo) {
134 | mAudioSampleRate = sampleRate;
135 | mAudioSampleSize = sampleSize;
136 | mIsStereo = isStereo;
137 | }
138 |
139 | // TODO: 2018/6/4 是否发送音频
140 | public void setSendAudio(boolean sendAudio) {
141 | this.mSendAudio = sendAudio;
142 | }
143 |
144 | // TODO: 2018/6/4 wt 给编码出的aac裸流添加adts头字段
145 | private byte[] getADTSHeader(int packetLen) {
146 | byte[] packet = new byte[7];
147 | int profile = 2; //AAC LC
148 | int freqIdx = getFrequencyIdx(AudioConfiguration.DEFAULT_FREQUENCY); //44100Hz
149 | int chanCfg = 2; //CPE 声道数
150 | packet[0] = (byte) 0xFF;
151 | packet[1] = (byte) 0xF9;
152 | packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
153 | packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
154 | packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
155 | packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
156 | packet[6] = (byte) 0xFC;
157 | return packet;
158 | }
159 |
160 | // TODO: 2018/6/5 wt 找出采集频率对应下标
161 | private int getFrequencyIdx(int defaultFrequency) {
162 | Map samplingFrequencyIndexMap = new HashMap<>();
163 | samplingFrequencyIndexMap.put(96000, 0);
164 | samplingFrequencyIndexMap.put(88200, 1);
165 | samplingFrequencyIndexMap.put(64000, 2);
166 | samplingFrequencyIndexMap.put(48000, 3);
167 | samplingFrequencyIndexMap.put(44100, 4);
168 | samplingFrequencyIndexMap.put(32000, 5);
169 | samplingFrequencyIndexMap.put(24000, 6);
170 | samplingFrequencyIndexMap.put(22050, 7);
171 | samplingFrequencyIndexMap.put(16000, 8);
172 | samplingFrequencyIndexMap.put(12000, 9);
173 | samplingFrequencyIndexMap.put(11025, 10);
174 | samplingFrequencyIndexMap.put(8000, 11);
175 | return samplingFrequencyIndexMap.get(defaultFrequency);
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/OnSenderListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender;
2 |
3 | /**
4 | * Created by wt
5 | * Date on 2018/5/28
6 | *
7 | * @Desc 发送监听
8 | */
9 |
10 | public interface OnSenderListener {
11 | void onConnecting();
12 |
13 | void onConnected();
14 |
15 | void onDisConnected(String message);
16 |
17 | void onConnectFail(String message);
18 |
19 | void onPublishFail();
20 |
21 | void onNetGood();
22 |
23 | void onNetBad();
24 |
25 | // TODO: 2018/7/25 手动断开连接
26 | void shutDown();
27 |
28 | void netSpeedChange(String netSpeedMsg);
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/Sender.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender;
2 |
3 | public interface Sender {
4 | void start();
5 | void onData(byte[] data, int type);
6 | void stop();
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/sendqueue/ISendQueue.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.sendqueue;
2 |
3 |
4 | import com.test.screenimage.entity.Frame;
5 |
6 | public interface ISendQueue {
7 | void start();
8 | void stop();
9 | void setBufferSize(int size);
10 | void putFrame(Frame frame);
11 | Frame takeFrame();
12 | void setSendQueueListener(SendQueueListener listener);
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/sendqueue/NormalSendQueue.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.sendqueue;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.screenimage.constant.Constants;
6 | import com.test.screenimage.entity.Frame;
7 | import com.test.screenimage.utils.SopCastLog;
8 |
9 |
10 | import java.util.ArrayList;
11 | import java.util.concurrent.ArrayBlockingQueue;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | import static com.test.screenimage.entity.Frame.FRAME_TYPE_AUDIO;
15 | import static com.test.screenimage.entity.Frame.FRAME_TYPE_INTER_FRAME;
16 | import static com.test.screenimage.entity.Frame.FRAME_TYPE_KEY_FRAME;
17 |
18 | public class NormalSendQueue implements ISendQueue {
19 | // private static final int NORMAL_FRAME_BUFFER_SIZE = 800;
20 | // TODO modify by wt tcp发送缓存区大小从800改为150
21 | //--------------------------------------------------------------
22 | private static final int NORMAL_FRAME_BUFFER_SIZE = 150; //缓存区大小
23 | private static final int SCAN_MAX_TIME = 5; //仲裁次数,每循环SCAN_MAX_TIME 次,每次sleep(DEFAULT_SLEEP_TIME),会执行一次检查网速的代码
24 | private static final int DEFAULT_SLEEP_TIME = 200; //
25 | private static final int DEFAULT_NEGATIVE_COUNT = 3; //循环SCAN_MAX_TIME 次,有 DEFAULT_NEGATIVE_COUNT 次输入queue的帧小于取走的帧
26 | //-----------------------------------------------------------
27 | private ArrayBlockingQueue mFrameBuffer;
28 | private int mFullQueueCount = NORMAL_FRAME_BUFFER_SIZE;
29 | private AtomicInteger mTotalFrameCount = new AtomicInteger(0); //总个数
30 | private AtomicInteger mGiveUpFrameCount = new AtomicInteger(0); //总个数
31 | private AtomicInteger mKeyFrameCount = new AtomicInteger(0); //队列里Key帧的总个数...
32 |
33 | private AtomicInteger mInFrameCount = new AtomicInteger(0); //进入总个数
34 | private AtomicInteger mOutFrameCount = new AtomicInteger(0); //输出总个数
35 | private volatile boolean mScanFlag;
36 | private SendQueueListener mSendQueueListener;
37 | private ScanThread mScanThread;
38 | private boolean isDebug = false;
39 | ;
40 |
41 | public NormalSendQueue() {
42 | mFrameBuffer = new ArrayBlockingQueue<>(mFullQueueCount, true);
43 | }
44 |
45 | @Override
46 | public void start() {
47 | mScanFlag = true;
48 | mScanThread = new ScanThread();
49 | mScanThread.start();
50 | }
51 |
52 | @Override
53 | public void stop() {
54 | mScanFlag = false;
55 | mInFrameCount.set(0);
56 | mOutFrameCount.set(0);
57 | mTotalFrameCount.set(0);
58 | mGiveUpFrameCount.set(0);
59 | mFrameBuffer.clear();
60 | }
61 |
62 | public void setSendQueueListener(SendQueueListener listener) {
63 | mSendQueueListener = listener;
64 | }
65 |
66 | @Override
67 | public void setBufferSize(int size) {
68 | mFullQueueCount = size;
69 | }
70 |
71 | public int getBufferFrameCount() {
72 | return mTotalFrameCount.get();
73 | }
74 |
75 | public int getGiveUpFrameCount() {
76 | return mGiveUpFrameCount.get();
77 | }
78 |
79 | @Override
80 | public void putFrame(Frame frame) {
81 | if (mFrameBuffer == null) {
82 | return;
83 | }
84 | if (frame.frameType == FRAME_TYPE_KEY_FRAME) mKeyFrameCount.getAndIncrement();
85 | abandonData();
86 | try {
87 | mFrameBuffer.put(frame);
88 | showLog("put frame total = " + mFrameBuffer.size());
89 | mInFrameCount.getAndIncrement();
90 | mTotalFrameCount.getAndIncrement();
91 | } catch (InterruptedException e) {
92 | showLog("put Frame exception" + e.toString());
93 | e.printStackTrace();
94 | }
95 | }
96 |
97 | @Override
98 | public Frame takeFrame() {
99 | if (mFrameBuffer == null) {
100 | return null;
101 | }
102 | Frame frame = null;
103 | try {
104 | showLog("take frame total size = " + mFrameBuffer.size());
105 | frame = mFrameBuffer.take();
106 | if (frame.frameType == FRAME_TYPE_KEY_FRAME) mKeyFrameCount.getAndDecrement();
107 | mOutFrameCount.getAndIncrement();
108 | mTotalFrameCount.getAndDecrement();
109 | } catch (InterruptedException e) {
110 | //do nothing
111 | showLog("take Frame exception" + e.toString());
112 | }
113 | return frame;
114 | }
115 |
116 | private void abandonData() {
117 | if (mTotalFrameCount.get() >= (mFullQueueCount / 3)) {
118 | showLog("队列里的帧数太多,开始丢帧..");
119 | //从队列头部开始搜索,删除最早发现的连续P帧
120 | boolean pFrameDelete = false;
121 | boolean start = false;
122 | for (Frame frame : mFrameBuffer) {
123 | if (!start) showLog("丢掉了下一个KEY_FRAME前的所有INTER_FRAME..");
124 | if (frame.frameType == FRAME_TYPE_INTER_FRAME) {
125 | start = true;
126 | }
127 | if (start) {
128 | if (frame.frameType == FRAME_TYPE_INTER_FRAME) {
129 | mFrameBuffer.remove(frame);
130 | mTotalFrameCount.getAndDecrement();
131 | mGiveUpFrameCount.getAndIncrement();
132 | pFrameDelete = true;
133 | } else if (frame.frameType == 3) {
134 | if (mKeyFrameCount.get() > 5) {
135 | showLog("丢掉了一个关键帧.. total" + mKeyFrameCount.get());
136 | mFrameBuffer.remove(frame);
137 | mKeyFrameCount.getAndDecrement();
138 | continue;
139 | }
140 | break;
141 | }
142 | }
143 | }
144 | boolean kFrameDelete = false;
145 | //从队列头部开始搜索,删除最早发现的I帧
146 | if (!pFrameDelete) {
147 | for (Frame frame : mFrameBuffer) {
148 | if (frame.frameType == FRAME_TYPE_KEY_FRAME) {
149 | mFrameBuffer.remove(frame);
150 | showLog("丢掉了一个关键帧..");
151 | mTotalFrameCount.getAndDecrement();
152 | mGiveUpFrameCount.getAndIncrement();
153 | mKeyFrameCount.getAndDecrement();
154 | kFrameDelete = true;
155 | break;
156 | }
157 | }
158 | }
159 | //从队列头部开始搜索,删除音频
160 | if (!pFrameDelete && !kFrameDelete) {
161 | for (Frame frame : mFrameBuffer) {
162 | if (frame.frameType == FRAME_TYPE_AUDIO) {
163 | mFrameBuffer.remove(frame);
164 | mTotalFrameCount.getAndDecrement();
165 | mGiveUpFrameCount.getAndIncrement();
166 | break;
167 | }
168 | }
169 | }
170 | }
171 | }
172 |
173 | private class ScanThread extends Thread {
174 |
175 | private int mCurrentScanTime = 0;
176 | private ArrayList mScanSnapShotList = new ArrayList<>();
177 |
178 | @Override
179 | public void run() {
180 | while (mScanFlag) {
181 | //达到仲裁次数了
182 | if (mCurrentScanTime == SCAN_MAX_TIME) {
183 | int averageDif = 0;
184 | int negativeCounter = 0;
185 | String strLog = "";
186 | for (int i = 0; i < SCAN_MAX_TIME; i++) {
187 | int dif = mScanSnapShotList.get(i).outCount - mScanSnapShotList.get(i).inCount;
188 | if (dif < 0) {
189 | negativeCounter++;
190 | }
191 | averageDif += dif;
192 | strLog = strLog + String.format("n%d:%d ", i, dif);
193 | }
194 | SopCastLog.e(Constants.TAG, strLog);
195 | if (negativeCounter >= DEFAULT_NEGATIVE_COUNT || averageDif < -100) {
196 | //坏
197 | SopCastLog.d(Constants.TAG, "Bad Send Speed.");
198 | if (mSendQueueListener != null) {
199 | mSendQueueListener.bad();
200 | }
201 | } else {
202 | //好
203 | SopCastLog.e(Constants.TAG, "Good Send Speed.");
204 | if (mSendQueueListener != null) {
205 | mSendQueueListener.good();
206 | }
207 | }
208 | //清空
209 | mScanSnapShotList.clear();
210 |
211 | }
212 | mScanSnapShotList.add(new ScanSnapShot(mInFrameCount.get(), mOutFrameCount.get()));
213 | mInFrameCount.set(0);
214 | mOutFrameCount.set(0);
215 | mCurrentScanTime++;
216 | try {
217 | Thread.sleep(DEFAULT_SLEEP_TIME);
218 | } catch (InterruptedException e) {
219 | e.printStackTrace();
220 | }
221 | }
222 | }
223 | }
224 |
225 | private class ScanSnapShot {
226 | public int inCount;
227 | public int outCount;
228 |
229 | public ScanSnapShot(int inCount, int outCount) {
230 | this.inCount = inCount;
231 | this.outCount = outCount;
232 | }
233 | }
234 |
235 | private void showLog(String msg) {
236 | if (isDebug) Log.e("NormalSendQueue", "" + msg);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/sendqueue/SendQueueListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.sendqueue;
2 | public interface SendQueueListener {
3 | void good();
4 | void bad();
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/EncodeV1.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp;
2 |
3 | import android.os.Build;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import com.test.screenimage.constant.ScreenImageApi;
8 | import com.test.screenimage.utils.ByteUtil;
9 |
10 | import java.nio.ByteBuffer;
11 |
12 | /**
13 | * Created by wt
14 | * Date on 2018/6/4 .
15 | * 传输数据格式
16 | */
17 |
18 | public class EncodeV1 {
19 | private int mainCmd;
20 | private int subCmd;
21 | private String sendBody;//要发送的内容
22 | private byte[] sendBuffer; //要发送的音视频内容
23 |
24 |
25 | /**
26 | * by wt
27 | *
28 | * @param mainCmd 主指令
29 | * @param subCmd 子指令
30 | * @param sendBody 文本内容
31 | * @param sendBuffer 音视频内容
32 | */
33 | public EncodeV1(int mainCmd, int subCmd, String sendBody, byte[] sendBuffer) {
34 | this.mainCmd = mainCmd;
35 | this.subCmd = subCmd;
36 | this.sendBody = sendBody;
37 | this.sendBuffer = sendBuffer;
38 | }
39 |
40 | public byte[] buildSendContent() {
41 | int bodyLength = 0;
42 | int bodyByte = 0;
43 | ByteBuffer bb = null;
44 | //文本数据
45 | if (!TextUtils.isEmpty(sendBody)) {
46 | bodyLength = sendBody.getBytes().length;
47 | }
48 | //音视频数据
49 | if (sendBuffer.length != 0) {
50 | bodyByte = sendBuffer.length;
51 | }
52 | //创建内存缓冲区
53 | bb = ByteBuffer.allocate(18 + bodyLength + bodyByte);
54 | bb.put(ScreenImageApi.encodeVersion1); //0-1编码版本
55 | bb.put(ByteUtil.int2Bytes(mainCmd)); //1-5 主指令
56 | bb.put(ByteUtil.int2Bytes(subCmd)); //5-9 子指令
57 | bb.put(ByteUtil.int2Bytes(bodyLength)); //9-13位,文本数据长度
58 | bb.put(ByteUtil.int2Bytes(bodyByte)); //13-17位,音视频数据长度
59 | byte[] tempb = bb.array();
60 | bb.put(ByteUtil.getCheckCode(tempb));
61 | //数据字节数组
62 | if (bodyLength != 0) {
63 | bb.put(sendBody.getBytes());
64 | }
65 | if (sendBuffer.length != 0) {
66 | bb.put(sendBuffer);
67 | }
68 | return bb.array();
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/TcpConnection.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.screenimage.configuration.VideoConfiguration;
6 | import com.test.screenimage.entity.ReceiveData;
7 | import com.test.screenimage.stream.sender.sendqueue.ISendQueue;
8 | import com.test.screenimage.stream.sender.tcp.interf.OnTcpReadListener;
9 | import com.test.screenimage.stream.sender.tcp.interf.OnTcpWriteListener;
10 | import com.test.screenimage.stream.sender.tcp.interf.TcpConnectListener;
11 |
12 |
13 | import java.io.BufferedInputStream;
14 | import java.io.BufferedOutputStream;
15 | import java.io.IOException;
16 | import java.net.InetSocketAddress;
17 | import java.net.Socket;
18 | import java.net.SocketAddress;
19 |
20 | /**
21 | * Created by wt
22 | * Date on 2018/5/28
23 | *
24 | * @Desc socket/tcp 连接
25 | */
26 |
27 | public class TcpConnection implements OnTcpReadListener, OnTcpWriteListener {
28 | private TcpConnectListener listener;
29 | private static final String TAG = "TcpConnection";
30 | private Socket socket;
31 | private ISendQueue mSendQueue;
32 | private TcpWriteThread mWrite;
33 | private TcpReadThread mRead;
34 | private BufferedInputStream in;
35 | private BufferedOutputStream out;
36 | private int width, height;
37 | private int maxBps;
38 | private int fps;
39 | private byte[] mSpsPps;
40 |
41 |
42 | public void setConnectListener(TcpConnectListener listener) {
43 | this.listener = listener;
44 | }
45 |
46 | public void setSendQueue(ISendQueue sendQueue) {
47 | mSendQueue = sendQueue;
48 | }
49 |
50 | //连接服务端
51 | public void connect(String ip, int port, int mainCmd, int subCmd, String sendBody) {
52 | socket = new Socket();
53 | SocketAddress socketAddress = new InetSocketAddress(ip, port);
54 | try {
55 | socket.connect(socketAddress, 20000);
56 | //tcp连接成功后is.read阻塞多长时间
57 | socket.setSoTimeout(60000);
58 | } catch (IOException e) {
59 | e.printStackTrace();
60 | if (listener != null) {
61 | listener.onSocketConnectFail(e.getMessage());
62 | return;
63 | }
64 | }
65 | listener.onSocketConnectSuccess();
66 | if (listener == null || socket == null || !socket.isConnected()) {
67 | listener.onSocketConnectFail(null);
68 |
69 | return;
70 | }
71 | try {
72 | // 获取当前连接的输出流
73 | out = new BufferedOutputStream(socket.getOutputStream());
74 | // 获取当前连接的输入流
75 | in = new BufferedInputStream(socket.getInputStream());
76 | mWrite = new TcpWriteThread(out, mSendQueue, mainCmd, subCmd, sendBody);
77 | mWrite.setOnTcpWriteThread(this);
78 | mRead = new TcpReadThread(in);
79 | mRead.setOnTcpReadListener(this);
80 | mRead.start();
81 | listener.onTcpConnectSuccess();
82 | mWrite.sendStartBuff();
83 | } catch (IOException e) {
84 | e.printStackTrace();
85 | listener.onTcpConnectFail(e.getMessage());
86 | }
87 | }
88 |
89 | public void setVideoParams(VideoConfiguration videoConfiguration) {
90 | width = videoConfiguration.width;
91 | height = videoConfiguration.height;
92 | fps = videoConfiguration.fps;
93 | maxBps = videoConfiguration.maxBps;
94 | }
95 |
96 | public void setSpsPps(byte[] spsPps) {
97 | this.mSpsPps = spsPps;
98 | }
99 |
100 | @Override
101 | public void socketDisconnect(String message) {
102 | //与服务端连接断开
103 | listener.onSocketDisconnect(message);
104 | }
105 |
106 | @Override
107 | public void netSpeedChange(String msg) {
108 | if (listener != null) listener.onNetSpeedChange(msg);
109 | }
110 |
111 | @Override
112 | public void connectSuccess(ReceiveData data) {
113 | //收到数据后,解析后得到数据
114 | Log.e(TAG, "connectSuccess: " + data.getHeader().getSubCmd());
115 | if (data == null) {
116 | return;
117 | }
118 | int subCmd = data.getHeader().getSubCmd();
119 | switch (subCmd) {
120 | case 0x01:
121 | //连接成功,开启发送线程
122 | mWrite.start();
123 | break;
124 | }
125 | }
126 |
127 |
128 | public void stop() {
129 | new Thread() {
130 | @Override
131 | public void run() {
132 | super.run();
133 | try {
134 | Log.i("TcpConnecttion", "stop");
135 | if (out != null) out.close();
136 | if (in != null) in.close();
137 | } catch (IOException e) {
138 | e.printStackTrace();
139 | }
140 | clearSocket();
141 | if (mWrite != null) {
142 | mWrite.setOnTcpWriteThread(null);
143 | mWrite.shutDown();
144 | }
145 | if (mRead != null) {
146 | mRead.setOnTcpReadListener(null);
147 | mRead.shutDown();
148 | }
149 | }
150 | }.start();
151 | }
152 |
153 | private void clearSocket() {
154 | if (socket != null && socket.isConnected()) {
155 | try {
156 | socket.close();
157 | Log.i(TAG, "socket close");
158 | socket = null;
159 | } catch (Exception e) {
160 | e.printStackTrace();
161 | }
162 | }
163 | }
164 |
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/TcpReadThread.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp;
2 |
3 | import android.os.SystemClock;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import com.test.screenimage.constant.ScreenImageApi;
8 | import com.test.screenimage.entity.ReceiveData;
9 | import com.test.screenimage.entity.ReceiveHeader;
10 | import com.test.screenimage.stream.sender.tcp.interf.OnTcpReadListener;
11 | import com.test.screenimage.utils.AnalyticDataUtils;
12 | import com.test.screenimage.utils.ByteUtil;
13 | import com.test.screenimage.utils.SopCastLog;
14 |
15 |
16 | import java.io.BufferedInputStream;
17 | import java.io.ByteArrayOutputStream;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 |
21 | /**
22 | * Created by wt
23 | * Date on 2018/5/28
24 | *
25 | * @Desc 处理流消息
26 | */
27 |
28 | public class TcpReadThread extends Thread implements AnalyticDataUtils.OnAnalyticDataListener {
29 | private final static String TAG = "wt";
30 | private BufferedInputStream bis;
31 | private AnalyticDataUtils mAnalyticDataUtils;
32 | private OnTcpReadListener mListener;
33 | private volatile boolean startFlag;
34 |
35 | public TcpReadThread(BufferedInputStream bis) {
36 | this.bis = bis;
37 | mAnalyticDataUtils = new AnalyticDataUtils();
38 | mAnalyticDataUtils.setOnAnalyticDataListener(this);
39 | startFlag = true;
40 | }
41 |
42 | public void setOnTcpReadListener(OnTcpReadListener listener) {
43 | this.mListener = listener;
44 | }
45 |
46 | @Override
47 | public void run() {
48 | super.run();
49 | while (startFlag) {
50 | SystemClock.sleep(50);
51 | try {
52 | acceptMsg();
53 | } catch (IOException e) {
54 | startFlag = false;
55 | if (mListener != null) mListener.socketDisconnect(e.getMessage());
56 | }
57 | }
58 | }
59 |
60 |
61 | // TODO: 2018/6/6 wt 接收到服务端发来的消息指令
62 | public void acceptMsg() throws IOException {
63 | if (mListener == null) {
64 | return;
65 | }
66 | if (bis.available() <= 0) {
67 | return;
68 | }
69 | byte[] header = mAnalyticDataUtils.readByte(bis, 18);
70 | //根据协议分析数据头
71 | ReceiveHeader receiveHeader = mAnalyticDataUtils.analysisHeader(header);
72 | if (receiveHeader.getStringBodylength() == 0 && receiveHeader.getBuffSize() == 0) {
73 | SopCastLog.e("wtt", "接收数据为空");
74 | return;
75 | }
76 | if (receiveHeader.getEncodeVersion() != ScreenImageApi.encodeVersion1) {
77 | SopCastLog.e("wtt", "收到的消息无法解析");
78 | return;
79 | }
80 | //解析数据
81 | mAnalyticDataUtils.analyticData(bis, receiveHeader);
82 | }
83 |
84 | /**
85 | * 停止读
86 | */
87 | public void shutDown() {
88 | startFlag = false;
89 | this.interrupt();
90 | }
91 |
92 | @Override
93 | public void onSuccess(ReceiveData data) {
94 | if (mListener != null) mListener.connectSuccess(data);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/TcpSender.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.test.screenimage.configuration.VideoConfiguration;
7 | import com.test.screenimage.entity.Frame;
8 | import com.test.screenimage.stream.packer.TcpPacker;
9 | import com.test.screenimage.stream.sender.OnSenderListener;
10 | import com.test.screenimage.stream.sender.Sender;
11 | import com.test.screenimage.stream.sender.sendqueue.ISendQueue;
12 | import com.test.screenimage.stream.sender.sendqueue.NormalSendQueue;
13 | import com.test.screenimage.stream.sender.sendqueue.SendQueueListener;
14 | import com.test.screenimage.stream.sender.tcp.interf.TcpConnectListener;
15 | import com.test.screenimage.utils.WeakHandler;
16 |
17 |
18 | /**
19 | * Created by wt
20 | * tcp发送
21 | */
22 |
23 | public class TcpSender implements Sender, SendQueueListener {
24 | private ISendQueue mSendQueue = new NormalSendQueue();
25 | private static final String TAG = "TcpSender";
26 | private OnSenderListener sendListener;
27 | private TcpConnection mTcpConnection;
28 | private WeakHandler weakHandler = new WeakHandler();
29 | private String ip;
30 | private int port;
31 | private int mainCmd;
32 | private int subCmd;
33 | //文本消息
34 | private String sendBody = null;
35 |
36 |
37 | public TcpSender(String ip, int port) {
38 | mTcpConnection = new TcpConnection();
39 | this.ip = ip;
40 | this.port = port;
41 | }
42 |
43 | public void setVideoParams(VideoConfiguration videoConfiguration) {
44 | mTcpConnection.setVideoParams(videoConfiguration);
45 | }
46 |
47 | // TODO: 2018/6/11 wt设置主指令
48 | public void setMianCmd(int mainCmd) {
49 | this.mainCmd = mainCmd;
50 | }
51 |
52 | // TODO: 2018/6/11 wt设置子指令
53 | public void setSubCmd(int subCmd) {
54 | this.subCmd = subCmd;
55 | }
56 |
57 | // TODO: 2018/6/11 wt设置要发送的文本内容
58 | public void setSendBody(String body) {
59 | this.sendBody = body;
60 | }
61 |
62 | // TODO: 2018/5/29 wt
63 | @Override
64 | public void onData(byte[] data, int type) {
65 | Frame frame = null;
66 | if (type == TcpPacker.FIRST_VIDEO) {
67 | frame = new Frame(data, type, Frame.FRAME_TYPE_CONFIGURATION);
68 | } else if (type == TcpPacker.KEY_FRAME) {
69 | frame = new Frame(data, type, Frame.FRAME_TYPE_KEY_FRAME);
70 | } else if (type == TcpPacker.INTER_FRAME) {
71 | frame = new Frame(data, type, Frame.FRAME_TYPE_INTER_FRAME);
72 | } else if (type == TcpPacker.AUDIO) {
73 | frame = new Frame(data, type, Frame.FRAME_TYPE_AUDIO);
74 | }
75 | if (frame == null) {
76 | return;
77 | }
78 | mSendQueue.putFrame(frame);
79 | }
80 |
81 | /**
82 | * 开启连接
83 | */
84 | public void openConnect() {
85 | //设置缓存队列
86 | mTcpConnection.setSendQueue(mSendQueue);
87 | new Thread(new Runnable() {
88 | @Override
89 | public void run() {
90 | connectNotInUi();
91 | }
92 | }).start();
93 |
94 | }
95 |
96 | @Override
97 | public void start() {
98 | mSendQueue.setSendQueueListener(this);
99 | mSendQueue.start();
100 | }
101 |
102 | private synchronized void connectNotInUi() {
103 | //设置连接回调
104 | mTcpConnection.setConnectListener(mTcpListener);
105 | //开始连接服务器
106 | mTcpConnection.connect(ip, port, mainCmd, subCmd, sendBody);
107 | }
108 |
109 | // TODO: 2018/6/4 监听回调
110 | private TcpConnectListener mTcpListener = new TcpConnectListener() {
111 | @Override
112 | public void onSocketConnectSuccess() {
113 | // connected();
114 | Log.e("wtt", "onSocketConnectSuccess");
115 | }
116 |
117 | @Override
118 | public void onSocketConnectFail(String message) {
119 | Log.e("wtt", "onSocketConnectFail: zzzz");
120 | weakHandler.post(new Runnable() {
121 | @Override
122 | public void run() {
123 | sendListener.onConnectFail(message);
124 | }
125 | });
126 | }
127 |
128 | @Override
129 | public void onTcpConnectSuccess() {
130 | connected();
131 | Log.e("wtt", "onTcpConnectSuccess");
132 | }
133 |
134 | @Override
135 | public void onTcpConnectFail(String message) {
136 | Log.e("wtt", "onTcpConnectFail: zzz");
137 | disConnected(message);
138 | }
139 |
140 | @Override
141 | public void onPublishSuccess() {
142 | //数据发送成功
143 | // connected();
144 | }
145 |
146 | @Override
147 | public void onPublishFail() {
148 | //数据发送失败
149 | weakHandler.post(new Runnable() {
150 | @Override
151 | public void run() {
152 | sendListener.onPublishFail();
153 | }
154 | });
155 | }
156 |
157 | @Override
158 | public void onSocketDisconnect(String message) {
159 | Log.e("wtt", "onSocketDisconnect: xxxx");
160 | disConnected(message);
161 | }
162 |
163 | @Override
164 | public void onNetSpeedChange(String msg) {
165 | weakHandler.post(new Runnable() {
166 | @Override
167 | public void run() {
168 | if (sendListener != null) sendListener.netSpeedChange(msg);
169 | }
170 | });
171 | }
172 |
173 | };
174 |
175 |
176 | @Override
177 | public void stop() {
178 | mTcpConnection.stop();
179 | mSendQueue.stop();
180 | }
181 |
182 |
183 | @Override
184 | public void good() {
185 | weakHandler.post(new Runnable() {
186 | @Override
187 | public void run() {
188 | //网络好
189 | sendListener.onNetGood();
190 | }
191 | });
192 | }
193 |
194 | @Override
195 | public void bad() {
196 | weakHandler.post(new Runnable() {
197 | @Override
198 | public void run() {
199 | //网络差
200 | sendListener.onNetBad();
201 | }
202 | });
203 | }
204 |
205 |
206 | // TODO: 2018/6/6 连接成功
207 | private void connected() {
208 | weakHandler.post(new Runnable() {
209 | @Override
210 | public void run() {
211 | sendListener.onConnected();
212 | }
213 | });
214 | }
215 |
216 |
217 | // TODO: 2018/6/6 连接失败
218 | private void disConnected(String message) {
219 | weakHandler.post(new Runnable() {
220 | @Override
221 | public void run() {
222 | sendListener.onDisConnected(message);
223 | }
224 | });
225 | }
226 |
227 | public void setSenderListener(OnSenderListener listener) {
228 | this.sendListener = listener;
229 | }
230 |
231 | /**
232 | * add by wt 为解决首次黑屏而加
233 | */
234 | public void setSpsPps(byte[] spsPps) {
235 | if (mTcpConnection != null) mTcpConnection.setSpsPps(spsPps);
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/TcpWriteThread.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.test.screenimage.entity.Frame;
7 | import com.test.screenimage.stream.sender.sendqueue.ISendQueue;
8 | import com.test.screenimage.stream.sender.tcp.interf.OnTcpWriteListener;
9 |
10 | import java.io.BufferedOutputStream;
11 | import java.io.IOException;
12 | import java.util.Timer;
13 | import java.util.TimerTask;
14 |
15 | /**
16 | * Created by wt
17 | * Date on 2018/5/28
18 | *
19 | * @Desc
20 | */
21 |
22 | public class TcpWriteThread extends Thread {
23 | private BufferedOutputStream bos;
24 | private ISendQueue iSendQueue;
25 | private volatile boolean startFlag;
26 | private int mainCmd;
27 | private int subCmd;
28 | private String sendBody;
29 | private OnTcpWriteListener mListener;
30 | private final String TAG = "TcpWriteThread";
31 | private volatile int readLength = 0;
32 | private Timer timer;
33 | private boolean isCalculate = false;
34 |
35 | /**
36 | * by wt
37 | *
38 | * @param bos 输入流
39 | * @param sendQueue
40 | * @param mainCmd 主指令
41 | * @param subCmd 子指令
42 | * @param sendBody 文本消息内容
43 | */
44 | public TcpWriteThread(BufferedOutputStream bos, ISendQueue sendQueue, int mainCmd,
45 | int subCmd, String sendBody) {
46 | this.bos = bos;
47 | this.iSendQueue = sendQueue;
48 | this.mainCmd = mainCmd;
49 | this.subCmd = subCmd;
50 | this.sendBody = sendBody;
51 | startFlag = true;
52 | }
53 |
54 | public void setOnTcpWriteThread(OnTcpWriteListener listener) {
55 | this.mListener = listener;
56 | }
57 |
58 | @Override
59 | public void run() {
60 | super.run();
61 | startNetSpeedCalculate();
62 | while (startFlag) {
63 | Frame frame = iSendQueue.takeFrame();
64 | if (frame == null) {
65 | continue;
66 | }
67 | // TODO: 2018/5/29 wt修改
68 | if (frame.data.length != 0) {
69 | sendData(frame.data);
70 | }
71 | }
72 | }
73 |
74 | // TODO: 2018/6/4 wt 发送数据
75 | public void sendData(byte[] buff) {
76 | try {
77 | byte[] sendBuff = new EncodeV1(mainCmd, subCmd, sendBody, buff).buildSendContent();
78 | if (isCalculate) readLength += sendBuff.length;
79 | bos.write(sendBuff);
80 | bos.flush();
81 | // Log.e(TAG,"send data ");
82 | } catch (IOException e) {
83 | startFlag = false;
84 | if (mListener != null) mListener.socketDisconnect(e.getMessage());
85 | }
86 | }
87 |
88 | /**
89 | * 停止写
90 | */
91 | public void shutDown() {
92 | mListener = null;
93 | isCalculate = false;
94 | try {
95 | if (timer != null) timer.cancel();
96 | } catch (Exception e) {
97 | }
98 | startFlag = false;
99 | this.interrupt();
100 | }
101 |
102 | public void sendStartBuff() {
103 | sendData(new byte[0]);
104 | }
105 |
106 | public void startNetSpeedCalculate() {
107 | try {
108 | if (timer != null) timer.cancel();
109 | } catch (Exception e) {
110 | }
111 | readLength = 0;
112 | isCalculate = true;
113 | timer = new Timer();
114 | timer.schedule(new TimerTask() {
115 | @Override
116 | public void run() {
117 | if (mListener != null) {
118 | mListener.netSpeedChange((readLength / 1024) + " kb/s");
119 | readLength = 0;
120 | }
121 | }
122 | }, 1000, 1000);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/interf/OnTcpReadListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp.interf;
2 |
3 | import com.test.screenimage.entity.ReceiveData;
4 | import com.test.screenimage.entity.ReceiveHeader;
5 |
6 | /**
7 | * Created by wt
8 | * Date on 2018/5/28
9 | *
10 | * @Desc 从tcp read thread中的回调
11 | */
12 |
13 | public interface OnTcpReadListener {
14 |
15 | void socketDisconnect(String message); //断开连接
16 |
17 | //收到server消息,连接成功
18 | //date:解析后的数据包
19 | void connectSuccess(ReceiveData data);
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/interf/OnTcpWriteListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp.interf;
2 |
3 | /**
4 | * Created by wt
5 | * Date on 2018/5/28
6 | * 从tcp Write thread中的回调
7 | */
8 |
9 | public interface OnTcpWriteListener {
10 | //断开连接
11 | void socketDisconnect(String message);
12 |
13 | void netSpeedChange(String msg);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/tcp/interf/TcpConnectListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.tcp.interf;
2 |
3 | /**
4 | * Created by wt
5 | * Date on 2018/5/28
6 | *
7 | * @Desc 连接监听
8 | */
9 |
10 | public interface TcpConnectListener {
11 | //socket连接成功
12 | void onSocketConnectSuccess();
13 |
14 | //socket连接失败
15 | void onSocketConnectFail(String message);
16 |
17 | //tcp连接成功
18 | void onTcpConnectSuccess();
19 |
20 | //tcp连接失败
21 | void onTcpConnectFail(String message);
22 |
23 | //发送成功
24 | void onPublishSuccess();
25 |
26 | //发送失败
27 | void onPublishFail();
28 |
29 | //socket断开连接
30 | void onSocketDisconnect(String message);
31 |
32 | void onNetSpeedChange(String msg);
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/udp/UDPClientThread.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.udp;
2 |
3 | import android.os.Message;
4 | import android.util.Log;
5 |
6 | import com.test.screenimage.MyApplication;
7 | import com.test.screenimage.stream.sender.udp.interf.OnUdpConnectListener;
8 |
9 | import java.io.IOException;
10 | import java.net.DatagramPacket;
11 | import java.net.InetAddress;
12 | import java.net.MulticastSocket;
13 |
14 | /**
15 | * Created by wt on 2018/7/11.
16 | * 基于udp的组网连接
17 | */
18 | public class UDPClientThread extends Thread {
19 | static final String BROADCAST_IP = "224.0.0.1";
20 | //监听的端口号
21 | static final int BROADCAST_PORT = 15000;
22 | private InetAddress inetAddress = null;
23 | //服务端的局域网IP
24 | private static String ip;
25 | private OnUdpConnectListener mListener;
26 |
27 | public UDPClientThread(OnUdpConnectListener listener) {
28 | this.mListener = listener;
29 | this.start();
30 | }
31 |
32 | @Override
33 | public void run() {
34 | MulticastSocket multicastSocket = null;//多点广播套接字
35 | try {
36 | /**
37 | * 1.实例化MulticastSocket对象,并指定端口
38 | * 2.加入广播地址,MulticastSocket使用public void joinGroup(InetAddress mcastaddr)
39 | * 3.开始接收广播
40 | * 4.关闭广播
41 | */
42 | multicastSocket = new MulticastSocket(BROADCAST_PORT);
43 | inetAddress = InetAddress.getByName(BROADCAST_IP);
44 | Log.e("UdpClientThread", "udp server start");
45 | multicastSocket.joinGroup(inetAddress);
46 | byte buf[] = new byte[1024];
47 | DatagramPacket dp = new DatagramPacket(buf, buf.length);
48 | while (true) {
49 | multicastSocket.receive(dp);
50 | Log.e("UdpClientThread", "receive a msg");
51 | ip = new String(buf, 0, dp.getLength());
52 | multicastSocket.leaveGroup(inetAddress);
53 | multicastSocket.close();
54 | MyApplication.mHandler.post(new Runnable() {
55 | @Override
56 | public void run() {
57 | mListener.udpConnectSuccess(ip);
58 | }
59 | });
60 | }
61 | } catch (Exception e) {
62 | MyApplication.mHandler.post(new Runnable() {
63 | @Override
64 | public void run() {
65 | mListener.udpDisConnec(e.getMessage());
66 | }
67 | });
68 | } finally {
69 | Log.e("UdpClientThread", "udp server close");
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/stream/sender/udp/interf/OnUdpConnectListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.stream.sender.udp.interf;
2 |
3 | /**
4 | * Created by wt on 2018/7/11.
5 | */
6 | public interface OnUdpConnectListener {
7 | void udpConnectSuccess(String ip);
8 | void udpDisConnec(String message);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/AnalyticDataUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.util.Log;
4 |
5 | import com.test.screenimage.entity.ReceiveData;
6 | import com.test.screenimage.entity.ReceiveHeader;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 |
12 | /**
13 | * Created by wt on 2018/6/12.
14 | * 根据协议解析数据
15 | */
16 | public class AnalyticDataUtils {
17 | private OnAnalyticDataListener mListener;
18 |
19 | // TODO: 2018/6/11 wt处理协议相应指令,把消息内容解析出来
20 | public void analyticData(InputStream is, ReceiveHeader receiveHeader) throws IOException {
21 | byte[] sendBody = null;
22 | byte[] buff = null;
23 | //文本长度
24 | if (receiveHeader.getStringBodylength() != 0) {
25 | sendBody = readByte(is, receiveHeader.getStringBodylength());
26 | }
27 | //音视频长度
28 | if (receiveHeader.getBuffSize() != 0) {
29 | buff = readByte(is, receiveHeader.getBuffSize());
30 | }
31 | ReceiveData data = new ReceiveData();
32 | data.setHeader(receiveHeader);
33 | data.setSendBody(sendBody == null ? "" : new String(sendBody));
34 | data.setBuff(buff);
35 | if (mListener != null) mListener.onSuccess(data);
36 | }
37 |
38 |
39 | /**
40 | * 保证从流里读到指定长度数据
41 | *
42 | * @param is
43 | * @param readSize
44 | * @return
45 | * @throws IOException
46 | */
47 | public byte[] readByte(InputStream is, int readSize) throws IOException {
48 | byte[] buff = new byte[readSize];
49 | int len = 0;
50 | int eachLen = 0;
51 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
52 | while (len < readSize) {
53 | eachLen = is.read(buff);
54 | if (eachLen != -1) {
55 | len += eachLen;
56 | baos.write(buff, 0, eachLen);
57 | } else {
58 | baos.close();
59 | throw new IOException();
60 | }
61 | if (len < readSize) {
62 | buff = new byte[readSize - len];
63 | }
64 | }
65 | byte[] b = baos.toByteArray();
66 | baos.close();
67 | return b;
68 | }
69 | /**
70 | * 实现数组之间的复制,分析数据头
71 | * bytes:源数组
72 | * srcPos:源数组要复制的起始位置
73 | * dest:目的数组
74 | * destPos:目的数组放置的起始位置
75 | * length:复制的长度
76 | */
77 | public ReceiveHeader analysisHeader(byte[] header) {
78 | byte[] buff = new byte[4];
79 | System.arraycopy(header, 1, buff, 0, 4);
80 | final short mainCmd = ByteUtil.bytesToShort(buff); //主指令 1`5
81 | buff = new byte[4];
82 | System.arraycopy(header, 5, buff, 0, 4);
83 | final short subCmd = ByteUtil.bytesToShort(buff); //子指令 5`9
84 | buff = new byte[4];
85 | System.arraycopy(header, 9, buff, 0, 4);
86 | int stringBodyLength = ByteUtil.bytesToInt(buff);//文本数据 9 ~ 13;
87 | buff = new byte[4];
88 | System.arraycopy(header, 13, buff, 0, 4);
89 | int byteBodySize = ByteUtil.bytesToInt(buff);//byte数据 13^17
90 | return new ReceiveHeader(mainCmd, subCmd, header[0], stringBodyLength, byteBodySize);
91 | }
92 |
93 | /**
94 | * 分离解析头数据
95 | * @param is
96 | * @param receiveHeader
97 | * @return
98 | * @throws IOException
99 | */
100 | public ReceiveData synchAnalyticData(InputStream is, ReceiveHeader receiveHeader) throws IOException {
101 | byte[] sendBody = null;
102 | byte[] buff = null;
103 | //文本长度
104 | if (receiveHeader.getStringBodylength() != 0) {
105 | sendBody = readByte(is, receiveHeader.getStringBodylength());
106 | }
107 | //音视频长度
108 | if (receiveHeader.getBuffSize() != 0) {
109 | buff = readByte(is, receiveHeader.getBuffSize());
110 | }
111 | ReceiveData data = new ReceiveData();
112 | data.setHeader(receiveHeader);
113 | data.setSendBody(sendBody == null ? "" : new String(sendBody));
114 | Log.e("wtt", "analyticData: " + new String(sendBody));
115 | data.setBuff(buff);
116 | return data;
117 | }
118 | // TODO: 2018/6/12 wt回调解析后数据
119 | public interface OnAnalyticDataListener {
120 | void onSuccess(ReceiveData data);
121 |
122 | }
123 |
124 | public void setOnAnalyticDataListener(OnAnalyticDataListener listener) {
125 | this.mListener = listener;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/AudioUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.media.AudioFormat;
6 | import android.media.AudioManager;
7 | import android.media.AudioRecord;
8 | import android.media.MediaRecorder;
9 | import android.util.Log;
10 |
11 | import com.test.screenimage.configuration.AudioConfiguration;
12 |
13 |
14 | public class AudioUtils {
15 | // public static boolean checkMicSupport(AudioConfiguration audioConfiguration) {
16 | // boolean result;
17 | // int recordBufferSize = getRecordBufferSize(audioConfiguration);
18 | // byte[] mRecordBuffer = new byte[recordBufferSize];
19 | // AudioRecord audioRecord = getAudioRecord(audioConfiguration);
20 | // try {
21 | // audioRecord.startRecording();
22 | // } catch (Exception e) {
23 | // e.printStackTrace();
24 | // }
25 | // int readLen = audioRecord.read(mRecordBuffer, 0, recordBufferSize);
26 | // result = readLen >= 0;
27 | // try {
28 | // audioRecord.release();
29 | // } catch (Exception e) {
30 | // e.printStackTrace();
31 | // }
32 | // return result;
33 | // }
34 |
35 |
36 | @TargetApi(18)
37 | public static AudioRecord getAudioRecord(AudioConfiguration audioConfiguration,Context context)
38 | {
39 | int frequency = audioConfiguration.frequency;
40 | int audioEncoding = audioConfiguration.encoding;
41 | //单声道(定义采样通道)
42 | int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
43 | if(audioConfiguration.channelCount == 2) {
44 | channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
45 | }
46 | //音频源:麦克风
47 | int audioSource = MediaRecorder.AudioSource.MIC;
48 | if(audioConfiguration.aec) {
49 | //对麦克风中类似ip通话的交流声音进行识别,默认会开启回声消除和自动增益
50 | audioSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
51 | }
52 | // TODO: 2018/6/5 wt 消除回声
53 | AudioManager audioManager = (AudioManager)context.getSystemService(context.AUDIO_SERVICE);
54 | audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
55 | audioManager.setSpeakerphoneOn(false);
56 | audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 0,
57 | AudioManager.STREAM_VOICE_CALL);
58 | //音频源,采样率,声道数,采样位数,缓冲区大小
59 | Log.e("wtt", "getAudioRecord: "+ getRecordBufferSize(audioConfiguration) );
60 | AudioRecord audioRecord = new AudioRecord(audioSource, frequency,
61 | channelConfiguration, audioEncoding, getRecordBufferSize(audioConfiguration));
62 | return audioRecord;
63 | }
64 |
65 | // TODO: 2018/7/16 wt 内部的音频缓冲区的大小
66 | public static int getRecordBufferSize(AudioConfiguration audioConfiguration) {
67 | int frequency = audioConfiguration.frequency;
68 | int audioEncoding = audioConfiguration.encoding;
69 | int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
70 | if(audioConfiguration.channelCount == 2) {
71 | channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
72 | }
73 | int size = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
74 | return size;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/BatteryUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Build;
7 | import android.os.PowerManager;
8 | import android.provider.Settings;
9 | import android.support.annotation.RequiresApi;
10 |
11 | import static android.content.Context.POWER_SERVICE;
12 |
13 | /**
14 | * Created by wt
15 | * Date on 2017/12/13 15:20:46.
16 | *
17 | * @Desc 检查设置忽略电池优化.
18 | */
19 |
20 | public class BatteryUtils {
21 |
22 | @RequiresApi(api = Build.VERSION_CODES.M)
23 | public static void ignoreBatteryOptimization(Context context) {
24 | PowerManager powerManager = (PowerManager) context.getApplicationContext().getSystemService(POWER_SERVICE);
25 | boolean hasIgnored = powerManager.isIgnoringBatteryOptimizations(context.getPackageName());
26 | // 判断当前APP是否有加入电池优化的白名单,如果没有,弹出加入电池优化的白名单的设置对话框。
27 | if (!hasIgnored) {
28 | Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
29 | intent.setData(Uri.parse("package:" + context.getPackageName()));
30 | context.startActivity(intent);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/BlackListHelper.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.os.Build;
4 | import android.text.TextUtils;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 |
10 |
11 | public class BlackListHelper {
12 | private static final String[] BLACKLISTED_AEC_MODELS = new String[] {
13 | "Nexus 5", // Nexus 5
14 | };
15 |
16 | private static final String[] BLACKLISTED_FPS_MODELS = new String[] {
17 | "OPPO R9",
18 | "Nexus 6P",
19 | };
20 |
21 |
22 | public static boolean deviceInAecBlacklisted() {
23 | List blackListedModels = Arrays.asList(BLACKLISTED_AEC_MODELS);
24 | for(String blackModel : blackListedModels) {
25 | String model = Build.MODEL;
26 | if(!TextUtils.isEmpty(model) && model.contains(blackModel)) {
27 | return true;
28 | }
29 | }
30 | return false;
31 | }
32 |
33 | public static boolean deviceInFpsBlacklisted() {
34 | List blackListedModels = Arrays.asList(BLACKLISTED_FPS_MODELS);
35 | for(String blackModel : blackListedModels) {
36 | String model = Build.MODEL;
37 | if(!TextUtils.isEmpty(model) && model.contains(blackModel)) {
38 | return true;
39 | }
40 | }
41 | return false;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/ByteUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | /**
4 | * Created by wt
5 | * Date on 2018/5/28
6 | */
7 | public class ByteUtil {
8 | /**
9 | * 将int转为长度为4的byte数组
10 | *
11 | * @param length
12 | * @return
13 | */
14 | public static byte[] int2Bytes(int length) {
15 | byte[] result = new byte[4];
16 | result[0] = (byte) length;
17 | result[1] = (byte) (length >> 8);
18 | result[2] = (byte) (length >> 16);
19 | result[3] = (byte) (length >> 24);
20 | return result;
21 | }
22 |
23 | //转成2个字节
24 | public static byte[] short2Bytes(short size) {
25 | byte[] result = new byte[2];
26 | result[0] = (byte) size;
27 | result[1] = (byte) (size >> 8);
28 | return result;
29 | }
30 | /**
31 | * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用
32 | *
33 | * @param src byte数组
34 | * @return int数值
35 | */
36 | public static int bytesToInt(byte[] src) {
37 | int value;
38 | value = (int) ((src[0] & 0xFF)
39 | | ((src[1] & 0xFF) << 8)
40 | | ((src[2] & 0xFF) << 16)
41 | | ((src[3] & 0xFF) << 24));
42 | return value;
43 | }
44 |
45 | // TODO: 2018/6/11 wt byte转short
46 | public static short bytesToShort(byte[] src) {
47 | short value;
48 | value = (short) ((src[0] & 0xFF)
49 | | ((src[1] & 0xFF) << 8));
50 | return value;
51 | }
52 |
53 |
54 | /**
55 | * 获得校验码
56 | *
57 | * @param bytes 根据通讯协议的前12个字节
58 | * @return
59 | */
60 | public static byte getCheckCode(byte[] bytes) {
61 | byte b = 0x00;
62 | for (int i = 0; i < bytes.length; i++) {
63 | b ^= bytes[i];
64 | }
65 | return b;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/DialogUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.support.v7.app.AlertDialog;
8 | import android.content.DialogInterface.OnClickListener;
9 | import android.view.View;
10 |
11 | /**
12 | * Created by wt on 2018/6/25.
13 | * 弹出框工具类
14 | */
15 | public class DialogUtils {
16 |
17 | private static DialogUtils instance = null;
18 |
19 | public static DialogUtils getInstance() {
20 | if (instance == null) {
21 | instance = new DialogUtils();
22 | }
23 | return instance;
24 | }
25 |
26 | public void showDialog(Context context, String titleInfo,String message,String postBtnName,
27 | String navBtnName,
28 | final DialogUtils.DialogCallBack callBack) {
29 | AlertDialog.Builder alterDialog = new AlertDialog.Builder(context);
30 | alterDialog.setTitle(titleInfo);
31 | alterDialog.setMessage(message);
32 | alterDialog.setCancelable(true);
33 |
34 | alterDialog.setPositiveButton(postBtnName, new DialogInterface.OnClickListener() {
35 | @Override
36 | public void onClick(DialogInterface dialog, int which) {
37 | callBack.exectEvent();
38 | }
39 | });
40 | alterDialog.setNegativeButton(navBtnName, new DialogInterface.OnClickListener() {
41 | @Override
42 | public void onClick(DialogInterface dialog, int which) {
43 | dialog.cancel();
44 | }
45 | });
46 | alterDialog.show();
47 | }
48 |
49 | public interface DialogCallBack {
50 | void exectEvent();
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/SopCastLog.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * 打印log
7 | */
8 | public class SopCastLog {
9 | private static boolean open = true;
10 |
11 | public static void isOpen(boolean isOpen) {
12 | open = isOpen;
13 | }
14 |
15 | public static void d(String tag, String msg) {
16 | if(open) {
17 | Log.d(tag, msg);
18 | }
19 | }
20 |
21 | public static void w(String tag, String msg) {
22 | if(open) {
23 | Log.w(tag, msg);
24 | }
25 | }
26 |
27 | public static void e(String tag, String msg) {
28 | if(open) {
29 | Log.e(tag, msg);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/SopCastUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.os.Build;
4 | import android.os.Looper;
5 |
6 |
7 | public class SopCastUtils {
8 |
9 | public interface INotUIProcessor {
10 | void process();
11 | }
12 |
13 | public static void processNotUI(final INotUIProcessor processor) {
14 | if(Looper.myLooper() == Looper.getMainLooper()) {
15 | new Thread(new Runnable() {
16 | @Override
17 | public void run() {
18 | processor.process();
19 | }
20 | }).start();
21 | } else {
22 | processor.process();
23 | }
24 | }
25 |
26 | public static boolean isOverLOLLIPOP() {
27 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/StatusBarUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.graphics.Color;
6 | import android.os.Build;
7 | import android.view.View;
8 | import android.view.Window;
9 | import android.view.WindowManager;
10 |
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Method;
13 |
14 | /**
15 | * Created by wt on 2017/4/22.
16 | */
17 |
18 | public class StatusBarUtil {
19 | /**
20 | * 修改状态栏为全透明
21 | * @param activity
22 | */
23 | @TargetApi(19)
24 | public static void transparencyBar(Activity activity){
25 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
26 | Window window = activity.getWindow();
27 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
28 | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
29 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
30 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
31 | window.setStatusBarColor(Color.TRANSPARENT);
32 |
33 | } else
34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
35 | Window window =activity.getWindow();
36 | window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
37 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
38 | }
39 | }
40 |
41 |
42 |
43 | /**
44 | *设置状态栏黑色字体图标,
45 | * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
46 | * @param activity
47 | * @return 1:MIUUI 2:Flyme 3:android6.0
48 | */
49 | public static int StatusBarLightMode(Activity activity){
50 | int result=0;
51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
52 | if(MIUISetStatusBarLightMode(activity.getWindow(), true)){
53 | result=1;
54 | }else if(FlymeSetStatusBarLightMode(activity.getWindow(), true)){
55 | result=2;
56 | }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
57 | activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
58 | result=3;
59 | }
60 | }
61 | return result;
62 | }
63 |
64 | /**
65 | * 已知系统类型时,设置状态栏黑色字体图标。
66 | * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
67 | * @param activity
68 | * @param type 1:MIUUI 2:Flyme 3:android6.0
69 | */
70 | public static void StatusBarLightMode(Activity activity,int type){
71 | if(type==1){
72 | MIUISetStatusBarLightMode(activity.getWindow(), true);
73 | }else if(type==2){
74 | FlymeSetStatusBarLightMode(activity.getWindow(), true);
75 | }else if(type==3){
76 | activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
77 | }
78 |
79 | }
80 |
81 | /**
82 | * 清除MIUI或flyme或6.0以上版本状态栏黑色字体
83 | */
84 | public static void StatusBarDarkMode(Activity activity,int type){
85 | if(type==1){
86 | MIUISetStatusBarLightMode(activity.getWindow(), false);
87 | }else if(type==2){
88 | FlymeSetStatusBarLightMode(activity.getWindow(), false);
89 | }else if(type==3){
90 | activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
91 | }
92 |
93 | }
94 |
95 |
96 | /**
97 | * 设置状态栏图标为深色和魅族特定的文字风格
98 | * 可以用来判断是否为Flyme用户
99 | * @param window 需要设置的窗口
100 | * @param dark 是否把状态栏字体及图标颜色设置为深色
101 | * @return boolean 成功执行返回true
102 | *
103 | */
104 | public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
105 | boolean result = false;
106 | if (window != null) {
107 | try {
108 | WindowManager.LayoutParams lp = window.getAttributes();
109 | Field darkFlag = WindowManager.LayoutParams.class
110 | .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
111 | Field meizuFlags = WindowManager.LayoutParams.class
112 | .getDeclaredField("meizuFlags");
113 | darkFlag.setAccessible(true);
114 | meizuFlags.setAccessible(true);
115 | int bit = darkFlag.getInt(null);
116 | int value = meizuFlags.getInt(lp);
117 | if (dark) {
118 | value |= bit;
119 | } else {
120 | value &= ~bit;
121 | }
122 | meizuFlags.setInt(lp, value);
123 | window.setAttributes(lp);
124 | result = true;
125 | } catch (Exception e) {
126 |
127 | }
128 | }
129 | return result;
130 | }
131 |
132 | /**
133 | * 设置状态栏字体图标为深色,需要MIUIV6以上
134 | * @param window 需要设置的窗口
135 | * @param dark 是否把状态栏字体及图标颜色设置为深色
136 | * @return boolean 成功执行返回true
137 | *
138 | */
139 | public static boolean MIUISetStatusBarLightMode(Window window, boolean dark) {
140 | boolean result = false;
141 | if (window != null) {
142 | Class clazz = window.getClass();
143 | try {
144 | int darkModeFlag = 0;
145 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
146 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
147 | darkModeFlag = field.getInt(layoutParams);
148 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
149 | if(dark){
150 | extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//状态栏透明且黑色字体
151 | }else{
152 | extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
153 | }
154 | result=true;
155 | }catch (Exception e){
156 |
157 | }
158 | }
159 | return result;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/SupportMultipleScreensUtil.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.DisplayMetrics;
7 | import android.util.Log;
8 | import android.util.TypedValue;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.ViewGroup.LayoutParams;
12 | import android.view.ViewGroup.MarginLayoutParams;
13 | import android.widget.TextView;
14 |
15 | import com.test.screenimage.R;
16 |
17 |
18 | /**
19 | * 关于适配
20 | */
21 | public class SupportMultipleScreensUtil {
22 |
23 | public static final int BASE_SCREEN_WIDTH = 1080;
24 | public static final int BASE_SCREEN_HEIGHT = 1920;
25 | public static final float BASE_SCREEN_WIDTH_FLOAT = 1080F;
26 | public static final float BASE_SCREEN_HEIGHT_FLOAT = 1920F;
27 | public static float scale = 1.0F;
28 |
29 | public SupportMultipleScreensUtil() {
30 |
31 | }
32 |
33 | public static void init(Context context) {
34 | Resources resources=context.getResources();
35 | DisplayMetrics displayMetrics = resources.getDisplayMetrics();
36 | int widthPixels = displayMetrics.widthPixels;
37 | Log.e("123", "init: "+widthPixels+"<<<<<<<"+displayMetrics.heightPixels );
38 | scale = (float)widthPixels / BASE_SCREEN_WIDTH_FLOAT;
39 | }
40 |
41 |
42 | public static void scale(View view) {
43 | if(null != view) {
44 | if(view instanceof ViewGroup) {
45 | scaleViewGroup((ViewGroup)view);
46 | } else {
47 | scaleView(view);
48 | }
49 | }
50 | }
51 |
52 | private static void scaleView(View view) {
53 | Object isScale = view.getTag(R.id.is_scale_size_tag);
54 | if (!(isScale instanceof Boolean) || !((Boolean) isScale).booleanValue()) {
55 | if (view instanceof TextView) {
56 | scaleTextView((TextView) view);
57 | } else {
58 | scaleViewSize(view);
59 | }
60 | view.setTag(R.id.is_scale_size_tag, Boolean.valueOf(true));
61 | }
62 | }
63 |
64 |
65 |
66 | private static void scaleViewGroup(ViewGroup viewGroup) {
67 | for (int i = 0; i < viewGroup.getChildCount(); ++i) {
68 | View view = viewGroup.getChildAt(i);
69 | if (view instanceof ViewGroup) {
70 | scaleViewGroup((ViewGroup) view);
71 | }
72 | scaleView(view);
73 | }
74 | }
75 |
76 | public static void scaleViewSize(View view) {
77 | if (null != view) {
78 | int paddingLeft = getScaleValue(view.getPaddingLeft());
79 | int paddingTop = getScaleValue(view.getPaddingTop());
80 | int paddingRight = getScaleValue(view.getPaddingRight());
81 | int paddingBottom = getScaleValue(view.getPaddingBottom());
82 | view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
83 |
84 | LayoutParams layoutParams = view.getLayoutParams();
85 |
86 | if (null != layoutParams) {
87 |
88 | if (layoutParams.width > 0) {
89 | layoutParams.width = getScaleValue(layoutParams.width);
90 | }
91 |
92 | if (layoutParams.height > 0) {
93 | layoutParams.height = getScaleValue(layoutParams.height);
94 | }
95 |
96 | if (layoutParams instanceof MarginLayoutParams) {
97 | MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;
98 | int topMargin = getScaleValue(marginLayoutParams.topMargin);
99 | int leftMargin = getScaleValue(marginLayoutParams.leftMargin);
100 | int bottomMargin = getScaleValue(marginLayoutParams.bottomMargin);
101 | int rightMargin = getScaleValue(marginLayoutParams.rightMargin);
102 | marginLayoutParams.topMargin = topMargin;
103 | marginLayoutParams.leftMargin = leftMargin;
104 | marginLayoutParams.bottomMargin = bottomMargin;
105 | marginLayoutParams.rightMargin = rightMargin;
106 | }
107 | }
108 | view.setLayoutParams(layoutParams);
109 | }
110 | }
111 |
112 | private static void setTextViewCompoundDrawables(TextView textView, Drawable leftDrawable, Drawable topDrawable, Drawable rightDrawable, Drawable bottomDrawable) {
113 | if(null != leftDrawable) {
114 | scaleDrawableBounds(leftDrawable);
115 | }
116 |
117 | if(null != rightDrawable) {
118 | scaleDrawableBounds(rightDrawable);
119 | }
120 |
121 | if(null != topDrawable) {
122 | scaleDrawableBounds(topDrawable);
123 | }
124 |
125 | if(null != bottomDrawable) {
126 | scaleDrawableBounds(bottomDrawable);
127 | }
128 |
129 | textView.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
130 | }
131 |
132 | public static Drawable scaleDrawableBounds(Drawable drawable) {
133 | int right=getScaleValue(drawable.getIntrinsicWidth());
134 | int bottom=getScaleValue(drawable.getIntrinsicHeight());
135 | drawable.setBounds(0, 0, right, bottom);
136 | return drawable;
137 | }
138 |
139 | public static void scaleTextView(TextView textView) {
140 | if (null != textView) {
141 |
142 | scaleViewSize(textView);
143 |
144 | Object isScale = textView.getTag(R.id.is_scale_font_tag);
145 | if (!(isScale instanceof Boolean) || !((Boolean) isScale).booleanValue()) {
146 | float size = textView.getTextSize();
147 | size *= scale;
148 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
149 | }
150 |
151 | Drawable[] drawables = textView.getCompoundDrawables();
152 | Drawable leftDrawable = drawables[0];
153 | Drawable topDrawable = drawables[1];
154 | Drawable rightDrawable = drawables[2];
155 | Drawable bottomDrawable = drawables[3];
156 | setTextViewCompoundDrawables(textView, leftDrawable, topDrawable, rightDrawable, bottomDrawable);
157 | int compoundDrawablePadding = getScaleValue(textView.getCompoundDrawablePadding());
158 | textView.setCompoundDrawablePadding(compoundDrawablePadding);
159 | }
160 | }
161 |
162 | public static int getScaleValue(int value) {
163 | return value <= 2?value:(int)Math.ceil((double)(scale * (float)value));
164 | }
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/ToastUtils.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.utils;
2 |
3 | /**
4 | * Created by wt on 2016/6/20.
5 | */
6 | import android.content.Context;
7 | import android.text.TextUtils;
8 | import android.widget.Toast;
9 |
10 | public class ToastUtils
11 | {
12 | public static boolean isShow = true;
13 | private static Toast mToast;
14 |
15 | public static void show(Context paramContext, int paramInt1, int paramInt2)
16 | {
17 | if (isShow)
18 | Toast.makeText(paramContext, paramInt1, paramInt2).show();
19 | }
20 |
21 | public static void show(Context paramContext, CharSequence paramCharSequence, int paramInt)
22 | {
23 | if (isShow)
24 | Toast.makeText(paramContext, paramCharSequence, paramInt).show();
25 | }
26 |
27 | public static void showLong(Context paramContext, int paramInt)
28 | {
29 | if (isShow)
30 | Toast.makeText(paramContext, paramInt, Toast.LENGTH_LONG).show();
31 | }
32 |
33 | public static void showLong(Context paramContext, CharSequence paramCharSequence)
34 | {
35 | if (isShow)
36 | Toast.makeText(paramContext, paramCharSequence, Toast.LENGTH_LONG).show();
37 | }
38 |
39 | public static void showShort(Context paramContext, int paramInt)
40 | {
41 | if (isShow)
42 | Toast.makeText(paramContext, paramInt, Toast.LENGTH_SHORT).show();
43 | }
44 |
45 | public static void showShort(Context paramContext, CharSequence paramCharSequence)
46 | {
47 | if (isShow)
48 | Toast.makeText(paramContext, paramCharSequence, Toast.LENGTH_SHORT).show();
49 | }
50 |
51 | public static void showToast(Context paramContext, String paramString)
52 | {
53 | if ((TextUtils.isEmpty(paramString)) || (paramContext == null))
54 | return;
55 | Toast.makeText(paramContext, paramString, Toast.LENGTH_SHORT).show();
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/utils/WeakHandler.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.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 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/video/OnVideoEncodeListener.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.video;
2 |
3 | import android.media.MediaCodec;
4 |
5 | import java.nio.ByteBuffer;
6 |
7 | public interface OnVideoEncodeListener {
8 | void onVideoEncode(ByteBuffer bb, MediaCodec.BufferInfo bi);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/widget/CustomDialog.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.widget;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import android.text.TextUtils;
7 | import android.view.Display;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.View.OnClickListener;
11 | import android.view.WindowManager;
12 | import android.widget.Button;
13 | import android.widget.EditText;
14 | import android.widget.FrameLayout;
15 | import android.widget.ImageView;
16 | import android.widget.LinearLayout;
17 | import android.widget.LinearLayout.LayoutParams;
18 | import android.widget.TextView;
19 |
20 | import com.test.screenimage.R;
21 | import com.test.screenimage.utils.SupportMultipleScreensUtil;
22 |
23 |
24 | public class CustomDialog {
25 | private Context context;
26 | private Dialog dialog;
27 | private LinearLayout lLayout_bg;
28 | private TextView txt_title;
29 | private TextView txt_msg;
30 | private Button btn_neg;
31 | private Button btn_pos;
32 | private ImageView img_line;
33 | private Display display;
34 | private boolean showTitle = false;
35 | private boolean showMsg = false;
36 | private boolean showPosBtn = false;
37 | private boolean showNegBtn = false;
38 | private boolean showEtMsg = false;
39 | private EditText et_msg;
40 |
41 | public CustomDialog(Context context) {
42 | this.context = context;
43 | WindowManager windowManager = (WindowManager) context
44 | .getSystemService(Context.WINDOW_SERVICE);
45 | display = windowManager.getDefaultDisplay();
46 | }
47 |
48 | @SuppressWarnings("deprecation")
49 | public CustomDialog builder() {
50 | // 获取Dialog布局
51 | View view = LayoutInflater.from(context).inflate(R.layout.view_alertdialog, null);
52 | SupportMultipleScreensUtil.scale(view);
53 | // 获取自定义Dialog布局中的控件
54 | lLayout_bg = (LinearLayout) view.findViewById(R.id.lLayout_bg);
55 | txt_title = (TextView) view.findViewById(R.id.txt_title);
56 | txt_title.setVisibility(View.GONE);
57 | txt_msg = (TextView) view.findViewById(R.id.txt_msg);
58 | txt_msg.setVisibility(View.GONE);
59 | btn_neg = (Button) view.findViewById(R.id.btn_neg);
60 | btn_neg.setVisibility(View.GONE);
61 | btn_pos = (Button) view.findViewById(R.id.btn_pos);
62 | btn_pos.setVisibility(View.GONE);
63 | img_line = (ImageView) view.findViewById(R.id.img_line);
64 | et_msg = (EditText) view.findViewById(R.id.et_msg);
65 | img_line.setVisibility(View.GONE);
66 |
67 | // 定义Dialog布局和参数
68 | dialog = new Dialog(context, R.style.AlertDialogStyle);
69 | dialog.setContentView(view);
70 |
71 | // 调整dialog背景大小
72 | lLayout_bg.setLayoutParams(new FrameLayout.LayoutParams((int) (display.getWidth() * 0.75),
73 | LayoutParams.WRAP_CONTENT));
74 |
75 | return this;
76 | }
77 |
78 | public CustomDialog setTitle(String title) {
79 | showTitle = true;
80 | if ("".equals(title) || title == null) {
81 | showTitle = false;
82 | txt_title.setText("标题");
83 | } else {
84 | txt_title.setText(title);
85 | }
86 | return this;
87 | }
88 |
89 | // 重载函数 增加对ResId资源的支持
90 | public CustomDialog setTitle(int titleId) {
91 | String title = context.getResources().getString(titleId);
92 | showTitle = true;
93 | if ("".equals(title)) {
94 | txt_title.setText("标题");
95 | } else {
96 | txt_title.setText(title);
97 | }
98 | return this;
99 | }
100 |
101 | public CustomDialog setMessage(CharSequence msg) {
102 | showMsg = true;
103 | if ("".equals(msg)) {
104 | txt_msg.setText("内容");
105 | } else {
106 | txt_msg.setText(msg);
107 | }
108 | return this;
109 | }
110 |
111 | // 重载函数 增加对ResId资源的支持
112 | public CustomDialog setMessage(int msgId) {
113 | String msg = context.getResources().getString(msgId);
114 | showMsg = true;
115 | if ("".equals(msg)) {
116 | txt_msg.setText("内容");
117 | } else {
118 | txt_msg.setText(msg);
119 | }
120 | return this;
121 | }
122 |
123 | public CustomDialog setCancelable(boolean cancel) {
124 | dialog.setCancelable(cancel);
125 | return this;
126 | }
127 |
128 | public CustomDialog setPositiveButton(String text, final OnClickListener listener) {
129 | showPosBtn = true;
130 | if ("".equals(text)) {
131 | btn_pos.setText("确定");
132 | } else {
133 | btn_pos.setText(text);
134 | }
135 | btn_pos.setOnClickListener(new OnClickListener() {
136 | @Override
137 | public void onClick(View v) {
138 | listener.onClick(v);
139 | dialog.dismiss();
140 | }
141 | });
142 | return this;
143 | }
144 |
145 | // 重载函数 增加对ResId资源的支持
146 | public CustomDialog setPositiveButton(int textId, final OnClickListener listener) {
147 | String text = context.getResources().getString(textId);
148 | showPosBtn = true;
149 | if ("".equals(text)) {
150 | btn_pos.setText("确定");
151 | } else {
152 | btn_pos.setText(text);
153 | }
154 | btn_pos.setOnClickListener(new OnClickListener() {
155 | @Override
156 | public void onClick(View v) {
157 | listener.onClick(v);
158 | dialog.dismiss();
159 | }
160 | });
161 | return this;
162 | }
163 |
164 | public CustomDialog setNegativeButton(String text, final OnClickListener listener) {
165 | showNegBtn = true;
166 | if ("".equals(text)) {
167 | btn_neg.setText("取消");
168 | } else {
169 | btn_neg.setText(text);
170 | }
171 | btn_neg.setOnClickListener(new OnClickListener() {
172 | @Override
173 | public void onClick(View v) {
174 | listener.onClick(v);
175 | dialog.dismiss();
176 | }
177 | });
178 | return this;
179 | }
180 |
181 | // 重载函数 增加对ResId资源的支持
182 | public CustomDialog setNegativeButton(int textId, final OnClickListener listener) {
183 | String text = context.getResources().getString(textId);
184 | showNegBtn = true;
185 | if ("".equals(text)) {
186 | btn_neg.setText("取消");
187 | } else {
188 | btn_neg.setText(text);
189 | }
190 | btn_neg.setOnClickListener(new OnClickListener() {
191 | @Override
192 | public void onClick(View v) {
193 | listener.onClick(v);
194 | dialog.dismiss();
195 | }
196 | });
197 | return this;
198 | }
199 |
200 | public CustomDialog setEtMsg(String text) {
201 | showEtMsg = true;
202 | if (!TextUtils.isEmpty(text)) {
203 | et_msg.setText(text);
204 | et_msg.setSelection(text.length());
205 | }
206 | return this;
207 | }
208 |
209 | public String getEtMsg() {
210 | return et_msg.getText().toString();
211 | }
212 |
213 | private void setLayout() {
214 | if (!showTitle) {
215 | // txt_title.setText("提示");
216 | txt_title.setVisibility(View.GONE);
217 | }
218 |
219 | if (showTitle) {
220 | txt_title.setVisibility(View.VISIBLE);
221 | }
222 |
223 | if (showMsg) {
224 | txt_msg.setVisibility(View.VISIBLE);
225 | }
226 |
227 | if (showEtMsg) {
228 | et_msg.setVisibility(View.VISIBLE);
229 | }
230 | if (!showPosBtn && !showNegBtn) {
231 | btn_pos.setText("确定");
232 | btn_pos.setVisibility(View.VISIBLE);
233 | btn_pos.setOnClickListener(new OnClickListener() {
234 | @Override
235 | public void onClick(View v) {
236 | dialog.dismiss();
237 | }
238 | });
239 | }
240 |
241 | if (showPosBtn && showNegBtn) {
242 | btn_pos.setVisibility(View.VISIBLE);
243 | btn_neg.setVisibility(View.VISIBLE);
244 | img_line.setVisibility(View.VISIBLE);
245 | }
246 |
247 | if (showPosBtn && !showNegBtn) {
248 | btn_pos.setVisibility(View.VISIBLE);
249 |
250 | }
251 |
252 | if (!showPosBtn && showNegBtn) {
253 | btn_neg.setVisibility(View.VISIBLE);
254 |
255 | }
256 | }
257 |
258 | public void setPosBackground(int color) {
259 | btn_pos.setBackgroundColor(color);
260 | }
261 |
262 | public EditText getEt_msg() {
263 | return et_msg;
264 | }
265 |
266 | public void show() {
267 | setLayout();
268 | dialog.show();
269 | }
270 |
271 | public void dismiss() {
272 | if (dialog == null) return;
273 | dialog.dismiss();
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/app/src/main/java/com/test/screenimage/widget/LoadingDialog.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage.widget;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import android.view.Gravity;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.WindowManager.LayoutParams;
10 |
11 | import com.test.screenimage.R;
12 | import com.test.screenimage.utils.SupportMultipleScreensUtil;
13 |
14 | /**
15 | * 加载对话框控件
16 | *
17 | * @author wt
18 | * @version V1.0 创建时间:2017-3-7
19 | */
20 | public class LoadingDialog extends Dialog {
21 | private LayoutParams lp;
22 | private LayoutInflater inflater;
23 | private Context mContext;
24 |
25 | public LoadingDialog(Context context, int theme) {
26 | super(context, theme);
27 | this.mContext = context;
28 | }
29 |
30 | public LoadingDialog(Context context) {
31 | super(context, R.style.MyLoadDialog);
32 | this.mContext = context;
33 | inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
34 | View layout = inflater.inflate(R.layout.loadingdialog, null);
35 | SupportMultipleScreensUtil.scale(layout);
36 | setContentView(layout);
37 | // 设置window属性
38 | lp = getWindow().getAttributes();
39 | lp.gravity = Gravity.CENTER;
40 | lp.dimAmount = 0; // 去背景遮盖
41 | lp.alpha = 1.0f;
42 | getWindow().setAttributes(lp);
43 | setCanceledOnTouchOutside(true);
44 | setCancelable(true);
45 | }
46 |
47 | @Override
48 | public void onBackPressed() {
49 | super.onBackPressed();
50 | this.dismiss();
51 | }
52 |
53 | @Override
54 | public void dismiss() {
55 | super.dismiss();
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_client.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_lamp_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_lamp_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_lamp_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_lamp_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_lamp_press.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_lamp_press.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_loading.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_network.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable-nodpi/ic_server.png
--------------------------------------------------------------------------------
/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/bg_recommend.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/dialog_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/f_new_image_progress_popbox.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/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/drawable/scan_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/drawable/scan_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/select_lamp_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
26 |
27 |
33 |
34 |
41 |
42 |
43 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_scan_qr_code.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
17 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_screen_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/custom_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loadingdialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_alertdialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
23 |
36 |
37 |
52 |
53 |
58 |
59 |
63 |
64 |
73 |
74 |
79 |
80 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/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/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/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 | #9099af
7 | #000000
8 | #318CE7
9 | #ffffff
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ScreenImage
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
20 |
21 |
22 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/test/java/com/test/screenimage/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.test.screenimage;
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() {
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.1.2'
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 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtt123/ScreenImage/0fef35e3491004431639aadc5176a3d2341bf309/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 04 13:34:42 CST 2018
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.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------