Skip to content

Commit

Permalink
ImageGenerator update
Browse files Browse the repository at this point in the history
  • Loading branch information
dkrivoruchko committed May 2, 2017
1 parent eacb289 commit 32a606b
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 98 deletions.
22 changes: 19 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'

android {
compileSdkVersion 25
buildToolsVersion "25.0.3"

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

defaultConfig {
applicationId "info.dvkr.screenstream"
minSdkVersion 21
targetSdkVersion 25
versionCode 22
versionName "1.2.6"
versionCode 23
versionName "1.2.7"
resConfigs "en", "ru"
}

buildTypes {
release {
buildConfigField "Boolean", "DEBUG_MODE", "false"
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
buildConfigField "Boolean", "DEBUG_MODE", "true"
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
Expand All @@ -37,9 +46,16 @@ android {
dependencies {
compile 'com.android.support:design:25.3.1'
compile 'com.google.firebase:firebase-crash:10.2.4'
compile 'org.greenrobot:eventbus:3.0.0'

compile "io.reactivex.rxjava2:rxjava:2.1.0"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

compile 'org.greenrobot:eventbus:3.0.0'
compile 'com.jrummyapps:colorpicker:2.1.7'

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}

apply plugin: 'com.google.gms.google-services'
4 changes: 4 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
# public *;
#}

# Retrolambda
-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*

# EventBus 3.0
-keepattributes *Annotation*
-keepclassmembers class * {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package info.dvkr.screenstream;

import android.app.Application;
import android.util.Log;

import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;

import java.io.IOException;
import java.net.SocketException;

import info.dvkr.screenstream.data.AppData;
import info.dvkr.screenstream.data.local.PreferencesHelper;
import info.dvkr.screenstream.service.ForegroundService;
import info.dvkr.screenstream.viewModel.MainActivityViewModel;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.plugins.RxJavaPlugins;


public class ScreenStreamApplication extends Application {
Expand All @@ -15,11 +24,46 @@ public class ScreenStreamApplication extends Application {
private MainActivityViewModel mMainActivityViewModel;
private PreferencesHelper mPreferencesHelper;

private RefWatcher refWatcher;

@Override
public void onCreate() {
super.onCreate();
sAppInstance = this;

if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
refWatcher = LeakCanary.install(this);

RxJavaPlugins.setErrorHandler(e -> {
if (e instanceof UndeliverableException) {
e = e.getCause();
}
if ((e instanceof IOException) || (e instanceof SocketException)) {
// fine, irrelevant network problem or API that throws on cancellation
return;
}
if (e instanceof InterruptedException) {
// fine, some blocking code was interrupted by a dispose call
return;
}
if ((e instanceof NullPointerException) || (e instanceof IllegalArgumentException)) {
// that's likely a bug in the application
Log.e(">>>>>> ", "likely a bug in the application", e);
return;
}
if (e instanceof IllegalStateException) {
// that's a bug in RxJava or in a custom operator
Log.e(">>>>>>", "a bug in RxJava or in a custom operator", e);
return;
}
Log.e(">>>>>>", "Undeliverable exception received, not sure what to do", e);
});


mAppData = new AppData(this);
mMainActivityViewModel = new MainActivityViewModel(this);
mPreferencesHelper = new PreferencesHelper(this);
Expand All @@ -38,4 +82,8 @@ public static AppData getAppData() {
public static PreferencesHelper getAppPreference() {
return sAppInstance.mPreferencesHelper;
}

public static RefWatcher getRafWatcher() {
return sAppInstance.refWatcher;
}
}
142 changes: 73 additions & 69 deletions app/src/main/java/info/dvkr/screenstream/data/ImageGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.util.Log;

import com.google.firebase.crash.FirebaseCrash;

import org.greenrobot.eventbus.EventBus;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import info.dvkr.screenstream.data.local.PreferencesHelper;
import info.dvkr.screenstream.service.ForegroundService;
Expand All @@ -26,137 +27,140 @@
import static info.dvkr.screenstream.ScreenStreamApplication.getAppPreference;

public final class ImageGenerator {
private static final String TAG = ImageGenerator.class.getSimpleName();
private final Object mLock = new Object();

private volatile boolean isThreadRunning;
private final AtomicBoolean isPrepared = new AtomicBoolean();

private HandlerThread mImageThread;
private Handler mImageHandler;
private ImageReader mImageReader;
private VirtualDisplay mVirtualDisplay;
private Bitmap mReusableBitmap;
private ByteArrayOutputStream mJpegOutputStream;

private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
private Image mImage;
private Image.Plane mPlane;
private int mWidth;
private Bitmap mCleanBitmap;
private byte[] mJpegByteArray;
private final ByteArrayOutputStream mJpegOutputStream = new ByteArrayOutputStream();
private final Matrix mMatrix;

ImageAvailableListener(final Matrix matrix) {
this.mMatrix = matrix;
}

@Override
public void onImageAvailable(ImageReader reader) {
public void onImageAvailable(final ImageReader reader) {
synchronized (mLock) {
if (!isThreadRunning) return;
// Log.e(TAG, "ImageGenerator.onImageAvailable Tread: " + Thread.currentThread().getName());

if (!isPrepared.get()) return;

final Image image;
try {
mImage = mImageReader.acquireLatestImage();
} catch (UnsupportedOperationException e) {
image = reader.acquireLatestImage();
} catch (UnsupportedOperationException e) { // TODO use onError
EventBus.getDefault().postSticky(new BusMessages(BusMessages.MESSAGE_STATUS_IMAGE_GENERATOR_ERROR));
FirebaseCrash.report(e);
return;
}
if (image == null) return;

if (mImage == null) return;

mPlane = mImage.getPlanes()[0];
mWidth = mPlane.getRowStride() / mPlane.getPixelStride();
final Image.Plane plane = image.getPlanes()[0];
final int width = plane.getRowStride() / plane.getPixelStride();

if (mWidth > mImage.getWidth()) {
final Bitmap cleanBitmap;
if (width > image.getWidth()) {
if (mReusableBitmap == null) {
mReusableBitmap = Bitmap.createBitmap(mWidth, mImage.getHeight(), Bitmap.Config.ARGB_8888);
mReusableBitmap = Bitmap.createBitmap(width, image.getHeight(), Bitmap.Config.ARGB_8888);
}
mReusableBitmap.copyPixelsFromBuffer(mPlane.getBuffer());
mCleanBitmap = Bitmap.createBitmap(mReusableBitmap, 0, 0, mImage.getWidth(), mImage.getHeight());
mReusableBitmap.copyPixelsFromBuffer(plane.getBuffer());
cleanBitmap = Bitmap.createBitmap(mReusableBitmap, 0, 0, image.getWidth(), image.getHeight());
} else {
mCleanBitmap = Bitmap.createBitmap(mImage.getWidth(), mImage.getHeight(), Bitmap.Config.ARGB_8888);
mCleanBitmap.copyPixelsFromBuffer(mPlane.getBuffer());
cleanBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
cleanBitmap.copyPixelsFromBuffer(plane.getBuffer());
}

Bitmap resizedBitmap;
if (getAppPreference().getResizeFactor() != PreferencesHelper.DEFAULT_RESIZE_FACTOR) {
float scale = getAppPreference().getResizeFactor() / 10f;
final Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
resizedBitmap = Bitmap.createBitmap(mCleanBitmap, 0, 0, mImage.getWidth(), mImage.getHeight(), matrix, false);
mCleanBitmap.recycle();
final Bitmap resizedBitmap;
if (mMatrix.isIdentity()) {
resizedBitmap = cleanBitmap;
} else {
resizedBitmap = mCleanBitmap;
resizedBitmap = Bitmap.createBitmap(cleanBitmap, 0, 0, image.getWidth(), image.getHeight(), mMatrix, false);
cleanBitmap.recycle();
}
mImage.close();
image.close();

mJpegOutputStream.reset();
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, getAppPreference().getJpegQuality(), mJpegOutputStream);
resizedBitmap.recycle();
mJpegByteArray = mJpegOutputStream.toByteArray();
final byte[] jpegByteArray = mJpegOutputStream.toByteArray();

if (mJpegByteArray != null) {
if (jpegByteArray != null) { // TODO use onNext
if (getAppData().getImageQueue().size() > 3) {
getAppData().getImageQueue().pollLast();
}
getAppData().getImageQueue().add(mJpegByteArray);
mJpegByteArray = null;
getAppData().getImageQueue().add(jpegByteArray);
}
}
}
}

public void start() {
public void start() throws IllegalStateException {
synchronized (mLock) {
if (isThreadRunning) return;
Log.e(TAG, "ImageGenerator start: " + Thread.currentThread().getName());

if (isPrepared.get())
throw new IllegalStateException("ImageGenerator is already running");

final MediaProjection mediaProjection = ForegroundService.getMediaProjection();
if (mediaProjection == null) return;
if (mediaProjection == null) throw new IllegalStateException("MediaProjection is null");

mImageThread = new HandlerThread(ImageGenerator.class.getSimpleName(),
Process.THREAD_PRIORITY_MORE_FAVORABLE);
final Matrix matrix = new Matrix();
if (getAppPreference().getResizeFactor() != PreferencesHelper.DEFAULT_RESIZE_FACTOR) {
final float scale = getAppPreference().getResizeFactor() / 10f;
matrix.postScale(scale, scale);
}

mImageThread = new HandlerThread(ImageGenerator.class.getSimpleName(), Process.THREAD_PRIORITY_MORE_FAVORABLE);
mImageThread.start();
mImageReader = ImageReader.newInstance(getAppData().getScreenSize().x,
getAppData().getScreenSize().y,
PixelFormat.RGBA_8888, 2);

mImageHandler = new Handler(mImageThread.getLooper());
mJpegOutputStream = new ByteArrayOutputStream();
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mImageHandler);
mImageReader = ImageReader.newInstance(getAppData().getScreenSize().x, getAppData().getScreenSize().y, PixelFormat.RGBA_8888, 2);
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(matrix), new Handler(mImageThread.getLooper()));
mVirtualDisplay = mediaProjection.createVirtualDisplay("ScreenStreamVirtualDisplay",
getAppData().getScreenSize().x,
getAppData().getScreenSize().y,
getAppData().getScreenDensity(),
getAppData().getScreenSize().x, getAppData().getScreenSize().y, getAppData().getScreenDensity(),
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(),
null, mImageHandler);
null, null);

isThreadRunning = true;
isPrepared.set(true);
}
}

public void stop() {
public void stop() throws IllegalStateException {
synchronized (mLock) {
if (!isThreadRunning) return;
Log.e(TAG, "ImageGenerator stop: " + Thread.currentThread().getName());

// final RefWatcher refWatcher = getRafWatcher();
// refWatcher.watch(mImageReader);

mImageReader.setOnImageAvailableListener(null, null);
mImageReader.close();
mImageReader = null;
if (!isPrepared.get()) throw new IllegalStateException("ImageGenerator is not running");
isPrepared.set(false);

try {
mJpegOutputStream.close();
} catch (IOException e) {
FirebaseCrash.report(e);
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}

mVirtualDisplay.release();
mVirtualDisplay = null;
if (mImageReader != null) {
mImageReader.setOnImageAvailableListener(null, null);
mImageReader.close();
mImageReader = null;
}

mImageHandler.removeCallbacksAndMessages(null);
mImageThread.quit();
mImageThread = null;
if (mImageThread != null) {
mImageThread.quit();
mImageThread = null;
}

if (mReusableBitmap != null) {
mReusableBitmap.recycle();
mReusableBitmap = null;
}

isThreadRunning = false;
}
}
}
Loading

0 comments on commit 32a606b

Please sign in to comment.