Compare commits

..

1 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
6 changed files with 330 additions and 184 deletions

View File

@@ -183,10 +183,6 @@ It may only work over USB.
Also see \fB\-\-hid\-keyboard\fR. Also see \fB\-\-hid\-keyboard\fR.
.TP
.B \-\-no\-audio
Disable audio forwarding.
.TP .TP
.B \-\-no\-cleanup .B \-\-no\-cleanup
By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit. By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit.

View File

@@ -30,7 +30,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_CONTEXT_PROFILE_CORE);
LOGD("Creating OpenGL Core Profile context"); LOGD("Creating OpenGL Core Profile Context");
display->gl_context = SDL_GL_CreateContext(window); display->gl_context = SDL_GL_CreateContext(window);
if (!display->gl_context) { if (!display->gl_context) {
LOGE("Could not create OpenGL context: %s", SDL_GetError()); LOGE("Could not create OpenGL context: %s", SDL_GetError());

View File

@@ -630,6 +630,17 @@ aoa_hid_end:
} }
controller_started = true; controller_started = true;
controller = &s->controller; controller = &s->controller;
if (options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
} }
// There is a controller if and only if control is enabled // There is a controller if and only if control is enabled
@@ -717,18 +728,6 @@ aoa_hid_end:
audio_demuxer_started = true; audio_demuxer_started = true;
} }
// If the device screen is to be turned off, send the control message after
// everything is set up
if (options->control && options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
ret = event_loop(s); ret = event_loop(s);
LOGD("quit..."); LOGD("quit...");

View File

@@ -86,8 +86,8 @@ public final class AudioCapture {
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
if (attempts == 0) { if (attempts == 0) {
Ln.e("Failed to start audio capture"); Ln.e("Failed to start audio capture");
Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " +
+ "scrcpy."); "scrcpy.");
throw new AudioCaptureForegroundException(); throw new AudioCaptureForegroundException();
} else { } else {
Ln.d("Failed to start audio capture, retrying..."); Ln.d("Failed to start audio capture, retrying...");

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import android.graphics.Rect; import android.graphics.Rect;
import java.util.List; import java.util.List;
import java.util.Locale;
public class Options { public class Options {
@@ -47,82 +46,166 @@ public class Options {
return logLevel; return logLevel;
} }
public void setLogLevel(Ln.Level logLevel) {
this.logLevel = logLevel;
}
public int getScid() { public int getScid() {
return scid; return scid;
} }
public void setScid(int scid) {
this.scid = scid;
}
public boolean getAudio() { public boolean getAudio() {
return audio; return audio;
} }
public void setAudio(boolean audio) {
this.audio = audio;
}
public int getMaxSize() { public int getMaxSize() {
return maxSize; return maxSize;
} }
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public VideoCodec getVideoCodec() { public VideoCodec getVideoCodec() {
return videoCodec; return videoCodec;
} }
public void setVideoCodec(VideoCodec videoCodec) {
this.videoCodec = videoCodec;
}
public AudioCodec getAudioCodec() { public AudioCodec getAudioCodec() {
return audioCodec; return audioCodec;
} }
public void setAudioCodec(AudioCodec audioCodec) {
this.audioCodec = audioCodec;
}
public int getVideoBitRate() { public int getVideoBitRate() {
return videoBitRate; return videoBitRate;
} }
public void setVideoBitRate(int videoBitRate) {
this.videoBitRate = videoBitRate;
}
public int getAudioBitRate() { public int getAudioBitRate() {
return audioBitRate; return audioBitRate;
} }
public void setAudioBitRate(int audioBitRate) {
this.audioBitRate = audioBitRate;
}
public int getMaxFps() { public int getMaxFps() {
return maxFps; return maxFps;
} }
public void setMaxFps(int maxFps) {
this.maxFps = maxFps;
}
public int getLockVideoOrientation() { public int getLockVideoOrientation() {
return lockVideoOrientation; return lockVideoOrientation;
} }
public void setLockVideoOrientation(int lockVideoOrientation) {
this.lockVideoOrientation = lockVideoOrientation;
}
public boolean isTunnelForward() { public boolean isTunnelForward() {
return tunnelForward; return tunnelForward;
} }
public void setTunnelForward(boolean tunnelForward) {
this.tunnelForward = tunnelForward;
}
public Rect getCrop() { public Rect getCrop() {
return crop; return crop;
} }
public void setCrop(Rect crop) {
this.crop = crop;
}
public boolean getControl() { public boolean getControl() {
return control; return control;
} }
public void setControl(boolean control) {
this.control = control;
}
public int getDisplayId() { public int getDisplayId() {
return displayId; return displayId;
} }
public void setDisplayId(int displayId) {
this.displayId = displayId;
}
public boolean getShowTouches() { public boolean getShowTouches() {
return showTouches; return showTouches;
} }
public void setShowTouches(boolean showTouches) {
this.showTouches = showTouches;
}
public boolean getStayAwake() { public boolean getStayAwake() {
return stayAwake; return stayAwake;
} }
public void setStayAwake(boolean stayAwake) {
this.stayAwake = stayAwake;
}
public List<CodecOption> getVideoCodecOptions() { public List<CodecOption> getVideoCodecOptions() {
return videoCodecOptions; return videoCodecOptions;
} }
public void setVideoCodecOptions(List<CodecOption> videoCodecOptions) {
this.videoCodecOptions = videoCodecOptions;
}
public List<CodecOption> getAudioCodecOptions() { public List<CodecOption> getAudioCodecOptions() {
return audioCodecOptions; return audioCodecOptions;
} }
public void setAudioCodecOptions(List<CodecOption> audioCodecOptions) {
this.audioCodecOptions = audioCodecOptions;
}
public String getVideoEncoder() { public String getVideoEncoder() {
return videoEncoder; return videoEncoder;
} }
public void setVideoEncoder(String videoEncoder) {
this.videoEncoder = videoEncoder;
}
public String getAudioEncoder() { public String getAudioEncoder() {
return audioEncoder; return audioEncoder;
} }
public void setAudioEncoder(String audioEncoder) {
this.audioEncoder = audioEncoder;
}
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose;
}
public boolean getPowerOffScreenOnClose() { public boolean getPowerOffScreenOnClose() {
return this.powerOffScreenOnClose; return this.powerOffScreenOnClose;
} }
@@ -131,204 +214,79 @@ public class Options {
return clipboardAutosync; return clipboardAutosync;
} }
public void setClipboardAutosync(boolean clipboardAutosync) {
this.clipboardAutosync = clipboardAutosync;
}
public boolean getDownsizeOnError() { public boolean getDownsizeOnError() {
return downsizeOnError; return downsizeOnError;
} }
public void setDownsizeOnError(boolean downsizeOnError) {
this.downsizeOnError = downsizeOnError;
}
public boolean getCleanup() { public boolean getCleanup() {
return cleanup; return cleanup;
} }
public void setCleanup(boolean cleanup) {
this.cleanup = cleanup;
}
public boolean getPowerOn() { public boolean getPowerOn() {
return powerOn; return powerOn;
} }
public void setPowerOn(boolean powerOn) {
this.powerOn = powerOn;
}
public boolean getListEncoders() { public boolean getListEncoders() {
return listEncoders; return listEncoders;
} }
public void setListEncoders(boolean listEncoders) {
this.listEncoders = listEncoders;
}
public boolean getListDisplays() { public boolean getListDisplays() {
return listDisplays; return listDisplays;
} }
public void setListDisplays(boolean listDisplays) {
this.listDisplays = listDisplays;
}
public boolean getSendDeviceMeta() { public boolean getSendDeviceMeta() {
return sendDeviceMeta; return sendDeviceMeta;
} }
public void setSendDeviceMeta(boolean sendDeviceMeta) {
this.sendDeviceMeta = sendDeviceMeta;
}
public boolean getSendFrameMeta() { public boolean getSendFrameMeta() {
return sendFrameMeta; return sendFrameMeta;
} }
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
public boolean getSendDummyByte() { public boolean getSendDummyByte() {
return sendDummyByte; return sendDummyByte;
} }
public void setSendDummyByte(boolean sendDummyByte) {
this.sendDummyByte = sendDummyByte;
}
public boolean getSendCodecMeta() { public boolean getSendCodecMeta() {
return sendCodecMeta; return sendCodecMeta;
} }
@SuppressWarnings("MethodLength") public void setSendCodecMeta(boolean sendCodecMeta) {
public static Options parse(String... args) { this.sendCodecMeta = sendCodecMeta;
if (args.length < 1) {
throw new IllegalArgumentException("Missing client version");
}
String clientVersion = args[0];
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
throw new IllegalArgumentException(
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
}
Options options = new Options();
for (int i = 1; i < args.length; ++i) {
String arg = args[i];
int equalIndex = arg.indexOf('=');
if (equalIndex == -1) {
throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\"");
}
String key = arg.substring(0, equalIndex);
String value = arg.substring(equalIndex + 1);
switch (key) {
case "scid":
int scid = Integer.parseInt(value, 0x10);
if (scid < -1) {
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
}
options.scid = scid;
break;
case "log_level":
options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
break;
case "audio":
options.audio = Boolean.parseBoolean(value);
break;
case "video_codec":
VideoCodec videoCodec = VideoCodec.findByName(value);
if (videoCodec == null) {
throw new IllegalArgumentException("Video codec " + value + " not supported");
}
options.videoCodec = videoCodec;
break;
case "audio_codec":
AudioCodec audioCodec = AudioCodec.findByName(value);
if (audioCodec == null) {
throw new IllegalArgumentException("Audio codec " + value + " not supported");
}
options.audioCodec = audioCodec;
break;
case "max_size":
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
break;
case "video_bit_rate":
options.videoBitRate = Integer.parseInt(value);
break;
case "audio_bit_rate":
options.audioBitRate = Integer.parseInt(value);
break;
case "max_fps":
options.maxFps = Integer.parseInt(value);
break;
case "lock_video_orientation":
options.lockVideoOrientation = Integer.parseInt(value);
break;
case "tunnel_forward":
options.tunnelForward = Boolean.parseBoolean(value);
break;
case "crop":
options.crop = parseCrop(value);
break;
case "control":
options.control = Boolean.parseBoolean(value);
break;
case "display_id":
options.displayId = Integer.parseInt(value);
break;
case "show_touches":
options.showTouches = Boolean.parseBoolean(value);
break;
case "stay_awake":
options.stayAwake = Boolean.parseBoolean(value);
break;
case "video_codec_options":
options.videoCodecOptions = CodecOption.parse(value);
break;
case "audio_codec_options":
options.audioCodecOptions = CodecOption.parse(value);
break;
case "video_encoder":
if (!value.isEmpty()) {
options.videoEncoder = value;
}
break;
case "audio_encoder":
if (!value.isEmpty()) {
options.audioEncoder = value;
}
case "power_off_on_close":
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
break;
case "clipboard_autosync":
options.clipboardAutosync = Boolean.parseBoolean(value);
break;
case "downsize_on_error":
options.downsizeOnError = Boolean.parseBoolean(value);
break;
case "cleanup":
options.cleanup = Boolean.parseBoolean(value);
break;
case "power_on":
options.powerOn = Boolean.parseBoolean(value);
break;
case "list_encoders":
options.listEncoders = Boolean.parseBoolean(value);
break;
case "list_displays":
options.listDisplays = Boolean.parseBoolean(value);
break;
case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value);
break;
case "send_frame_meta":
options.sendFrameMeta = Boolean.parseBoolean(value);
break;
case "send_dummy_byte":
options.sendDummyByte = Boolean.parseBoolean(value);
break;
case "send_codec_meta":
options.sendCodecMeta = Boolean.parseBoolean(value);
break;
case "raw_video_stream":
boolean rawVideoStream = Boolean.parseBoolean(value);
if (rawVideoStream) {
options.sendDeviceMeta = false;
options.sendFrameMeta = false;
options.sendDummyByte = false;
options.sendCodecMeta = false;
}
break;
default:
Ln.w("Unknown server option: " + key);
break;
}
}
return options;
}
private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
return new Rect(x, y, x + width, y + height);
} }
} }

View File

@@ -1,11 +1,13 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import android.graphics.Rect;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build; import android.os.Build;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
public final class Server { public final class Server {
@@ -159,12 +161,203 @@ public final class Server {
return thread; return thread;
} }
@SuppressWarnings("MethodLength")
private static Options createOptions(String... args) {
if (args.length < 1) {
throw new IllegalArgumentException("Missing client version");
}
String clientVersion = args[0];
if (!clientVersion.equals(BuildConfig.VERSION_NAME)) {
throw new IllegalArgumentException(
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
}
Options options = new Options();
for (int i = 1; i < args.length; ++i) {
String arg = args[i];
int equalIndex = arg.indexOf('=');
if (equalIndex == -1) {
throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\"");
}
String key = arg.substring(0, equalIndex);
String value = arg.substring(equalIndex + 1);
switch (key) {
case "scid":
int scid = Integer.parseInt(value, 0x10);
if (scid < -1) {
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
}
options.setScid(scid);
break;
case "log_level":
Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
options.setLogLevel(level);
break;
case "audio":
boolean audio = Boolean.parseBoolean(value);
options.setAudio(audio);
break;
case "video_codec":
VideoCodec videoCodec = VideoCodec.findByName(value);
if (videoCodec == null) {
throw new IllegalArgumentException("Video codec " + value + " not supported");
}
options.setVideoCodec(videoCodec);
break;
case "audio_codec":
AudioCodec audioCodec = AudioCodec.findByName(value);
if (audioCodec == null) {
throw new IllegalArgumentException("Audio codec " + value + " not supported");
}
options.setAudioCodec(audioCodec);
break;
case "max_size":
int maxSize = Integer.parseInt(value) & ~7; // multiple of 8
options.setMaxSize(maxSize);
break;
case "video_bit_rate":
int videoBitRate = Integer.parseInt(value);
options.setVideoBitRate(videoBitRate);
break;
case "audio_bit_rate":
int audioBitRate = Integer.parseInt(value);
options.setAudioBitRate(audioBitRate);
break;
case "max_fps":
int maxFps = Integer.parseInt(value);
options.setMaxFps(maxFps);
break;
case "lock_video_orientation":
int lockVideoOrientation = Integer.parseInt(value);
options.setLockVideoOrientation(lockVideoOrientation);
break;
case "tunnel_forward":
boolean tunnelForward = Boolean.parseBoolean(value);
options.setTunnelForward(tunnelForward);
break;
case "crop":
Rect crop = parseCrop(value);
options.setCrop(crop);
break;
case "control":
boolean control = Boolean.parseBoolean(value);
options.setControl(control);
break;
case "display_id":
int displayId = Integer.parseInt(value);
options.setDisplayId(displayId);
break;
case "show_touches":
boolean showTouches = Boolean.parseBoolean(value);
options.setShowTouches(showTouches);
break;
case "stay_awake":
boolean stayAwake = Boolean.parseBoolean(value);
options.setStayAwake(stayAwake);
break;
case "video_codec_options":
List<CodecOption> videoCodecOptions = CodecOption.parse(value);
options.setVideoCodecOptions(videoCodecOptions);
break;
case "audio_codec_options":
List<CodecOption> audioCodecOptions = CodecOption.parse(value);
options.setAudioCodecOptions(audioCodecOptions);
break;
case "video_encoder":
if (!value.isEmpty()) {
options.setVideoEncoder(value);
}
break;
case "audio_encoder":
if (!value.isEmpty()) {
options.setAudioEncoder(value);
}
case "power_off_on_close":
boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
break;
case "clipboard_autosync":
boolean clipboardAutosync = Boolean.parseBoolean(value);
options.setClipboardAutosync(clipboardAutosync);
break;
case "downsize_on_error":
boolean downsizeOnError = Boolean.parseBoolean(value);
options.setDownsizeOnError(downsizeOnError);
break;
case "cleanup":
boolean cleanup = Boolean.parseBoolean(value);
options.setCleanup(cleanup);
break;
case "power_on":
boolean powerOn = Boolean.parseBoolean(value);
options.setPowerOn(powerOn);
break;
case "list_encoders":
boolean listEncoders = Boolean.parseBoolean(value);
options.setListEncoders(listEncoders);
break;
case "list_displays":
boolean listDisplays = Boolean.parseBoolean(value);
options.setListDisplays(listDisplays);
break;
case "send_device_meta":
boolean sendDeviceMeta = Boolean.parseBoolean(value);
options.setSendDeviceMeta(sendDeviceMeta);
break;
case "send_frame_meta":
boolean sendFrameMeta = Boolean.parseBoolean(value);
options.setSendFrameMeta(sendFrameMeta);
break;
case "send_dummy_byte":
boolean sendDummyByte = Boolean.parseBoolean(value);
options.setSendDummyByte(sendDummyByte);
break;
case "send_codec_meta":
boolean sendCodecMeta = Boolean.parseBoolean(value);
options.setSendCodecMeta(sendCodecMeta);
break;
case "raw_video_stream":
boolean rawVideoStream = Boolean.parseBoolean(value);
if (rawVideoStream) {
options.setSendDeviceMeta(false);
options.setSendFrameMeta(false);
options.setSendDummyByte(false);
options.setSendCodecMeta(false);
}
break;
default:
Ln.w("Unknown server option: " + key);
break;
}
}
return options;
}
private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
return new Rect(x, y, x + width, y + height);
}
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> { Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
Ln.e("Exception on thread " + t, e); Ln.e("Exception on thread " + t, e);
}); });
Options options = Options.parse(args); Options options = createOptions(args);
Ln.initLogLevel(options.getLogLevel()); Ln.initLogLevel(options.getLogLevel());