Compare commits
1 Commits
content-re
...
opengl_fil
Author | SHA1 | Date | |
---|---|---|---|
4397dfba89 |
@ -77,7 +77,6 @@ _scrcpy() {
|
||||
--rotation=
|
||||
-s --serial=
|
||||
-S --turn-screen-off
|
||||
--screen-off-timeout=
|
||||
--shortcut-mod=
|
||||
--start-app=
|
||||
-t --show-touches
|
||||
|
@ -80,7 +80,6 @@ arguments=(
|
||||
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||
'--screen-off-timeout=[Set the screen off timeout in seconds]'
|
||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||
'--start-app=[Start an Android app]'
|
||||
{-t,--show-touches}'[Show physical touches]'
|
||||
|
@ -671,10 +671,6 @@ Pause or re-pause display
|
||||
.B MOD+Shift+z
|
||||
Unpause display
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+r
|
||||
Reset video capture/encoding
|
||||
|
||||
.TP
|
||||
.B MOD+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
|
@ -288,7 +288,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
|
||||
// Enable compensation when the difference exceeds +/- 4ms.
|
||||
// Disable compensation when the difference is lower than +/- 1ms.
|
||||
int threshold = ar->compensation_active
|
||||
int threshold = ar->compensation != 0
|
||||
? ar->sample_rate / 1000 /* 1ms */
|
||||
: ar->sample_rate * 4 / 1000; /* 4ms */
|
||||
|
||||
@ -309,12 +309,14 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
||||
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
} else {
|
||||
ar->compensation_active = diff != 0;
|
||||
if (diff != ar->compensation) {
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
} else {
|
||||
ar->compensation = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +392,7 @@ sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
||||
atomic_init(&ar->played, false);
|
||||
atomic_init(&ar->received, false);
|
||||
atomic_init(&ar->underflow, 0);
|
||||
ar->compensation_active = false;
|
||||
ar->compensation = 0;
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -44,8 +44,8 @@ struct sc_audio_regulator {
|
||||
// Number of silence samples inserted since the last received packet
|
||||
atomic_uint_least32_t underflow;
|
||||
|
||||
// Non-zero compensation applied (only used by the receiver thread)
|
||||
bool compensation_active;
|
||||
// Current applied compensation value (only used by the receiver thread)
|
||||
int compensation;
|
||||
|
||||
// Set to true the first time a sample is received
|
||||
atomic_bool received;
|
||||
|
@ -106,7 +106,6 @@ enum {
|
||||
OPT_NEW_DISPLAY,
|
||||
OPT_LIST_APPS,
|
||||
OPT_START_APP,
|
||||
OPT_SCREEN_OFF_TIMEOUT,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -794,13 +793,6 @@ static const struct sc_option options[] = {
|
||||
.longopt = "turn-screen-off",
|
||||
.text = "Turn the device screen off immediately.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_SCREEN_OFF_TIMEOUT,
|
||||
.longopt = "screen-off-timeout",
|
||||
.argdesc = "seconds",
|
||||
.text = "Set the screen off timeout while scrcpy is running (restore "
|
||||
"the initial value on exit).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_SHORTCUT_MOD,
|
||||
.longopt = "shortcut-mod",
|
||||
@ -1030,10 +1022,6 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.shortcuts = { "MOD+Shift+z" },
|
||||
.text = "Unpause display",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+r" },
|
||||
.text = "Reset video capture/encoding",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+g" },
|
||||
.text = "Resize window to 1:1 (pixel-perfect)",
|
||||
@ -2163,20 +2151,6 @@ parse_time_limit(const char *s, sc_tick *tick) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_screen_off_timeout(const char *s, sc_tick *tick) {
|
||||
long value;
|
||||
// value in seconds, but must fit in 31 bits in milliseconds
|
||||
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF / 1000,
|
||||
"screen off timeout");
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*tick = SC_TICK_FROM_SEC(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
||||
if (!s || !strcmp(s, "true")) {
|
||||
@ -2752,12 +2726,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_START_APP:
|
||||
opts->start_app = optarg;
|
||||
break;
|
||||
case OPT_SCREEN_OFF_TIMEOUT:
|
||||
if (!parse_screen_off_timeout(optarg,
|
||||
&opts->screen_off_timeout)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
@ -181,7 +181,6 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
@ -305,9 +304,6 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_START_APP:
|
||||
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
LOG_CMSG("reset video");
|
||||
break;
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
|
@ -42,7 +42,6 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_START_APP,
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
|
@ -284,18 +284,6 @@ open_hard_keyboard_settings(struct sc_input_manager *im) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reset_video(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request reset video");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
apply_orientation_transform(struct sc_input_manager *im,
|
||||
enum sc_orientation transform) {
|
||||
@ -533,12 +521,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
reset_video(im);
|
||||
} else {
|
||||
rotate_device(im);
|
||||
}
|
||||
if (control && !shift && !repeat && down && !paused) {
|
||||
rotate_device(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
|
@ -62,7 +62,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.audio_buffer = -1, // depends on the audio format,
|
||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||
.time_limit = 0,
|
||||
.screen_off_timeout = -1,
|
||||
#ifdef HAVE_V4L2
|
||||
.v4l2_device = NULL,
|
||||
.v4l2_buffer = 0,
|
||||
|
@ -265,7 +265,6 @@ struct scrcpy_options {
|
||||
sc_tick audio_buffer;
|
||||
sc_tick audio_output_buffer;
|
||||
sc_tick time_limit;
|
||||
sc_tick screen_off_timeout;
|
||||
#ifdef HAVE_V4L2
|
||||
const char *v4l2_device;
|
||||
sc_tick v4l2_buffer;
|
||||
|
@ -428,7 +428,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.video_bit_rate = options->video_bit_rate,
|
||||
.audio_bit_rate = options->audio_bit_rate,
|
||||
.max_fps = options->max_fps,
|
||||
.screen_off_timeout = options->screen_off_timeout,
|
||||
.lock_video_orientation = options->lock_video_orientation,
|
||||
.control = options->control,
|
||||
.display_id = options->display_id,
|
||||
|
@ -320,11 +320,6 @@ execute_server(struct sc_server *server,
|
||||
if (params->stay_awake) {
|
||||
ADD_PARAM("stay_awake=true");
|
||||
}
|
||||
if (params->screen_off_timeout != -1) {
|
||||
assert(params->screen_off_timeout >= 0);
|
||||
uint64_t ms = SC_TICK_TO_MS(params->screen_off_timeout);
|
||||
ADD_PARAM("screen_off_timeout=%" PRIu64, ms);
|
||||
}
|
||||
if (params->video_codec_options) {
|
||||
VALIDATE_STRING(params->video_codec_options);
|
||||
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||
|
@ -45,7 +45,6 @@ struct sc_server_params {
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
const char *max_fps; // float to be parsed by the server
|
||||
sc_tick screen_off_timeout;
|
||||
int8_t lock_video_orientation;
|
||||
bool control;
|
||||
uint32_t display_id;
|
||||
|
@ -407,21 +407,6 @@ static void test_serialize_open_hard_keyboard(void) {
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_reset_video(void) {
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
};
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 1);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@ -444,6 +429,5 @@ int main(int argc, char *argv[]) {
|
||||
test_serialize_uhid_input();
|
||||
test_serialize_uhid_destroy();
|
||||
test_serialize_open_hard_keyboard();
|
||||
test_serialize_reset_video();
|
||||
return 0;
|
||||
}
|
||||
|
@ -71,31 +71,6 @@ adb shell cmd display power-on 0
|
||||
```
|
||||
|
||||
|
||||
## Screen off timeout
|
||||
|
||||
The Android screen automatically turns off after some delay.
|
||||
|
||||
To change this delay while scrcpy is running:
|
||||
|
||||
```bash
|
||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
||||
```
|
||||
|
||||
The initial value is restored on exit.
|
||||
|
||||
It is possible to change this setting manually:
|
||||
|
||||
```bash
|
||||
# get the current screen_off_timeout value
|
||||
adb shell settings get system screen_off_timeout
|
||||
# set a new value (in milliseconds)
|
||||
adb shell settings put system screen_off_timeout 30000
|
||||
```
|
||||
|
||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
||||
argument is in seconds.
|
||||
|
||||
|
||||
## Show touches
|
||||
|
||||
For presentations, it may be useful to show physical touches (on the physical
|
||||
|
@ -30,7 +30,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
||||
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
||||
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
||||
| Reset video capture/encoding | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>r</kbd>
|
||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||
|
@ -50,11 +50,6 @@ cd "$SERVER_DIR/src/main/aidl"
|
||||
android/content/IOnPrimaryClipChangedListener.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
||||
|
||||
# Fake sources to expose hidden Android types to the project
|
||||
FAKE_SRC=( \
|
||||
android/content/*java \
|
||||
)
|
||||
|
||||
SRC=( \
|
||||
com/genymobile/scrcpy/*.java \
|
||||
com/genymobile/scrcpy/audio/*.java \
|
||||
@ -73,11 +68,10 @@ done
|
||||
|
||||
echo "Compiling java sources..."
|
||||
cd ../java
|
||||
javac -encoding UTF-8 -bootclasspath "$ANDROID_JAR" \
|
||||
javac -bootclasspath "$ANDROID_JAR" \
|
||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||
-d "$CLASSES_DIR" \
|
||||
-source 1.8 -target 1.8 \
|
||||
${FAKE_SRC[@]} \
|
||||
${SRC[@]}
|
||||
|
||||
echo "Dexing..."
|
||||
|
@ -1,5 +0,0 @@
|
||||
package android.content;
|
||||
|
||||
public interface IContentProvider {
|
||||
// android.content.IContentProvider is hidden, this is a fake one to expose the type to the project
|
||||
}
|
@ -5,8 +5,6 @@ import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.Settings;
|
||||
import com.genymobile.scrcpy.util.SettingsException;
|
||||
|
||||
import android.os.BatteryManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@ -18,131 +16,59 @@ import java.io.OutputStream;
|
||||
*/
|
||||
public final class CleanUp {
|
||||
|
||||
// Dynamic options
|
||||
private static final int PENDING_CHANGE_DISPLAY_POWER = 1 << 0;
|
||||
private int pendingChanges;
|
||||
private boolean pendingRestoreDisplayPower;
|
||||
private static final int MSG_TYPE_MASK = 0b11;
|
||||
private static final int MSG_TYPE_RESTORE_STAY_ON = 0;
|
||||
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1;
|
||||
private static final int MSG_TYPE_RESTORE_DISPLAY_POWER = 2;
|
||||
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;
|
||||
|
||||
private Thread thread;
|
||||
private static final int MSG_PARAM_SHIFT = 2;
|
||||
|
||||
private CleanUp(int displayId, Options options) {
|
||||
thread = new Thread(() -> runCleanUp(displayId, options), "cleanup");
|
||||
thread.start();
|
||||
private final OutputStream out;
|
||||
|
||||
public CleanUp(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public static CleanUp start(int displayId, Options options) {
|
||||
return new CleanUp(displayId, options);
|
||||
}
|
||||
|
||||
public void interrupt() {
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public void join() throws InterruptedException {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
private void runCleanUp(int displayId, Options options) {
|
||||
boolean disableShowTouches = false;
|
||||
if (options.getShowTouches()) {
|
||||
try {
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||
// If "show touches" was disabled, it must be disabled back on clean up
|
||||
disableShowTouches = !"1".equals(oldValue);
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not change \"show_touches\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
int restoreStayOn = -1;
|
||||
if (options.getStayAwake()) {
|
||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||
try {
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||
try {
|
||||
int currentStayOn = Integer.parseInt(oldValue);
|
||||
// Restore only if the current value is different
|
||||
if (currentStayOn != stayOn) {
|
||||
restoreStayOn = currentStayOn;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
int restoreScreenOffTimeout = -1;
|
||||
int screenOffTimeout = options.getScreenOffTimeout();
|
||||
if (screenOffTimeout != -1) {
|
||||
try {
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(screenOffTimeout));
|
||||
try {
|
||||
int currentScreenOffTimeout = Integer.parseInt(oldValue);
|
||||
// Restore only if the current value is different
|
||||
if (currentScreenOffTimeout != screenOffTimeout) {
|
||||
restoreScreenOffTimeout = currentScreenOffTimeout;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not change \"screen_off_timeout\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean powerOffScreen = options.getPowerOffScreenOnClose();
|
||||
|
||||
try {
|
||||
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
} catch (IOException e) {
|
||||
Ln.e("Clean up I/O exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
|
||||
throws IOException, InterruptedException {
|
||||
String[] cmd = {
|
||||
"app_process",
|
||||
"/",
|
||||
CleanUp.class.getName(),
|
||||
String.valueOf(displayId),
|
||||
String.valueOf(restoreStayOn),
|
||||
String.valueOf(disableShowTouches),
|
||||
String.valueOf(powerOffScreen),
|
||||
String.valueOf(restoreScreenOffTimeout),
|
||||
};
|
||||
public static CleanUp configure(int displayId) throws IOException {
|
||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)};
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
||||
Process process = builder.start();
|
||||
OutputStream out = process.getOutputStream();
|
||||
return new CleanUp(process.getOutputStream());
|
||||
}
|
||||
|
||||
while (true) {
|
||||
int localPendingChanges;
|
||||
boolean localPendingRestoreDisplayPower;
|
||||
synchronized (this) {
|
||||
while (pendingChanges == 0) {
|
||||
wait();
|
||||
}
|
||||
localPendingChanges = pendingChanges;
|
||||
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
|
||||
pendingChanges = 0;
|
||||
}
|
||||
if ((localPendingChanges & PENDING_CHANGE_DISPLAY_POWER) != 0) {
|
||||
out.write(localPendingRestoreDisplayPower ? 1 : 0);
|
||||
out.flush();
|
||||
}
|
||||
private boolean sendMessage(int type, int param) {
|
||||
assert (type & ~MSG_TYPE_MASK) == 0;
|
||||
int msg = type | param << MSG_PARAM_SHIFT;
|
||||
try {
|
||||
out.write(msg);
|
||||
out.flush();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setRestoreDisplayPower(boolean restoreDisplayPower) {
|
||||
pendingRestoreDisplayPower = restoreDisplayPower;
|
||||
pendingChanges |= PENDING_CHANGE_DISPLAY_POWER;
|
||||
notify();
|
||||
public boolean setRestoreStayOn(int restoreValue) {
|
||||
// Restore the value (between 0 and 7), -1 to not restore
|
||||
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
|
||||
assert restoreValue >= -1 && restoreValue <= 7;
|
||||
return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111);
|
||||
}
|
||||
|
||||
public boolean setDisableShowTouches(boolean disableOnExit) {
|
||||
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
|
||||
}
|
||||
|
||||
public boolean setRestoreDisplayPower(boolean restoreOnExit) {
|
||||
return sendMessage(MSG_TYPE_RESTORE_DISPLAY_POWER, restoreOnExit ? 1 : 0);
|
||||
}
|
||||
|
||||
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
|
||||
return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0);
|
||||
}
|
||||
|
||||
public static void unlinkSelf() {
|
||||
@ -157,21 +83,35 @@ public final class CleanUp {
|
||||
unlinkSelf();
|
||||
|
||||
int displayId = Integer.parseInt(args[0]);
|
||||
int restoreStayOn = Integer.parseInt(args[1]);
|
||||
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
|
||||
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
|
||||
int restoreScreenOffTimeout = Integer.parseInt(args[4]);
|
||||
|
||||
// Dynamic option
|
||||
int restoreStayOn = -1;
|
||||
boolean disableShowTouches = false;
|
||||
boolean restoreDisplayPower = false;
|
||||
boolean powerOffScreen = false;
|
||||
|
||||
try {
|
||||
// Wait for the server to die
|
||||
int msg;
|
||||
while ((msg = System.in.read()) != -1) {
|
||||
// Only restore display power
|
||||
assert msg == 0 || msg == 1;
|
||||
restoreDisplayPower = msg != 0;
|
||||
int type = msg & MSG_TYPE_MASK;
|
||||
int param = msg >> MSG_PARAM_SHIFT;
|
||||
switch (type) {
|
||||
case MSG_TYPE_RESTORE_STAY_ON:
|
||||
restoreStayOn = param > 7 ? -1 : param;
|
||||
break;
|
||||
case MSG_TYPE_DISABLE_SHOW_TOUCHES:
|
||||
disableShowTouches = param != 0;
|
||||
break;
|
||||
case MSG_TYPE_RESTORE_DISPLAY_POWER:
|
||||
restoreDisplayPower = param != 0;
|
||||
break;
|
||||
case MSG_TYPE_POWER_OFF_SCREEN:
|
||||
powerOffScreen = param != 0;
|
||||
break;
|
||||
default:
|
||||
Ln.w("Unexpected msg type: " + type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Expected when the server is dead
|
||||
@ -197,16 +137,7 @@ public final class CleanUp {
|
||||
}
|
||||
}
|
||||
|
||||
if (restoreScreenOffTimeout != -1) {
|
||||
Ln.i("Restoring \"screen off timeout\"");
|
||||
try {
|
||||
Settings.putValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(restoreScreenOffTimeout));
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not restore \"screen_off_timeout\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) {
|
||||
if (Device.isScreenOn() && displayId != Device.DISPLAY_ID_NONE) {
|
||||
if (powerOffScreen) {
|
||||
Ln.i("Power off screen");
|
||||
Device.powerOffScreen(displayId);
|
||||
|
@ -1,14 +1,9 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.IContentProvider;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
|
||||
public final class FakeContext extends ContextWrapper {
|
||||
@ -22,38 +17,6 @@ public final class FakeContext extends ContextWrapper {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final ContentResolver contentResolver = new ContentResolver(this) {
|
||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
||||
// @Override (but super-class method not visible)
|
||||
protected IContentProvider acquireProvider(Context c, String name) {
|
||||
return ServiceManager.getActivityManager().getContentProviderExternal(name, new Binder());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// @Override (but super-class method not visible)
|
||||
public boolean releaseProvider(IContentProvider icp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
||||
// @Override (but super-class method not visible)
|
||||
protected IContentProvider acquireUnstableProvider(Context c, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// @Override (but super-class method not visible)
|
||||
public boolean releaseUnstableProvider(IContentProvider icp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// @Override (but super-class method not visible)
|
||||
public void unstableProviderDied(IContentProvider icp) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
private FakeContext() {
|
||||
super(Workarounds.getSystemContext());
|
||||
}
|
||||
@ -86,9 +49,4 @@ public final class FakeContext extends ContextWrapper {
|
||||
public Context getApplicationContext() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentResolver getContentResolver() {
|
||||
return contentResolver;
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ public class Options {
|
||||
private boolean cameraHighSpeed;
|
||||
private boolean showTouches;
|
||||
private boolean stayAwake;
|
||||
private int screenOffTimeout = -1;
|
||||
private List<CodecOption> videoCodecOptions;
|
||||
private List<CodecOption> audioCodecOptions;
|
||||
|
||||
@ -175,10 +174,6 @@ public class Options {
|
||||
return stayAwake;
|
||||
}
|
||||
|
||||
public int getScreenOffTimeout() {
|
||||
return screenOffTimeout;
|
||||
}
|
||||
|
||||
public List<CodecOption> getVideoCodecOptions() {
|
||||
return videoCodecOptions;
|
||||
}
|
||||
@ -368,12 +363,6 @@ public class Options {
|
||||
case "stay_awake":
|
||||
options.stayAwake = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "screen_off_timeout":
|
||||
options.screenOffTimeout = Integer.parseInt(value);
|
||||
if (options.screenOffTimeout < -1) {
|
||||
throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout);
|
||||
}
|
||||
break;
|
||||
case "video_codec_options":
|
||||
options.videoCodecOptions = CodecOption.parse(value);
|
||||
break;
|
||||
|
@ -16,6 +16,8 @@ import com.genymobile.scrcpy.device.NewDisplay;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.util.Settings;
|
||||
import com.genymobile.scrcpy.util.SettingsException;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||
@ -23,6 +25,7 @@ import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||
import com.genymobile.scrcpy.video.VideoSource;
|
||||
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
@ -73,6 +76,51 @@ public final class Server {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
private static void initAndCleanUp(Options options, CleanUp cleanUp) {
|
||||
// This method is called from its own thread, so it may only configure cleanup actions which are NOT dynamic (i.e. they are configured once
|
||||
// and for all, they cannot be changed from another thread)
|
||||
|
||||
if (options.getShowTouches()) {
|
||||
try {
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||
// If "show touches" was disabled, it must be disabled back on clean up
|
||||
if (!"1".equals(oldValue)) {
|
||||
if (!cleanUp.setDisableShowTouches(true)) {
|
||||
Ln.e("Could not disable show touch on exit");
|
||||
}
|
||||
}
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not change \"show_touches\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.getStayAwake()) {
|
||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||
try {
|
||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||
try {
|
||||
int restoreStayOn = Integer.parseInt(oldValue);
|
||||
if (restoreStayOn != stayOn) {
|
||||
// Restore only if the current value is different
|
||||
if (!cleanUp.setRestoreStayOn(restoreStayOn)) {
|
||||
Ln.e("Could not restore stay on on exit");
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (SettingsException e) {
|
||||
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.getPowerOffScreenOnClose()) {
|
||||
if (!cleanUp.setPowerOffScreen(true)) {
|
||||
Ln.e("Could not power off screen on exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||
Ln.e("Camera mirroring is not supported before Android 12");
|
||||
@ -102,12 +150,14 @@ public final class Server {
|
||||
}
|
||||
|
||||
CleanUp cleanUp = null;
|
||||
Thread initThread = null;
|
||||
|
||||
NewDisplay newDisplay = options.getNewDisplay();
|
||||
int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE;
|
||||
|
||||
if (options.getCleanup()) {
|
||||
cleanUp = CleanUp.start(displayId, options);
|
||||
cleanUp = CleanUp.configure(displayId);
|
||||
initThread = startInitThread(options, cleanUp);
|
||||
}
|
||||
|
||||
int scid = options.getScid();
|
||||
@ -175,10 +225,6 @@ public final class Server {
|
||||
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
|
||||
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
|
||||
asyncProcessors.add(surfaceEncoder);
|
||||
|
||||
if (controller != null) {
|
||||
controller.setSurfaceCapture(surfaceCapture);
|
||||
}
|
||||
}
|
||||
|
||||
Completion completion = new Completion(asyncProcessors.size());
|
||||
@ -190,8 +236,8 @@ public final class Server {
|
||||
|
||||
completion.await();
|
||||
} finally {
|
||||
if (cleanUp != null) {
|
||||
cleanUp.interrupt();
|
||||
if (initThread != null) {
|
||||
initThread.interrupt();
|
||||
}
|
||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||
asyncProcessor.stop();
|
||||
@ -200,8 +246,8 @@ public final class Server {
|
||||
connection.shutdown();
|
||||
|
||||
try {
|
||||
if (cleanUp != null) {
|
||||
cleanUp.join();
|
||||
if (initThread != null) {
|
||||
initThread.join();
|
||||
}
|
||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||
asyncProcessor.join();
|
||||
@ -214,6 +260,12 @@ public final class Server {
|
||||
}
|
||||
}
|
||||
|
||||
private static Thread startInitThread(final Options options, final CleanUp cleanUp) {
|
||||
Thread thread = new Thread(() -> initAndCleanUp(options, cleanUp), "init-cleanup");
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
int status = 0;
|
||||
try {
|
||||
|
@ -24,7 +24,6 @@ public final class ControlMessage {
|
||||
public static final int TYPE_UHID_DESTROY = 14;
|
||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
||||
public static final int TYPE_START_APP = 16;
|
||||
public static final int TYPE_RESET_VIDEO = 17;
|
||||
|
||||
public static final long SEQUENCE_INVALID = 0;
|
||||
|
||||
|
@ -46,7 +46,6 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case ControlMessage.TYPE_RESET_VIDEO:
|
||||
return ControlMessage.createEmpty(type);
|
||||
case ControlMessage.TYPE_UHID_CREATE:
|
||||
return parseUhidCreate();
|
||||
|
@ -9,7 +9,6 @@ import com.genymobile.scrcpy.device.Point;
|
||||
import com.genymobile.scrcpy.device.Position;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||
@ -94,9 +93,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
private boolean keepDisplayPowerOff;
|
||||
|
||||
// Used for resetting video encoding on RESET_VIDEO message
|
||||
private SurfaceCapture surfaceCapture;
|
||||
|
||||
public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
|
||||
this.displayId = displayId;
|
||||
this.controlChannel = controlChannel;
|
||||
@ -147,10 +143,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
||||
this.surfaceCapture = surfaceCapture;
|
||||
}
|
||||
|
||||
private UhidManager getUhidManager() {
|
||||
if (uhidManager == null) {
|
||||
uhidManager = new UhidManager(sender);
|
||||
@ -174,7 +166,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
private void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (powerOn && displayId == 0 && !Device.isScreenOn(displayId)) {
|
||||
if (powerOn && displayId == 0 && !Device.isScreenOn()) {
|
||||
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
|
||||
// dirty hack
|
||||
@ -301,9 +293,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
case ControlMessage.TYPE_START_APP:
|
||||
startAppAsync(msg.getText());
|
||||
break;
|
||||
case ControlMessage.TYPE_RESET_VIDEO:
|
||||
resetVideo();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
@ -501,7 +490,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
|
||||
private boolean pressBackOrTurnScreenOn(int action) {
|
||||
if (displayId == Device.DISPLAY_ID_NONE || Device.isScreenOn(displayId)) {
|
||||
if (Device.isScreenOn()) {
|
||||
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
@ -691,11 +680,4 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetVideo() {
|
||||
if (surfaceCapture != null) {
|
||||
Ln.i("Video capture reset");
|
||||
surfaceCapture.requestInvalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +82,8 @@ public final class Device {
|
||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
||||
}
|
||||
|
||||
public static boolean isScreenOn(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
return ServiceManager.getPowerManager().isScreenOn(displayId);
|
||||
public static boolean isScreenOn() {
|
||||
return ServiceManager.getPowerManager().isScreenOn();
|
||||
}
|
||||
|
||||
public static void expandNotificationPanel() {
|
||||
@ -182,7 +181,7 @@ public final class Device {
|
||||
public static boolean powerOffScreen(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
|
||||
if (!isScreenOn(displayId)) {
|
||||
if (!isScreenOn()) {
|
||||
return true;
|
||||
}
|
||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
|
@ -68,7 +68,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() throws IOException {
|
||||
public void init() throws IOException {
|
||||
cameraThread = new HandlerThread("camera");
|
||||
cameraThread.start();
|
||||
cameraHandler = new Handler(cameraThread.getLooper());
|
||||
@ -256,7 +256,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public void onDisconnected(CameraDevice camera) {
|
||||
Ln.w("Camera disconnected");
|
||||
disconnected.set(true);
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -355,9 +355,4 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public boolean isClosed() {
|
||||
return disconnected.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
// do nothing (the user could not request a reset anyway for now, since there is no controller for camera mirroring)
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class CaptureReset implements SurfaceCapture.CaptureListener {
|
||||
|
||||
private final AtomicBoolean reset = new AtomicBoolean();
|
||||
|
||||
// Current instance of MediaCodec to "interrupt" on reset
|
||||
private MediaCodec runningMediaCodec;
|
||||
|
||||
public boolean consumeReset() {
|
||||
return reset.getAndSet(false);
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
reset.set(true);
|
||||
if (runningMediaCodec != null) {
|
||||
runningMediaCodec.signalEndOfInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setRunningMediaCodec(MediaCodec runningMediaCodec) {
|
||||
this.runningMediaCodec = runningMediaCodec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
reset();
|
||||
}
|
||||
}
|
@ -14,8 +14,6 @@ import android.hardware.display.VirtualDisplay;
|
||||
import android.os.Build;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NewDisplayCapture extends SurfaceCapture {
|
||||
|
||||
// Internal fields copied from android.hardware.display.DisplayManager
|
||||
@ -48,7 +46,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
public void init() {
|
||||
size = newDisplay.getSize();
|
||||
dpi = newDisplay.getDpi();
|
||||
if (size == null || dpi == 0) {
|
||||
@ -74,8 +72,13 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Surface surface) {
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
|
||||
public void startNew(Surface surface) {
|
||||
int virtualDisplayId;
|
||||
try {
|
||||
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
|
||||
@ -90,8 +93,8 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
|
||||
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||
}
|
||||
}
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
@ -104,21 +107,13 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
if (vdListener != null) {
|
||||
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||
Rect contentRect = new Rect(0, 0, size.getWidth(), size.getHeight());
|
||||
PositionMapper positionMapper = new PositionMapper(size, contentRect, 0);
|
||||
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Surface surface) throws IOException {
|
||||
if (virtualDisplay == null) {
|
||||
startNew(surface);
|
||||
} else {
|
||||
virtualDisplay.setSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (virtualDisplay != null) {
|
||||
@ -148,9 +143,4 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
int num = size.getMax();
|
||||
return initialDpi * num / den;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
Ln.v("ScreenCapture: requestReset(): " + getSessionDisplaySize() + " -> (unknown)");
|
||||
}
|
||||
setSessionDisplaySize(null);
|
||||
invalidate();
|
||||
requestReset();
|
||||
} else {
|
||||
Size size = di.getSize();
|
||||
|
||||
@ -102,7 +102,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
||||
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
||||
setSessionDisplaySize(size);
|
||||
invalidate();
|
||||
requestReset();
|
||||
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v("ScreenCapture: Size not changed (" + size + "): do not requestReset()");
|
||||
}
|
||||
@ -246,7 +246,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v("ScreenCapture: onRotationChanged(" + rotation + ")");
|
||||
}
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
};
|
||||
ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
||||
@ -272,7 +272,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
// Ignore events related to other display ids
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
};
|
||||
ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener);
|
||||
@ -291,9 +291,4 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
@ -6,37 +6,36 @@ import com.genymobile.scrcpy.device.Size;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A video source which can be rendered on a Surface for encoding.
|
||||
*/
|
||||
public abstract class SurfaceCapture {
|
||||
|
||||
public interface CaptureListener {
|
||||
void onInvalidated();
|
||||
}
|
||||
|
||||
private CaptureListener listener;
|
||||
private final AtomicBoolean resetCapture = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Notify the listener that the capture has been invalidated (for example, because its size changed).
|
||||
* Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on
|
||||
* device rotation for example).
|
||||
*/
|
||||
protected void invalidate() {
|
||||
listener.onInvalidated();
|
||||
protected void requestReset() {
|
||||
resetCapture.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the reset request (intended to be called by the encoder).
|
||||
*
|
||||
* @return {@code true} if a reset request was pending, {@code false} otherwise.
|
||||
*/
|
||||
public boolean consumeReset() {
|
||||
return resetCapture.getAndSet(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once before the first capture starts.
|
||||
*/
|
||||
public final void init(CaptureListener listener) throws ConfigurationException, IOException {
|
||||
this.listener = listener;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once before the first capture starts.
|
||||
*/
|
||||
protected abstract void init() throws ConfigurationException, IOException;
|
||||
public abstract void init() throws ConfigurationException, IOException;
|
||||
|
||||
/**
|
||||
* Called after the last capture ends (if and only if {@link #init()} has been called).
|
||||
@ -79,11 +78,4 @@ public abstract class SurfaceCapture {
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually request to invalidate (typically a user request).
|
||||
* <p>
|
||||
* The capture implementation is free to ignore the request and do nothing.
|
||||
*/
|
||||
public abstract void requestInvalidate();
|
||||
}
|
||||
|
@ -49,8 +49,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
private Thread thread;
|
||||
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||
|
||||
private final CaptureReset reset = new CaptureReset();
|
||||
|
||||
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List<CodecOption> codecOptions,
|
||||
String encoderName, boolean downsizeOnError) {
|
||||
this.capture = capture;
|
||||
@ -67,14 +65,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||
|
||||
capture.init(reset);
|
||||
capture.init();
|
||||
|
||||
try {
|
||||
boolean alive;
|
||||
boolean headerWritten = false;
|
||||
|
||||
do {
|
||||
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||
capture.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||
capture.prepare();
|
||||
Size size = capture.getSize();
|
||||
if (!headerWritten) {
|
||||
@ -90,27 +88,17 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
surface = mediaCodec.createInputSurface();
|
||||
|
||||
VideoFilter filter = new VideoFilter(surface);
|
||||
surface = filter.getInputSurface();
|
||||
|
||||
capture.start(surface);
|
||||
|
||||
mediaCodec.start();
|
||||
|
||||
// Set the MediaCodec instance to "interrupt" (by signaling an EOS) on reset
|
||||
reset.setRunningMediaCodec(mediaCodec);
|
||||
|
||||
if (stopped.get()) {
|
||||
alive = false;
|
||||
} else {
|
||||
boolean resetRequested = reset.consumeReset();
|
||||
if (!resetRequested) {
|
||||
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
|
||||
encode(mediaCodec, streamer);
|
||||
}
|
||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||
alive = !stopped.get() && !capture.isClosed();
|
||||
}
|
||||
|
||||
alive = encode(mediaCodec, streamer);
|
||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||
mediaCodec.stop();
|
||||
filter.release();
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||
if (!prepareRetry(size)) {
|
||||
@ -119,7 +107,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
Ln.i("Retrying...");
|
||||
alive = true;
|
||||
} finally {
|
||||
reset.setRunningMediaCodec(null);
|
||||
mediaCodec.reset();
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
@ -180,16 +167,25 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void encode(MediaCodec codec, Streamer streamer) throws IOException {
|
||||
private boolean encode(MediaCodec codec, Streamer streamer) throws IOException {
|
||||
boolean eof = false;
|
||||
boolean alive = true;
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
|
||||
boolean eos;
|
||||
do {
|
||||
while (!capture.consumeReset() && !eof) {
|
||||
if (stopped.get()) {
|
||||
alive = false;
|
||||
break;
|
||||
}
|
||||
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
||||
try {
|
||||
eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||
// On EOS, there might be data or not, depending on bufferInfo.size
|
||||
if (outputBufferId >= 0 && bufferInfo.size > 0) {
|
||||
if (capture.consumeReset()) {
|
||||
// must restart encoding with new size
|
||||
break;
|
||||
}
|
||||
|
||||
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||
if (outputBufferId >= 0) {
|
||||
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
|
||||
|
||||
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
||||
@ -206,7 +202,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
codec.releaseOutputBuffer(outputBufferId, false);
|
||||
}
|
||||
}
|
||||
} while (!eos);
|
||||
}
|
||||
|
||||
if (capture.isClosed()) {
|
||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||
alive = false;
|
||||
}
|
||||
|
||||
return !eof && alive;
|
||||
}
|
||||
|
||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||
@ -299,7 +302,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
public void stop() {
|
||||
if (thread != null) {
|
||||
stopped.set(true);
|
||||
reset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import android.view.Surface;
|
||||
|
||||
public class VideoFilter {
|
||||
private EGLDisplay eglDisplay;
|
||||
private EGLContext eglContext;
|
||||
private EGLSurface eglSurface;
|
||||
private SurfaceTexture surfaceTexture;
|
||||
private Surface inputSurface;
|
||||
private int textureId;
|
||||
|
||||
public VideoFilter(Surface outputSurface) {
|
||||
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
|
||||
throw new RuntimeException("Unable to get EGL14 display");
|
||||
}
|
||||
|
||||
int[] version = new int[2];
|
||||
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
|
||||
throw new RuntimeException("Unable to initialize EGL14");
|
||||
}
|
||||
|
||||
int[] attribList = {
|
||||
EGL14.EGL_RED_SIZE, 8,
|
||||
EGL14.EGL_GREEN_SIZE, 8,
|
||||
EGL14.EGL_BLUE_SIZE, 8,
|
||||
EGL14.EGL_ALPHA_SIZE, 8,
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
EGLConfig[] configs = new EGLConfig[1];
|
||||
int[] numConfigs = new int[1];
|
||||
EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0);
|
||||
if (numConfigs[0] <= 0) {
|
||||
throw new RuntimeException("Unable to find ES2 EGL config");
|
||||
}
|
||||
EGLConfig eglConfig = configs[0];
|
||||
|
||||
int[] contextAttribList = {
|
||||
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribList, 0);
|
||||
if (eglContext == null) {
|
||||
throw new RuntimeException("Failed to create EGL context");
|
||||
}
|
||||
|
||||
int[] surfaceAttribList = {
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, outputSurface, surfaceAttribList, 0);
|
||||
if (eglSurface == null) {
|
||||
throw new RuntimeException("Failed to create EGL window surface");
|
||||
}
|
||||
|
||||
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
||||
throw new RuntimeException("Failed to make EGL context current");
|
||||
}
|
||||
|
||||
int[] textures = new int[1];
|
||||
GLES20.glGenTextures(1, textures, 0);
|
||||
textureId = textures[0];
|
||||
|
||||
surfaceTexture = new SurfaceTexture(textureId);
|
||||
inputSurface = new Surface(surfaceTexture);
|
||||
|
||||
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
// XXX This should be called when the VirtualDisplay has rendered a new frame
|
||||
Ln.i("==== render");
|
||||
render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Surface getInputSurface() {
|
||||
return inputSurface;
|
||||
}
|
||||
|
||||
public void render() {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
|
||||
|
||||
// For now, just paint with a color
|
||||
GLES20.glClearColor(0.0f, 0.5f, 0.5f, 1.0f);
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLES20.glViewport(0, 0, 1920, 1080);
|
||||
|
||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
|
||||
EGL14.eglDestroySurface(eglDisplay, eglSurface);
|
||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||
EGL14.eglTerminate(eglDisplay);
|
||||
}
|
||||
eglDisplay = EGL14.EGL_NO_DISPLAY;
|
||||
eglContext = EGL14.EGL_NO_CONTEXT;
|
||||
eglSurface = EGL14.EGL_NO_SURFACE;
|
||||
surfaceTexture.release();
|
||||
inputSurface.release();
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.IContentProvider;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
@ -65,7 +64,7 @@ public final class ActivityManager {
|
||||
}
|
||||
|
||||
@TargetApi(AndroidVersions.API_29_ANDROID_10)
|
||||
public IContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||
private ContentProvider getContentProviderExternal(String name, IBinder token) {
|
||||
try {
|
||||
Method method = getGetContentProviderExternalMethod();
|
||||
Object[] args;
|
||||
@ -84,7 +83,11 @@ public final class ActivityManager {
|
||||
// IContentProvider provider = providerHolder.provider;
|
||||
Field providerField = providerHolder.getClass().getDeclaredField("provider");
|
||||
providerField.setAccessible(true);
|
||||
return (IContentProvider) providerField.get(providerHolder);
|
||||
Object provider = providerField.get(providerHolder);
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
return new ContentProvider(this, provider, name, token);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return null;
|
||||
@ -101,12 +104,7 @@ public final class ActivityManager {
|
||||
}
|
||||
|
||||
public ContentProvider createSettingsProvider() {
|
||||
IBinder token = new Binder();
|
||||
IContentProvider provider = getContentProviderExternal("settings", token);
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
return new ContentProvider(this, provider, "settings", token);
|
||||
return getContentProviderExternal("settings", new Binder());
|
||||
}
|
||||
|
||||
private Method getStartActivityAsUserMethod() throws NoSuchMethodException, ClassNotFoundException {
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.AndroidVersions;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.IInterface;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@ -23,22 +21,14 @@ public final class PowerManager {
|
||||
|
||||
private Method getIsScreenOnMethod() throws NoSuchMethodException {
|
||||
if (isScreenOnMethod == null) {
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
isScreenOnMethod = manager.getClass().getMethod("isDisplayInteractive", int.class);
|
||||
} else {
|
||||
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
||||
}
|
||||
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
||||
}
|
||||
return isScreenOnMethod;
|
||||
}
|
||||
|
||||
public boolean isScreenOn(int displayId) {
|
||||
|
||||
public boolean isScreenOn() {
|
||||
try {
|
||||
Method method = getIsScreenOnMethod();
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
}
|
||||
return (boolean) method.invoke(manager);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
|
Reference in New Issue
Block a user