Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4108a311a | ||
|
|
430ecc36c1 | ||
|
|
1ad2abec1d | ||
|
|
c61f86af0b | ||
|
|
8fe6d78070 | ||
|
|
3f532ab5b5 | ||
|
|
b006c6f3ef | ||
|
|
662615baa8 | ||
|
|
0f92c17b62 | ||
|
|
9f4c16bc77 | ||
|
|
5e964c7cb9 | ||
|
|
90c2ce8885 | ||
|
|
98d4e64a0f | ||
|
|
f9cbd8726f | ||
|
|
101d7908b2 | ||
|
|
c1a4c91c51 | ||
|
|
5865b5f9b4 | ||
|
|
6ff4c92f3e | ||
|
|
d9e5356586 | ||
|
|
60b39cdda5 | ||
|
|
b581fd78c1 | ||
|
|
97d773524c | ||
|
|
6b396f8297 | ||
|
|
6b9365e71b | ||
|
|
be6ec050b3 | ||
|
|
5782bb8743 | ||
|
|
159516b1d7 | ||
|
|
398e477738 | ||
|
|
db142b7c65 | ||
|
|
4533f186c0 | ||
|
|
b52741583c | ||
|
|
561492b199 | ||
|
|
a9d0a01a85 | ||
|
|
78bf9f4e27 | ||
|
|
d71ddc06db | ||
|
|
9d207456af | ||
|
|
00fdfcc4c7 | ||
|
|
2a1661672c | ||
|
|
7ca65b7452 | ||
|
|
ad9bf02300 |
@@ -31,10 +31,6 @@ Select an audio codec (opus or aac).
|
||||
|
||||
Default is opus.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-encoder " name
|
||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-b, \-\-bit\-rate " value
|
||||
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||
@@ -98,7 +94,7 @@ Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-\-encoder " name
|
||||
Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-codec\fR).
|
||||
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
||||
|
||||
.TP
|
||||
.B \-\-force\-adb\-forward
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
#define OPT_NO_AUDIO 1041
|
||||
#define OPT_AUDIO_BIT_RATE 1042
|
||||
#define OPT_AUDIO_CODEC 1043
|
||||
#define OPT_AUDIO_ENCODER_NAME 1044
|
||||
|
||||
struct sc_option {
|
||||
char shortopt;
|
||||
@@ -117,13 +116,6 @@ static const struct sc_option options[] = {
|
||||
.text = "Select an audio codec (opus or aac).\n"
|
||||
"Default is opus.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_ENCODER_NAME,
|
||||
.longopt = "audio-encoder",
|
||||
.argdesc = "name",
|
||||
.text = "Use a specific MediaCodec audio encoder (depending on the "
|
||||
"codec provided by --audio-codec).",
|
||||
},
|
||||
{
|
||||
.shortopt = 'b',
|
||||
.longopt = "bit-rate",
|
||||
@@ -199,8 +191,7 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_ENCODER_NAME,
|
||||
.longopt = "encoder",
|
||||
.argdesc = "name",
|
||||
.text = "Use a specific MediaCodec encoder (depending on the codec "
|
||||
"provided by --codec).",
|
||||
.text = "Use a specific MediaCodec encoder (must be a H.264 encoder).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
||||
@@ -1643,9 +1634,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_ENCODER_NAME:
|
||||
opts->encoder_name = optarg;
|
||||
break;
|
||||
case OPT_AUDIO_ENCODER_NAME:
|
||||
opts->audio_encoder_name = optarg;
|
||||
break;
|
||||
case OPT_FORCE_ADB_FORWARD:
|
||||
opts->force_adb_forward = true;
|
||||
break;
|
||||
|
||||
@@ -97,7 +97,6 @@ struct scrcpy_options {
|
||||
const char *render_driver;
|
||||
const char *codec_options;
|
||||
const char *encoder_name;
|
||||
const char *audio_encoder_name;
|
||||
#ifdef HAVE_V4L2
|
||||
const char *v4l2_device;
|
||||
#endif
|
||||
|
||||
@@ -630,6 +630,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
|
||||
|
||||
sc_mutex_lock(&recorder->mutex);
|
||||
// EOS also stops the recorder
|
||||
// TODO must stop only once both video and audio stream are complete
|
||||
recorder->stopped = true;
|
||||
sc_cond_signal(&recorder->queue_cond);
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
|
||||
@@ -368,7 +368,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.stay_awake = options->stay_awake,
|
||||
.codec_options = options->codec_options,
|
||||
.encoder_name = options->encoder_name,
|
||||
.audio_encoder_name = options->audio_encoder_name,
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
.power_off_on_close = options->power_off_on_close,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
|
||||
@@ -73,7 +73,6 @@ sc_server_params_destroy(struct sc_server_params *params) {
|
||||
free((char *) params->crop);
|
||||
free((char *) params->codec_options);
|
||||
free((char *) params->encoder_name);
|
||||
free((char *) params->audio_encoder_name);
|
||||
free((char *) params->tcpip_dst);
|
||||
}
|
||||
|
||||
@@ -98,7 +97,6 @@ sc_server_params_copy(struct sc_server_params *dst,
|
||||
COPY(crop);
|
||||
COPY(codec_options);
|
||||
COPY(encoder_name);
|
||||
COPY(audio_encoder_name);
|
||||
COPY(tcpip_dst);
|
||||
#undef COPY
|
||||
|
||||
@@ -272,9 +270,6 @@ execute_server(struct sc_server *server,
|
||||
if (params->encoder_name) {
|
||||
ADD_PARAM("encoder_name=%s", params->encoder_name);
|
||||
}
|
||||
if (params->audio_encoder_name) {
|
||||
ADD_PARAM("audio_encoder_name=%s", params->audio_encoder_name);
|
||||
}
|
||||
if (params->power_off_on_close) {
|
||||
ADD_PARAM("power_off_on_close=true");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ struct sc_server_params {
|
||||
const char *crop;
|
||||
const char *codec_options;
|
||||
const char *encoder_name;
|
||||
const char *audio_encoder_name;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
|
||||
@@ -45,7 +45,6 @@ public final class AudioEncoder {
|
||||
|
||||
private final Streamer streamer;
|
||||
private final int bitRate;
|
||||
private final String encoderName;
|
||||
|
||||
private AudioRecord recorder;
|
||||
private MediaCodec mediaCodec;
|
||||
@@ -63,10 +62,9 @@ public final class AudioEncoder {
|
||||
|
||||
private boolean ended;
|
||||
|
||||
public AudioEncoder(Streamer streamer, int bitRate, String encoderName) {
|
||||
public AudioEncoder(Streamer streamer, int bitRate) {
|
||||
this.streamer = streamer;
|
||||
this.bitRate = bitRate;
|
||||
this.encoderName = encoderName;
|
||||
}
|
||||
|
||||
private static AudioFormat createAudioFormat() {
|
||||
@@ -202,23 +200,17 @@ public final class AudioEncoder {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void encode() throws IOException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
streamer.writeDisableStream();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
Codec codec = streamer.getCodec();
|
||||
String mimeType = streamer.getCodec().getMimeType();
|
||||
|
||||
mediaCodec = createMediaCodec(codec, encoderName);
|
||||
mediaCodec = MediaCodec.createEncoderByType(mimeType); // may throw IOException
|
||||
recorder = createAudioRecord();
|
||||
|
||||
mediaCodecThread = new HandlerThread("AudioEncoder");
|
||||
mediaCodecThread.start();
|
||||
|
||||
MediaFormat format = createFormat(codec.getMimeType(), bitRate);
|
||||
MediaFormat format = createFormat(mimeType, bitRate);
|
||||
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
|
||||
@@ -257,8 +249,6 @@ public final class AudioEncoder {
|
||||
mediaCodec.start();
|
||||
inputThread.start();
|
||||
outputThread.start();
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (Throwable e) {
|
||||
if (mediaCodec != null) {
|
||||
mediaCodec.release();
|
||||
@@ -276,21 +266,6 @@ public final class AudioEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||
if (encoderName != null) {
|
||||
Ln.d("Creating audio encoder by name: '" + encoderName + "'");
|
||||
try {
|
||||
return MediaCodec.createByCodecName(encoderName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName));
|
||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
||||
}
|
||||
}
|
||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
||||
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
|
||||
return mediaCodec;
|
||||
}
|
||||
|
||||
private void cleanUp() {
|
||||
mediaCodecThread.getLooper().quit();
|
||||
inputThread.interrupt();
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class CodecUtils {
|
||||
|
||||
private CodecUtils() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static String buildUnknownEncoderMessage(Codec codec, String encoderName) {
|
||||
StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found");
|
||||
MediaCodecInfo[] encoders = listEncoders(codec.getMimeType());
|
||||
if (encoders != null && encoders.length > 0) {
|
||||
msg.append("\nTry to use one of the available encoders:");
|
||||
String codecOption = codec.getType() == Codec.Type.VIDEO ? "codec" : "audio-codec";
|
||||
for (MediaCodecInfo encoder : encoders) {
|
||||
msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName());
|
||||
msg.append(" --encoder='").append(encoder.getName()).append("'");
|
||||
}
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
private static MediaCodecInfo[] listEncoders(String mimeType) {
|
||||
List<MediaCodecInfo> result = new ArrayList<>();
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
|
||||
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
|
||||
result.add(codecInfo);
|
||||
}
|
||||
}
|
||||
return result.toArray(new MediaCodecInfo[result.size()]);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class ConfigurationException extends Exception {
|
||||
public ConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -61,12 +61,12 @@ public final class Device {
|
||||
|
||||
private final boolean supportsInputEvents;
|
||||
|
||||
public Device(Options options) throws ConfigurationException {
|
||||
public Device(Options options) {
|
||||
displayId = options.getDisplayId();
|
||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||
if (displayInfo == null) {
|
||||
Ln.e(buildUnknownDisplayIdMessage(displayId));
|
||||
throw new ConfigurationException("Unknown display id: " + displayId);
|
||||
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
||||
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||
}
|
||||
|
||||
int displayInfoFlags = displayInfo.getFlags();
|
||||
@@ -130,18 +130,6 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
private static String buildUnknownDisplayIdMessage(int displayId) {
|
||||
StringBuilder msg = new StringBuilder("Display ").append(displayId).append(" not found");
|
||||
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
||||
if (displayIds != null && displayIds.length > 0) {
|
||||
msg.append("\nTry to use one of the available display ids:");
|
||||
for (int id : displayIds) {
|
||||
msg.append("\n scrcpy --display=").append(id);
|
||||
}
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
public synchronized void setMaxSize(int newMaxSize) {
|
||||
maxSize = newMaxSize;
|
||||
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class InvalidDisplayIdException extends RuntimeException {
|
||||
|
||||
private final int displayId;
|
||||
private final int[] availableDisplayIds;
|
||||
|
||||
public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) {
|
||||
super("There is no display having id " + displayId);
|
||||
this.displayId = displayId;
|
||||
this.availableDisplayIds = availableDisplayIds;
|
||||
}
|
||||
|
||||
public int getDisplayId() {
|
||||
return displayId;
|
||||
}
|
||||
|
||||
public int[] getAvailableDisplayIds() {
|
||||
return availableDisplayIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodecInfo;
|
||||
|
||||
public class InvalidEncoderException extends RuntimeException {
|
||||
|
||||
private final String name;
|
||||
private final MediaCodecInfo[] availableEncoders;
|
||||
|
||||
public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) {
|
||||
super("There is no encoder having name '" + name + '"');
|
||||
this.name = name;
|
||||
this.availableEncoders = availableEncoders;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public MediaCodecInfo[] getAvailableEncoders() {
|
||||
return availableEncoders;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ public class Options {
|
||||
private boolean stayAwake;
|
||||
private List<CodecOption> codecOptions;
|
||||
private String encoderName;
|
||||
private String audioEncoderName;
|
||||
private boolean powerOffScreenOnClose;
|
||||
private boolean clipboardAutosync = true;
|
||||
private boolean downsizeOnError = true;
|
||||
@@ -181,14 +180,6 @@ public class Options {
|
||||
this.encoderName = encoderName;
|
||||
}
|
||||
|
||||
public String getAudioEncoderName() {
|
||||
return audioEncoderName;
|
||||
}
|
||||
|
||||
public void setAudioEncoderName(String audioEncoderName) {
|
||||
this.audioEncoderName = audioEncoderName;
|
||||
}
|
||||
|
||||
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
|
||||
this.powerOffScreenOnClose = powerOffScreenOnClose;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||
import android.graphics.Rect;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
@@ -13,6 +14,8 @@ import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -59,10 +62,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
return rotationChanged.getAndSet(false);
|
||||
}
|
||||
|
||||
public void streamScreen() throws IOException, ConfigurationException {
|
||||
Codec codec = streamer.getCodec();
|
||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||
MediaFormat format = createFormat(codec.getMimeType(), bitRate, maxFps, codecOptions);
|
||||
public void streamScreen() throws IOException {
|
||||
String videoMimeType = streamer.getCodec().getMimeType();
|
||||
MediaCodec codec = createCodec(videoMimeType, encoderName);
|
||||
MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions);
|
||||
IBinder display = createDisplay();
|
||||
device.setRotationListener(this);
|
||||
|
||||
@@ -81,8 +84,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
|
||||
Surface surface = null;
|
||||
try {
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
surface = mediaCodec.createInputSurface();
|
||||
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
surface = codec.createInputSurface();
|
||||
|
||||
// does not include the locked video orientation
|
||||
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
||||
@@ -90,11 +93,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
int layerStack = device.getLayerStack();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
|
||||
mediaCodec.start();
|
||||
codec.start();
|
||||
|
||||
alive = encode(mediaCodec, streamer);
|
||||
alive = encode(codec, streamer);
|
||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||
mediaCodec.stop();
|
||||
codec.stop();
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||
if (!prepareRetry(device, screenInfo)) {
|
||||
@@ -103,14 +106,14 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
Ln.i("Retrying...");
|
||||
alive = true;
|
||||
} finally {
|
||||
mediaCodec.reset();
|
||||
codec.reset();
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
}
|
||||
}
|
||||
} while (alive);
|
||||
} finally {
|
||||
mediaCodec.release();
|
||||
codec.release();
|
||||
device.setRotationListener(null);
|
||||
SurfaceControl.destroyDisplay(display);
|
||||
}
|
||||
@@ -196,19 +199,30 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
return !eof;
|
||||
}
|
||||
|
||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||
private static MediaCodecInfo[] listEncoders(String videoMimeType) {
|
||||
List<MediaCodecInfo> result = new ArrayList<>();
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
|
||||
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(videoMimeType)) {
|
||||
result.add(codecInfo);
|
||||
}
|
||||
}
|
||||
return result.toArray(new MediaCodecInfo[result.size()]);
|
||||
}
|
||||
|
||||
private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException {
|
||||
if (encoderName != null) {
|
||||
Ln.d("Creating encoder by name: '" + encoderName + "'");
|
||||
try {
|
||||
return MediaCodec.createByCodecName(encoderName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName));
|
||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
||||
MediaCodecInfo[] encoders = listEncoders(videoMimeType);
|
||||
throw new InvalidEncoderException(encoderName, encoders);
|
||||
}
|
||||
}
|
||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
||||
Ln.d("Using encoder: '" + mediaCodec.getName() + "'");
|
||||
return mediaCodec;
|
||||
MediaCodec codec = MediaCodec.createEncoderByType(videoMimeType);
|
||||
Ln.d("Using encoder: '" + codec.getName() + "'");
|
||||
return codec;
|
||||
}
|
||||
|
||||
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
|
||||
@@ -58,7 +59,7 @@ public final class Server {
|
||||
}
|
||||
}
|
||||
|
||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||
private static void scrcpy(Options options) throws IOException {
|
||||
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
||||
final Device device = new Device(options);
|
||||
List<CodecOption> codecOptions = options.getCodecOptions();
|
||||
@@ -92,9 +93,6 @@ public final class Server {
|
||||
Workarounds.fillAppInfo();
|
||||
}
|
||||
|
||||
Controller controller = null;
|
||||
AudioEncoder audioEncoder = null;
|
||||
|
||||
try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) {
|
||||
VideoCodec codec = options.getCodec();
|
||||
if (options.getSendDeviceMeta()) {
|
||||
@@ -102,6 +100,7 @@ public final class Server {
|
||||
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||
}
|
||||
|
||||
Controller controller = null;
|
||||
if (control) {
|
||||
controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
|
||||
controller.start();
|
||||
@@ -110,10 +109,11 @@ public final class Server {
|
||||
device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text));
|
||||
}
|
||||
|
||||
AudioEncoder audioEncoder = null;
|
||||
if (audio) {
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(),
|
||||
options.getSendFrameMeta());
|
||||
audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioEncoderName());
|
||||
audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate());
|
||||
audioEncoder.start();
|
||||
}
|
||||
|
||||
@@ -128,27 +128,27 @@ public final class Server {
|
||||
if (!IO.isBrokenPipe(e)) {
|
||||
Ln.e("Video encoding error", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Ln.d("Screen streaming stopped");
|
||||
initThread.interrupt();
|
||||
if (audioEncoder != null) {
|
||||
audioEncoder.stop();
|
||||
}
|
||||
if (controller != null) {
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
try {
|
||||
initThread.join();
|
||||
} finally {
|
||||
Ln.d("Screen streaming stopped");
|
||||
initThread.interrupt();
|
||||
if (audioEncoder != null) {
|
||||
audioEncoder.join();
|
||||
audioEncoder.stop();
|
||||
}
|
||||
if (controller != null) {
|
||||
controller.join();
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
try {
|
||||
initThread.join();
|
||||
if (audioEncoder != null) {
|
||||
audioEncoder.join();
|
||||
}
|
||||
if (controller != null) {
|
||||
controller.join();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,10 +264,6 @@ public final class Server {
|
||||
options.setEncoderName(value);
|
||||
}
|
||||
break;
|
||||
case "audio_encoder_name":
|
||||
if (!value.isEmpty()) {
|
||||
options.setAudioEncoderName(value);
|
||||
}
|
||||
case "power_off_on_close":
|
||||
boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
|
||||
@@ -338,19 +334,38 @@ public final class Server {
|
||||
return new Rect(x, y, x + width, y + height);
|
||||
}
|
||||
|
||||
private static void suggestFix(Throwable e) {
|
||||
if (e instanceof InvalidDisplayIdException) {
|
||||
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
||||
int[] displayIds = idie.getAvailableDisplayIds();
|
||||
if (displayIds != null && displayIds.length > 0) {
|
||||
Ln.e("Try to use one of the available display ids:");
|
||||
for (int id : displayIds) {
|
||||
Ln.e(" scrcpy --display " + id);
|
||||
}
|
||||
}
|
||||
} else if (e instanceof InvalidEncoderException) {
|
||||
InvalidEncoderException iee = (InvalidEncoderException) e;
|
||||
MediaCodecInfo[] encoders = iee.getAvailableEncoders();
|
||||
if (encoders != null && encoders.length > 0) {
|
||||
Ln.e("Try to use one of the available encoders:");
|
||||
for (MediaCodecInfo encoder : encoders) {
|
||||
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
Ln.e("Exception on thread " + t, e);
|
||||
suggestFix(e);
|
||||
});
|
||||
|
||||
Options options = createOptions(args);
|
||||
|
||||
Ln.initLogLevel(options.getLogLevel());
|
||||
|
||||
try {
|
||||
scrcpy(options);
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
}
|
||||
scrcpy(options);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user