Compare commits
12 Commits
v2.0-insta
...
split_work
Author | SHA1 | Date | |
---|---|---|---|
3582592d2c | |||
337d6c2fd3 | |||
2eced46a37 | |||
1a80333747 | |||
fb61b779a6 | |||
5899af6a2f | |||
cbca79b95b | |||
02586cf21f | |||
80a6fa7a01 | |||
6b769675fa | |||
e5aa2ce01f | |||
cbc638c6ba |
@ -35,15 +35,15 @@ Its features include:
|
|||||||
- [OTG mode](doc/hid-otg.md#otg)
|
- [OTG mode](doc/hid-otg.md#otg)
|
||||||
- and more…
|
- and more…
|
||||||
|
|
||||||
## Requirements
|
## Prerequisites
|
||||||
|
|
||||||
The Android device requires at least API 21 (Android 5.0).
|
The Android device requires at least API 21 (Android 5.0).
|
||||||
|
|
||||||
[Audio forwarding](doc/audio.md) is supported from API 30 (Android 11).
|
[Audio forwarding](doc/audio.md) is supported from API 30 (Android 11).
|
||||||
|
|
||||||
Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
Make sure you [enabled USB debugging][enable-adb] on your device(s).
|
||||||
|
|
||||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
|
||||||
|
|
||||||
On some devices, you also need to enable [an additional option][control] `USB
|
On some devices, you also need to enable [an additional option][control] `USB
|
||||||
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
||||||
@ -90,10 +90,11 @@ documented in the following pages:
|
|||||||
|
|
||||||
- [Introducing scrcpy][article-intro]
|
- [Introducing scrcpy][article-intro]
|
||||||
- [Scrcpy now works wirelessly][article-tcpip]
|
- [Scrcpy now works wirelessly][article-tcpip]
|
||||||
|
- [Scrcpy 2.0, with audio][article-scrcpy2]
|
||||||
|
|
||||||
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
|
[article-scrcpy2]: https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio/
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
float avg = sc_average_get(&ap->avg_buffering);
|
float avg = sc_average_get(&ap->avg_buffering);
|
||||||
int diff = ap->target_buffering - avg;
|
int diff = ap->target_buffering - avg;
|
||||||
if (abs(diff) < ap->sample_rate / 1000) {
|
if (abs(diff) < (int) ap->sample_rate / 1000) {
|
||||||
// Do not compensate for less than 1ms, the error is just noise
|
// Do not compensate for less than 1ms, the error is just noise
|
||||||
diff = 0;
|
diff = 0;
|
||||||
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
|
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "demuxer.h"
|
#include "demuxer.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
@ -816,7 +816,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
bool relative_mode = sc_screen_is_relative_mode(screen);
|
bool relative_mode = sc_screen_is_relative_mode(screen);
|
||||||
|
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case SC_EVENT_SCREEN_INIT_SIZE:
|
case SC_EVENT_SCREEN_INIT_SIZE: {
|
||||||
// The initial size is passed via screen->frame_size
|
// The initial size is passed via screen->frame_size
|
||||||
bool ok = sc_screen_init_size(screen);
|
bool ok = sc_screen_init_size(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@ -824,6 +824,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
case SC_EVENT_NEW_FRAME: {
|
case SC_EVENT_NEW_FRAME: {
|
||||||
bool ok = sc_screen_update_frame(screen);
|
bool ok = sc_screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
@ -210,6 +210,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
|||||||
goto error_avformat_free_context;
|
goto error_avformat_free_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The codec is from the v4l2 encoder, not from the decoder
|
||||||
|
ostream->codecpar->codec_id = encoder->id;
|
||||||
|
|
||||||
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Failed to open output device: %s", vs->device_name);
|
LOGE("Failed to open output device: %s", vs->device_name);
|
||||||
|
@ -107,10 +107,10 @@ with the device IP address you found)_.
|
|||||||
7. Run `scrcpy` as usual.
|
7. Run `scrcpy` as usual.
|
||||||
8. Run `adb disconnect` once you're done.
|
8. Run `adb disconnect` once you're done.
|
||||||
|
|
||||||
Since Android 11, a [Wireless debugging option][adb-wireless] allows to bypass
|
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
|
||||||
having to physically connect your device directly to your computer.
|
having to physically connect your device directly to your computer.
|
||||||
|
|
||||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
|
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
|
||||||
|
|
||||||
|
|
||||||
## Autostart
|
## Autostart
|
||||||
|
@ -61,6 +61,8 @@ _See [build.md](build.md) to build and install the app manually._
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._
|
||||||
|
|
||||||
Once installed, run from a terminal:
|
Once installed, run from a terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -29,6 +29,8 @@ _See [build.md](build.md) to build and install the app manually._
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._
|
||||||
|
|
||||||
Once installed, run from a terminal:
|
Once installed, run from a terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -49,7 +49,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
| Drag & drop APK file | Install APK from computer
|
| Drag & drop APK file | Install APK from computer
|
||||||
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
|
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
|
@ -9,7 +9,7 @@ Download the [latest release]:
|
|||||||
- [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit)
|
- [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit)
|
||||||
<sub>SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c`</sub>
|
<sub>SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c`</sub>
|
||||||
|
|
||||||
[release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip
|
||||||
|
|
||||||
@ -38,6 +38,8 @@ _See [build.md](build.md) to build and install the app manually._
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._
|
||||||
|
|
||||||
Scrcpy is a command line application: it is mainly intended to be executed from
|
Scrcpy is a command line application: it is mainly intended to be executed from
|
||||||
a terminal with command line arguments.
|
a terminal with command line arguments.
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ public final class AudioCapture {
|
|||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
|
public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
|
||||||
int r = recorder.read(directBuffer, size);
|
int r = recorder.read(directBuffer, size);
|
||||||
if (r < 0) {
|
if (r <= 0) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
InputTask task = inputTasks.take();
|
InputTask task = inputTasks.take();
|
||||||
ByteBuffer buffer = mediaCodec.getInputBuffer(task.index);
|
ByteBuffer buffer = mediaCodec.getInputBuffer(task.index);
|
||||||
int r = capture.read(buffer, READ_SIZE, bufferInfo);
|
int r = capture.read(buffer, READ_SIZE, bufferInfo);
|
||||||
if (r < 0) {
|
if (r <= 0) {
|
||||||
throw new IOException("Could not read audio: " + r);
|
throw new IOException("Could not read audio: " + r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,15 +81,15 @@ public final class Server {
|
|||||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
// But only apply when strictly necessary, since workarounds can cause other issues:
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
||||||
boolean mustFillAppInfo = Build.BRAND.equalsIgnoreCase("meizu");
|
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
||||||
|
Workarounds.fillAppInfo();
|
||||||
|
}
|
||||||
|
|
||||||
// Before Android 11, audio is not supported.
|
// Before Android 11, audio is not supported.
|
||||||
// Since Android 12, we can properly set a context on the AudioRecord.
|
// Since Android 12, we can properly set a context on the AudioRecord.
|
||||||
// Only on Android 11 we must fill app info for the AudioRecord to work.
|
// Only on Android 11 we must fill the application context for the AudioRecord to work.
|
||||||
mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R;
|
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
Workarounds.fillAppContext();
|
||||||
if (mustFillAppInfo) {
|
|
||||||
Workarounds.fillAppInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
||||||
|
@ -10,6 +10,10 @@ import java.lang.reflect.Constructor;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public final class Workarounds {
|
public final class Workarounds {
|
||||||
|
|
||||||
|
private static Class<?> activityThreadClass;
|
||||||
|
private static Object activityThread;
|
||||||
|
|
||||||
private Workarounds() {
|
private Workarounds() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
@ -28,18 +32,25 @@ public final class Workarounds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||||
public static void fillAppInfo() {
|
private static void fillActivityThread() throws Exception {
|
||||||
try {
|
if (activityThread == null) {
|
||||||
// ActivityThread activityThread = new ActivityThread();
|
// ActivityThread activityThread = new ActivityThread();
|
||||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
||||||
activityThreadConstructor.setAccessible(true);
|
activityThreadConstructor.setAccessible(true);
|
||||||
Object activityThread = activityThreadConstructor.newInstance();
|
activityThread = activityThreadConstructor.newInstance();
|
||||||
|
|
||||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
// ActivityThread.sCurrentActivityThread = activityThread;
|
||||||
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
||||||
sCurrentActivityThreadField.setAccessible(true);
|
sCurrentActivityThreadField.setAccessible(true);
|
||||||
sCurrentActivityThreadField.set(null, activityThread);
|
sCurrentActivityThreadField.set(null, activityThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||||
|
public static void fillAppInfo() {
|
||||||
|
try {
|
||||||
|
fillActivityThread();
|
||||||
|
|
||||||
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
||||||
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
||||||
@ -59,6 +70,16 @@ public final class Workarounds {
|
|||||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||||
mBoundApplicationField.setAccessible(true);
|
mBoundApplicationField.setAccessible(true);
|
||||||
mBoundApplicationField.set(activityThread, appBindData);
|
mBoundApplicationField.set(activityThread, appBindData);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
// this is a workaround, so failing is not an error
|
||||||
|
Ln.d("Could not fill app info: " + throwable.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||||
|
public static void fillAppContext() {
|
||||||
|
try {
|
||||||
|
fillActivityThread();
|
||||||
|
|
||||||
Application app = Application.class.newInstance();
|
Application app = Application.class.newInstance();
|
||||||
Field baseField = ContextWrapper.class.getDeclaredField("mBase");
|
Field baseField = ContextWrapper.class.getDeclaredField("mBase");
|
||||||
@ -71,7 +92,7 @@ public final class Workarounds {
|
|||||||
mInitialApplicationField.set(activityThread, app);
|
mInitialApplicationField.set(activityThread, app);
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
// this is a workaround, so failing is not an error
|
// this is a workaround, so failing is not an error
|
||||||
Ln.d("Could not fill app info: " + throwable.getMessage());
|
Ln.d("Could not fill app context: " + throwable.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user