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)
|
||||
- and more…
|
||||
|
||||
## Requirements
|
||||
## Prerequisites
|
||||
|
||||
The Android device requires at least API 21 (Android 5.0).
|
||||
|
||||
[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
|
||||
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
||||
@ -90,10 +90,11 @@ documented in the following pages:
|
||||
|
||||
- [Introducing scrcpy][article-intro]
|
||||
- [Scrcpy now works wirelessly][article-tcpip]
|
||||
- [Scrcpy 2.0, with audio][article-scrcpy2]
|
||||
|
||||
[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-scrcpy2]: https://blog.rom1v.com/2023/03/scrcpy-2-0-with-audio/
|
||||
|
||||
## Contact
|
||||
|
||||
|
@ -287,7 +287,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
|
||||
float avg = sc_average_get(&ap->avg_buffering);
|
||||
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
|
||||
diff = 0;
|
||||
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "demuxer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/time.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);
|
||||
|
||||
switch (event->type) {
|
||||
case SC_EVENT_SCREEN_INIT_SIZE:
|
||||
case SC_EVENT_SCREEN_INIT_SIZE: {
|
||||
// The initial size is passed via screen->frame_size
|
||||
bool ok = sc_screen_init_size(screen);
|
||||
if (!ok) {
|
||||
@ -824,6 +824,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SC_EVENT_NEW_FRAME: {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
|
@ -210,6 +210,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
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);
|
||||
if (ret < 0) {
|
||||
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.
|
||||
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.
|
||||
|
||||
[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
|
||||
|
@ -61,6 +61,8 @@ _See [build.md](build.md) to build and install the app manually._
|
||||
|
||||
## Run
|
||||
|
||||
_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._
|
||||
|
||||
Once installed, run from a terminal:
|
||||
|
||||
```bash
|
||||
|
@ -29,6 +29,8 @@ _See [build.md](build.md) to build and install the app manually._
|
||||
|
||||
## Run
|
||||
|
||||
_Make sure that your device meets the [prerequisites](/README.md#prerequisites)._
|
||||
|
||||
Once installed, run from a terminal:
|
||||
|
||||
```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>
|
||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||
| 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._
|
||||
_²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)
|
||||
<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-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
|
||||
|
||||
_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
|
||||
a terminal with command line arguments.
|
||||
|
||||
|
@ -111,7 +111,7 @@ public final class AudioCapture {
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
|
||||
int r = recorder.read(directBuffer, size);
|
||||
if (r < 0) {
|
||||
if (r <= 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
InputTask task = inputTasks.take();
|
||||
ByteBuffer buffer = mediaCodec.getInputBuffer(task.index);
|
||||
int r = capture.read(buffer, READ_SIZE, bufferInfo);
|
||||
if (r < 0) {
|
||||
if (r <= 0) {
|
||||
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:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
||||
// - <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.
|
||||
// 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.
|
||||
mustFillAppInfo |= audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R;
|
||||
|
||||
if (mustFillAppInfo) {
|
||||
Workarounds.fillAppInfo();
|
||||
// Only on Android 11 we must fill the application context for the AudioRecord to work.
|
||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
Workarounds.fillAppContext();
|
||||
}
|
||||
|
||||
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
||||
|
@ -10,6 +10,10 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public final class Workarounds {
|
||||
|
||||
private static Class<?> activityThreadClass;
|
||||
private static Object activityThread;
|
||||
|
||||
private Workarounds() {
|
||||
// not instantiable
|
||||
}
|
||||
@ -28,18 +32,25 @@ public final class Workarounds {
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
public static void fillAppInfo() {
|
||||
try {
|
||||
private static void fillActivityThread() throws Exception {
|
||||
if (activityThread == null) {
|
||||
// ActivityThread activityThread = new ActivityThread();
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
||||
activityThreadConstructor.setAccessible(true);
|
||||
Object activityThread = activityThreadConstructor.newInstance();
|
||||
activityThread = activityThreadConstructor.newInstance();
|
||||
|
||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
||||
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
||||
sCurrentActivityThreadField.setAccessible(true);
|
||||
sCurrentActivityThreadField.set(null, activityThread);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
public static void fillAppInfo() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
||||
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
||||
@ -59,6 +70,16 @@ public final class Workarounds {
|
||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
mBoundApplicationField.setAccessible(true);
|
||||
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();
|
||||
Field baseField = ContextWrapper.class.getDeclaredField("mBase");
|
||||
@ -71,7 +92,7 @@ public final class Workarounds {
|
||||
mInitialApplicationField.set(activityThread, app);
|
||||
} catch (Throwable throwable) {
|
||||
// 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