Compare commits

..
Author SHA1 Message Date
Yan 3615b3ff15 Force OpenGL Core Profile context on macOS
By default, SDL creates an OpenGL 2.1 context on macOS for an OpenGL
renderer. As a consequence, mipmapping is not supported.

Force to use a core profile context, to get a higher version.

Before:

    INFO: Renderer: opengl
    INFO: OpenGL version: 2.1 NVIDIA-14.0.32 355.11.11.10.10.143
    WARN: Trilinear filtering disabled (OpenGL 3.0+ or ES 2.0+ required)

After:

    INFO: Renderer: opengl
    DEBUG: Creating OpenGL Core Profile context
    INFO: OpenGL version: 4.1 NVIDIA-14.0.32 355.11.11.10.10.143
    INFO: Trilinear filtering enabled

when running with:

    scrcpy --verbosity=debug --render-driver=opengl

Note:
Since SDL_CreateRenderer causes a fallback to OpenGL 2.1, the profile
and version attributes have to be set and the context created _after_.
2023-04-07 14:37:16 +02:00
Romain Vimont 051b74c883 Extract sc_display from sc_screen
Move the display code to a separate component.
2023-04-06 19:48:26 +02:00
Romain Vimont 2e532afd2b Pass const pointers to events
SDL_Events are only read.
2023-04-06 19:48:01 +02:00
Simon Chan fdf465851c Add Android version check in raw audio recorder
Do not attempt to capture audio below Android 11, this may cause a
segfault on the device.

PR #3889 <https://github.com/Genymobile/scrcpy/pull/3889>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-04-04 18:37:30 +02:00
Romain Vimont 669e9a8d1e Fix "ip route" parsing
If a line did not end with '\r', then the final `\n' was replaced by
'\0' for parsing the current line. This `\0` was then mistakenly
considered as the end of the whole "ip route" output, so the remaining
lines were not parsed, causing "scrcpy --tcpip" to fail in some cases.

To fix the issue, read the final character of the current line before it
is (possibly) overwritten by '\0'.
2023-04-02 19:30:23 +02:00
Romain Vimont f77e1c474e Fix copy-paste for some devices
On Honor Magic 5 Pro, the method to get the clipboard content has been
modified in the framework.

Adapt the call to make it work also on this device.

Fixes #3885 <https://github.com/Genymobile/scrcpy/issues/3885>
2023-04-01 11:52:37 +02:00
26 changed files with 340 additions and 249 deletions
+1
View File
@@ -14,6 +14,7 @@ src = [
'src/delay_buffer.c',
'src/demuxer.c',
'src/device_msg.c',
'src/display.c',
'src/icon.c',
'src/file_pusher.c',
'src/fps_counter.c',
+6 -5
View File
@@ -204,6 +204,7 @@ sc_adb_parse_device_ip(char *str) {
while (str[idx_line] != '\0') {
char *line = &str[idx_line];
size_t len = strcspn(line, "\n");
bool is_last_line = line[len] == '\0';
// The same, but without any trailing '\r'
size_t line_len = sc_str_remove_trailing_cr(line, len);
@@ -215,12 +216,12 @@ sc_adb_parse_device_ip(char *str) {
return ip;
}
idx_line += len;
if (str[idx_line] != '\0') {
// The next line starts after the '\n'
++idx_line;
if (is_last_line) {
break;
}
// The next line starts after the '\n'
idx_line += len + 1;
}
return NULL;
+185
View File
@@ -0,0 +1,185 @@
#include "display.h"
#include <assert.h>
#include "util/log.h"
bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!display->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
return false;
}
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
display->mipmaps = false;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
// Persuade macOS to give us something better than OpenGL 2.1.
// If we create a Core Profile context, we get the best OpenGL version.
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE);
LOGD("Creating OpenGL Core Profile Context");
display->gl_context = SDL_GL_CreateContext(window);
if (!display->gl_context) {
LOGE("Could not create OpenGL context: %s", SDL_GetError());
SDL_DestroyRenderer(display->renderer);
return false;
}
#endif
struct sc_opengl *gl = &display->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
display->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer");
}
return true;
}
void
sc_display_destroy(struct sc_display *display) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DeleteContext(display->gl_context);
#endif
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
SDL_DestroyRenderer(display->renderer);
}
static SDL_Texture *
sc_display_create_texture(struct sc_display *display,
struct sc_size size) {
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
LOGE("Could not create texture: %s", SDL_GetError());
return NULL;
}
if (display->mipmaps) {
struct sc_opengl *gl = &display->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
// Enable trilinear filtering for downscaling
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
SDL_GL_UnbindTexture(texture);
}
return texture;
}
bool
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
if (display->texture) {
SDL_DestroyTexture(display->texture);
}
display->texture = sc_display_create_texture(display, size);
if (!display->texture) {
return false;
}
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
return true;
}
bool
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (ret) {
LOGE("Could not update texture: %s", SDL_GetError());
return false;
}
if (display->mipmaps) {
SDL_GL_BindTexture(display->texture, NULL, NULL);
display->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(display->texture);
}
return true;
}
bool
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation) {
SDL_RenderClear(display->renderer);
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = display->texture;
if (rotation == 0) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return false;
}
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - rotation) % 4;
double angle = 90 * cw_rotation;
const SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (rotation & 1) {
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
rect.w = geometry->h;
rect.h = geometry->w;
dstrect = &rect;
} else {
assert(rotation == 2);
dstrect = geometry;
}
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
NULL, 0);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return false;
}
}
SDL_RenderPresent(display->renderer);
return true;
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef SC_DISPLAY_H
#define SC_DISPLAY_H
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL.h>
#include "coords.h"
#include "opengl.h"
#ifdef __APPLE__
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
#endif
struct sc_display {
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GLContext *gl_context;
#endif
bool mipmaps;
};
bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
void
sc_display_destroy(struct sc_display *display);
bool
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
bool
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
bool
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
unsigned rotation);
#endif
+2 -1
View File
@@ -797,7 +797,8 @@ sc_input_manager_process_file(struct sc_input_manager *im,
}
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) {
bool control = im->controller;
switch (event->type) {
case SDL_TEXTINPUT:
+2 -1
View File
@@ -61,6 +61,7 @@ sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event);
#endif
+26 -136
View File
@@ -239,35 +239,6 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
}
}
static bool
create_texture(struct sc_screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
LOGE("Could not create texture: %s", SDL_GetError());
return false;
}
if (screen->mipmaps) {
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
// Enable trilinear filtering for downscaling
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
SDL_GL_UnbindTexture(texture);
}
screen->texture = texture;
return true;
}
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
@@ -278,35 +249,11 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
sc_screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
bool ok = sc_display_render(&screen->display, &screen->rect,
screen->rotation);
(void) ok; // error already logged
}
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
@@ -453,46 +400,11 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_fps_counter;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
ok = sc_display_init(&screen->display, screen->window, params->mipmaps);
if (!ok) {
goto error_destroy_window;
}
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
screen->mipmaps = false;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (params->mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (params->mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
@@ -504,7 +416,7 @@ sc_screen_init(struct sc_screen *screen,
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOG_OOM();
goto error_destroy_renderer;
goto error_destroy_display;
}
struct sc_input_manager_params im_params = {
@@ -539,8 +451,8 @@ sc_screen_init(struct sc_screen *screen,
return true;
error_destroy_renderer:
SDL_DestroyRenderer(screen->renderer);
error_destroy_display:
sc_display_destroy(&screen->display);
error_destroy_window:
SDL_DestroyWindow(screen->window);
error_destroy_fps_counter:
@@ -596,11 +508,8 @@ sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
sc_display_destroy(&screen->display);
av_frame_free(&screen->frame);
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
sc_fps_counter_destroy(&screen->fps_counter);
sc_frame_buffer_destroy(&screen->fb);
@@ -667,7 +576,6 @@ static bool
sc_screen_init_size(struct sc_screen *screen) {
// Before first frame
assert(!screen->has_frame);
assert(!screen->texture);
// The requested size is passed via screen->frame_size
@@ -675,48 +583,27 @@ sc_screen_init_size(struct sc_screen *screen) {
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
LOGI("Initial texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
return create_texture(screen);
return sc_display_set_texture_size(&screen->display, screen->frame_size);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
return create_texture(screen);
if (screen->frame_size.width == new_frame_size.width
&& screen->frame_size.height == new_frame_size.height) {
return true;
}
return true;
}
// frame dimension changed
screen->frame_size = new_frame_size;
// write the frame into the texture
static void
update_texture(struct sc_screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
struct sc_size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
if (screen->mipmaps) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
sc_screen_update_content_rect(screen);
return sc_display_set_texture_size(&screen->display, screen->frame_size);
}
static bool
@@ -731,7 +618,10 @@ sc_screen_update_frame(struct sc_screen *screen) {
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
}
update_texture(screen, frame);
if (!sc_display_update_texture(&screen->display, frame)) {
return false;
}
if (!screen->has_frame) {
screen->has_frame = true;
@@ -812,7 +702,7 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) {
}
bool
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
bool relative_mode = sc_screen_is_relative_mode(screen);
switch (event->type) {
+3 -5
View File
@@ -9,6 +9,7 @@
#include "controller.h"
#include "coords.h"
#include "display.h"
#include "fps_counter.h"
#include "frame_buffer.h"
#include "input_manager.h"
@@ -24,6 +25,7 @@ struct sc_screen {
bool open; // track the open/close state to assert correct behavior
#endif
struct sc_display display;
struct sc_input_manager im;
struct sc_frame_buffer fb;
struct sc_fps_counter fps_counter;
@@ -39,9 +41,6 @@ struct sc_screen {
} req;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
struct sc_opengl gl;
struct sc_size frame_size;
struct sc_size content_size; // rotated frame_size
@@ -57,7 +56,6 @@ struct sc_screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool mipmaps;
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.
@@ -137,7 +135,7 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
// react to SDL events
// If this function returns false, scrcpy must exit with an error.
bool
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
+13
View File
@@ -217,6 +217,18 @@ static void test_get_ip_multiline_second_ok(void) {
free(ip);
}
static void test_get_ip_multiline_second_ok_without_cr(void) {
char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.3\n"
"192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.3\n";
char *ip = sc_adb_parse_device_ip(ip_route);
assert(ip);
assert(!strcmp(ip, "192.168.1.3"));
free(ip);
}
static void test_get_ip_no_wlan(void) {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34\r\r\n";
@@ -259,6 +271,7 @@ int main(int argc, char *argv[]) {
test_get_ip_single_line_with_trailing_space();
test_get_ip_multiline_first_ok();
test_get_ip_multiline_second_ok();
test_get_ip_multiline_second_ok_without_cr();
test_get_ip_no_wlan();
test_get_ip_no_wlan_without_eol();
test_get_ip_truncated();
+3 -14
View File
@@ -14,8 +14,8 @@ set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.0
PLATFORM=${ANDROID_PLATFORM:-23}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-23.0.3}
PLATFORM=${ANDROID_PLATFORM:-33}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
@@ -43,17 +43,6 @@ public final class BuildConfig {
}
EOF
STUBS_DIR="$BUILD_DIR/stubs"
rm -rf "$STUBS_DIR"
mkdir -p "$STUBS_DIR"
echo "Generating SDK stubs..."
cd "$SERVER_DIR/src/main/stubs"
javac -bootclasspath "$ANDROID_JAR" \
-d "$STUBS_DIR" \
-source 1.8 -target 1.8 \
android/content/*
cd -
echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl
@@ -63,7 +52,7 @@ cd "$SERVER_DIR/src/main/aidl"
echo "Compiling java sources..."
cd ../java
javac -bootclasspath "$ANDROID_JAR" \
-cp "$LAMBDA_JAR:$GEN_DIR:$STUBS_DIR" \
-cp "$LAMBDA_JAR:$GEN_DIR" \
-d "$CLASSES_DIR" \
-source 1.8 -target 1.8 \
com/genymobile/scrcpy/*.java \
@@ -5,7 +5,6 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioRecord;
@@ -15,7 +14,6 @@ import android.media.MediaRecorder;
import android.os.Build;
import android.os.SystemClock;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
public final class AudioCapture {
@@ -44,28 +42,13 @@ public final class AudioCapture {
return builder.build();
}
private static Method setBuilderContext;
@TargetApi(23)
private static void setBuilderContext(AudioRecord.Builder builder, Context context) {
try {
if (setBuilderContext == null) {
setBuilderContext = AudioRecord.Builder.class.getMethod("setContext", Context.class);
}
setBuilderContext.invoke(builder, context);
} catch (Exception e) {
Ln.e("Could not call AudioRecord.Builder.setContext() method");
//throw new RuntimeException(e);
}
}
@TargetApi(Build.VERSION_CODES.M)
@SuppressLint({"WrongConstant", "MissingPermission"})
private static AudioRecord createAudioRecord() {
AudioRecord.Builder builder = new AudioRecord.Builder();
if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// On older APIs, Workarounds.fillAppInfo() must be called beforehand
setBuilderContext(builder, FakeContext.get());
builder.setContext(FakeContext.get());
}
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
builder.setAudioFormat(createAudioFormat());
@@ -119,7 +102,7 @@ public final class AudioCapture {
}
public void start() throws AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT == 30) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
startWorkaroundAndroid11();
try {
tryStartRecording(3, 100);
@@ -138,21 +121,7 @@ public final class AudioCapture {
}
}
private static Method getTimestampMethod;
private static int getRecorderTimestamp(AudioRecord recorder, AudioTimestamp timestamp) {
try {
if (getTimestampMethod == null) {
getTimestampMethod = AudioRecord.class.getMethod("getTimestamp", AudioTimestamp.class, int.class);
}
return (int) getTimestampMethod.invoke(recorder, timestamp, 0);
} catch (Exception e) {
Ln.e("Could not call AudioRecord.getTimestamp() method");
return AudioRecord.ERROR;
}
}
@TargetApi(24)
@TargetApi(Build.VERSION_CODES.N)
public int read(ByteBuffer directBuffer, int size, MediaCodec.BufferInfo outBufferInfo) {
int r = recorder.read(directBuffer, size);
if (r <= 0) {
@@ -161,7 +130,7 @@ public final class AudioCapture {
long pts;
int ret = getRecorderTimestamp(recorder, timestamp);
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
if (ret == AudioRecord.SUCCESS) {
pts = timestamp.nanoTime / 1000;
} else {
@@ -84,7 +84,7 @@ public final class AudioEncoder implements AsyncProcessor {
return format;
}
@TargetApi(24)
@TargetApi(Build.VERSION_CODES.N)
private void inputThread(MediaCodec mediaCodec, AudioCapture capture) throws IOException, InterruptedException {
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@@ -159,7 +159,7 @@ public final class AudioEncoder implements AsyncProcessor {
@TargetApi(Build.VERSION_CODES.M)
public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT < 30) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
return;
@@ -290,7 +290,7 @@ public final class AudioEncoder implements AsyncProcessor {
}
private class EncoderCallback extends MediaCodec.Callback {
@TargetApi(24)
@TargetApi(Build.VERSION_CODES.N)
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
try {
@@ -1,6 +1,7 @@
package com.genymobile.scrcpy;
import android.media.MediaCodec;
import android.os.Build;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -19,6 +20,12 @@ public final class AudioRawRecorder implements AsyncProcessor {
}
private void record() throws IOException, AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
return;
}
final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE);
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@@ -373,8 +373,8 @@ public class Controller implements AsyncProcessor {
private void getClipboard(int copyKey) {
// On Android >= 7, press the COPY or CUT key if requested
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) {
int key = copyKey == ControlMessage.COPY_KEY_COPY ? 278 : 277;
if (copyKey != ControlMessage.COPY_KEY_NONE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
int key = copyKey == ControlMessage.COPY_KEY_COPY ? KeyEvent.KEYCODE_COPY : KeyEvent.KEYCODE_CUT;
// Wait until the event is finished, to ensure that the clipboard text we read just after is the correct one
device.pressReleaseKeycode(key, Device.INJECT_MODE_WAIT_FOR_FINISH);
}
@@ -397,8 +397,8 @@ public class Controller implements AsyncProcessor {
}
// On Android >= 7, also press the PASTE key if requested
if (paste && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) {
device.pressReleaseKeycode(279, Device.INJECT_MODE_ASYNC);
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE, Device.INJECT_MODE_ASYNC);
}
if (sequence != ControlMessage.SEQUENCE_INVALID) {
@@ -68,8 +68,7 @@ public final class DesktopConnection implements Closeable {
LocalSocket controlSocket = null;
try {
if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(socketName);
try {
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) {
videoSocket = localServerSocket.accept();
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
@@ -81,8 +80,6 @@ public final class DesktopConnection implements Closeable {
if (control) {
controlSocket = localServerSocket.accept();
}
} finally {
localServerSocket.close();
}
} else {
videoSocket = connect(socketName);
@@ -124,7 +124,7 @@ public final class Device {
}
// main display or any display on Android >= Q
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= 29;
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10");
}
@@ -173,7 +173,7 @@ public final class Device {
}
public static boolean supportsInputEvents(int displayId) {
return displayId == 0 || Build.VERSION.SDK_INT >= 29;
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
}
public boolean supportsInputEvents() {
@@ -277,7 +277,7 @@ public final class Device {
* @param mode one of the {@code POWER_MODE_*} constants
*/
public static boolean setScreenPowerMode(int mode) {
if (Build.VERSION.SDK_INT >= 29) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Change the power mode for all physical displays
long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds();
if (physicalDisplayIds == null) {
@@ -26,13 +26,15 @@ public final class FakeContext extends ContextWrapper {
return PACKAGE_NAME;
}
@Override
public String getOpPackageName() {
return PACKAGE_NAME;
}
@TargetApi(31)
@TargetApi(Build.VERSION_CODES.S)
@Override
public AttributionSource getAttributionSource() {
AttributionSource.Builder builder = new AttributionSource.Builder(0);
AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID);
builder.setPackageName(PACKAGE_NAME);
return builder.build();
}
@@ -252,7 +252,7 @@ public class ScreenEncoder implements Device.RotationListener {
private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < 30 || (Build.VERSION.SDK_INT == 30 && !"S"
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S"
.equals(Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);
}
@@ -88,7 +88,7 @@ public final class Server {
// 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 the application context for the AudioRecord to work.
if (audio && Build.VERSION.SDK_INT == 30) {
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
Workarounds.fillAppContext();
}
@@ -34,7 +34,7 @@ public final class Settings {
}
public static String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= 30) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
@@ -47,7 +47,7 @@ public final class Settings {
}
public static void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= 30) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
@@ -60,7 +60,7 @@ public final class Settings {
}
public static String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= 30) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);
@@ -7,7 +7,7 @@ public enum VideoCodec implements Codec {
H264(0x68_32_36_34, "h264", MediaFormat.MIMETYPE_VIDEO_AVC),
H265(0x68_32_36_35, "h265", MediaFormat.MIMETYPE_VIDEO_HEVC),
@SuppressLint("InlinedApi") // introduced in API 21
AV1(0x00_61_76_31, "av1", "video/av01");
AV1(0x00_61_76_31, "av1", MediaFormat.MIMETYPE_VIDEO_AV1);
private final int id; // 4-byte ASCII representation of the name
private final String name;
@@ -51,7 +51,7 @@ public class ActivityManager {
return removeContentProviderExternalMethod;
}
@TargetApi(29)
@TargetApi(Build.VERSION_CODES.Q)
private ContentProvider getContentProviderExternal(String name, IBinder token) {
try {
Method method = getGetContentProviderExternalMethod();
@@ -26,7 +26,7 @@ public class ClipboardManager {
private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
if (getPrimaryClipMethod == null) {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
} else {
try {
@@ -37,8 +37,13 @@ public class ClipboardManager {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
getMethodVersion = 1;
} catch (NoSuchMethodException e2) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
getMethodVersion = 2;
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
getMethodVersion = 2;
} catch (NoSuchMethodException e3) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
getMethodVersion = 3;
}
}
}
}
@@ -48,7 +53,7 @@ public class ClipboardManager {
private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
if (setPrimaryClipMethod == null) {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
} else {
try {
@@ -71,7 +76,7 @@ public class ClipboardManager {
private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager)
throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
}
@@ -80,14 +85,16 @@ public class ClipboardManager {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
case 1:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
default:
case 2:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
default:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
}
}
private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData)
throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
return;
}
@@ -133,7 +140,7 @@ public class ClipboardManager {
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager,
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
return;
}
@@ -153,7 +160,7 @@ public class ClipboardManager {
private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
if (addPrimaryClipChangedListener == null) {
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
addPrimaryClipChangedListener = manager.getClass()
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
} else {
@@ -54,7 +54,7 @@ public class ContentProvider implements Closeable {
@SuppressLint("PrivateApi")
private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) {
if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
callMethod = provider.getClass().getMethod("call", AttributionSource.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 0;
} else {
@@ -83,7 +83,7 @@ public class ContentProvider implements Closeable {
Method method = getCallMethod();
Object[] args;
if (Build.VERSION.SDK_INT >= 31 && callMethodVersion == 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && callMethodVersion == 0) {
args = new Object[]{FakeContext.get().getAttributionSource(), "settings", callMethod, arg, extras};
} else {
switch (callMethodVersion) {
@@ -90,7 +90,7 @@ public final class SurfaceControl {
if (getBuiltInDisplayMethod == null) {
// the method signature has changed in Android Q
// <https://github.com/Genymobile/scrcpy/issues/586>
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else {
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
@@ -102,7 +102,7 @@ public final class SurfaceControl {
public static IBinder getBuiltInDisplay() {
try {
Method method = getGetBuiltInDisplayMethod();
if (Build.VERSION.SDK_INT < 29) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// call getBuiltInDisplay(0)
return (IBinder) method.invoke(null, 0);
}
@@ -1,15 +0,0 @@
package android.content;
public class AttributionSource {
public static class Builder {
public Builder(int uid) {
throw new UnsupportedOperationException();
}
public Builder setPackageName(String value) {
throw new UnsupportedOperationException();
}
public AttributionSource build() {
throw new UnsupportedOperationException();
}
}
}