mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-02 22:29:25 +00:00
Merge 447fe26bb5
into 1a0d300786
This commit is contained in:
commit
6018699ee3
9 changed files with 106 additions and 36 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -63,17 +63,26 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
handle_video_session_packet(uint8_t *data){
|
||||||
|
int width = sc_read32be(data + 1);
|
||||||
|
int height = sc_read32be(data + 5);
|
||||||
|
bool isFlip = data[9] == 1;
|
||||||
|
int direction = (data[10] == (uint8_t)1 ? 2 : 0) + (data[11] == (uint8_t)1 ? 1 : 0);
|
||||||
|
LOGI("Width=%d, Height=%d, Flip=%s, Direction=%d", width, height, isFlip ? "True" : "False", direction);
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
||||||
uint32_t *height) {
|
uint32_t *height) {
|
||||||
uint8_t data[8];
|
uint8_t data[12];
|
||||||
ssize_t r = net_recv_all(demuxer->socket, data, 8);
|
ssize_t r = net_recv_all(demuxer->socket, data, 12);
|
||||||
if (r < 8) {
|
if (r < 12) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
*width = sc_read32be(data + 1);
|
||||||
*width = sc_read32be(data);
|
*height = sc_read32be(data + 5);
|
||||||
*height = sc_read32be(data + 4);
|
handle_video_session_packet(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +113,11 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
if (r < SC_PACKET_HEADER_SIZE) {
|
if (r < SC_PACKET_HEADER_SIZE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(header[0] == 0xff){
|
||||||
|
handle_video_session_packet(header);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint64_t pts_flags = sc_read64be(header);
|
uint64_t pts_flags = sc_read64be(header);
|
||||||
uint32_t len = sc_read32be(&header[8]);
|
uint32_t len = sc_read32be(&header[8]);
|
||||||
|
@ -218,11 +232,11 @@ run_demuxer(void *data) {
|
||||||
LOGE("Demuxer '%s': could not open codec", demuxer->name);
|
LOGE("Demuxer '%s': could not open codec", demuxer->name);
|
||||||
goto finally_free_context;
|
goto finally_free_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
|
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
|
||||||
goto finally_free_context;
|
goto finally_free_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config packets must be merged with the next non-config packet only for
|
// Config packets must be merged with the next non-config packet only for
|
||||||
// H.26x
|
// H.26x
|
||||||
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|
||||||
|
|
|
@ -258,8 +258,7 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||||
|
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct sc_screen *screen = DOWNCAST(sink);
|
||||||
|
|
||||||
if (ctx->width <= 0 || ctx->width > 0xFFFF
|
if (ctx->width <= 0 || ctx->width > 0xFFFF || ctx->height <= 0 || ctx->height > 0xFFFF) {
|
||||||
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
|
|
||||||
LOGE("Invalid video size: %dx%d", ctx->width, ctx->height);
|
LOGE("Invalid video size: %dx%d", ctx->width, ctx->height);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ public final class Server {
|
||||||
surfaceCapture = new NewDisplayCapture(controller, options);
|
surfaceCapture = new NewDisplayCapture(controller, options);
|
||||||
} else {
|
} else {
|
||||||
assert options.getDisplayId() != Device.DISPLAY_ID_NONE;
|
assert options.getDisplayId() != Device.DISPLAY_ID_NONE;
|
||||||
surfaceCapture = new ScreenCapture(controller, options);
|
surfaceCapture = new ScreenCapture(controller, videoStreamer, options);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
surfaceCapture = new CameraCapture(options);
|
surfaceCapture = new CameraCapture(options);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.media.MediaCodec;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -44,12 +45,26 @@ public final class Streamer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeVideoHeader(Size videoSize) throws IOException {
|
public void writeVideoHeader() throws IOException {
|
||||||
if (sendCodecMeta) {
|
if (sendCodecMeta) {
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(12);
|
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||||
buffer.putInt(codec.getId());
|
buffer.putInt(codec.getId());
|
||||||
|
buffer.flip();
|
||||||
|
IO.writeFully(fd, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeVideoSession(Size videoSize, boolean isFlip, int rotation) throws IOException{
|
||||||
|
if(sendCodecMeta){
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(12);
|
||||||
|
buffer.put((byte) 0xff);
|
||||||
buffer.putInt(videoSize.getWidth());
|
buffer.putInt(videoSize.getWidth());
|
||||||
buffer.putInt(videoSize.getHeight());
|
buffer.putInt(videoSize.getHeight());
|
||||||
|
buffer.put((byte) (isFlip ? 1 : 0));
|
||||||
|
int[] vertical = {0, 0, 1, 1};
|
||||||
|
int[] horizontal = {0, 1, 0, 1};
|
||||||
|
buffer.put((byte) vertical[rotation]);
|
||||||
|
buffer.put((byte) horizontal[rotation]);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
IO.writeFully(fd, buffer);
|
IO.writeFully(fd, buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,35 @@ import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.view.IDisplayWindowListener;
|
import android.view.IDisplayWindowListener;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DisplaySizeMonitor {
|
public class DisplaySizeMonitor {
|
||||||
|
|
||||||
|
private static class SessionInfo {
|
||||||
|
private Size size;
|
||||||
|
private int rotation;
|
||||||
|
|
||||||
|
public SessionInfo(Size size, int rotation) {
|
||||||
|
this.size = size;
|
||||||
|
this.rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (!(o instanceof SessionInfo))
|
||||||
|
return false;
|
||||||
|
SessionInfo that = (SessionInfo) o;
|
||||||
|
return rotation == that.rotation && Objects.equals(size, that.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{" + "size=" + size + ", rotation=" + rotation + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
void onDisplaySizeChanged();
|
void onDisplaySizeChanged();
|
||||||
}
|
}
|
||||||
|
@ -34,7 +61,7 @@ public class DisplaySizeMonitor {
|
||||||
|
|
||||||
private int displayId = Device.DISPLAY_ID_NONE;
|
private int displayId = Device.DISPLAY_ID_NONE;
|
||||||
|
|
||||||
private Size sessionDisplaySize;
|
private SessionInfo sessionInfo;
|
||||||
|
|
||||||
private Listener listener;
|
private Listener listener;
|
||||||
|
|
||||||
|
@ -98,12 +125,16 @@ public class DisplaySizeMonitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized Size getSessionDisplaySize() {
|
private synchronized SessionInfo getSessionInfo() {
|
||||||
return sessionDisplaySize;
|
return sessionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setSessionDisplaySize(Size sessionDisplaySize) {
|
private synchronized void setSessionInfo(SessionInfo sessionInfo) {
|
||||||
this.sessionDisplaySize = sessionDisplaySize;
|
this.sessionInfo = sessionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionInfo(Size size, int rotation) {
|
||||||
|
setSessionInfo(new SessionInfo(size, rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDisplaySizeChanged() {
|
private void checkDisplaySizeChanged() {
|
||||||
|
@ -112,29 +143,29 @@ public class DisplaySizeMonitor {
|
||||||
Ln.w("DisplayInfo for " + displayId + " cannot be retrieved");
|
Ln.w("DisplayInfo for " + displayId + " cannot be retrieved");
|
||||||
// We can't compare with the current size, so reset unconditionally
|
// We can't compare with the current size, so reset unconditionally
|
||||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||||
Ln.v("DisplaySizeMonitor: requestReset(): " + getSessionDisplaySize() + " -> (unknown)");
|
Ln.v("DisplaySizeMonitor: requestReset(): " + getSessionInfo() + " -> (unknown)");
|
||||||
}
|
}
|
||||||
setSessionDisplaySize(null);
|
setSessionInfo(null);
|
||||||
listener.onDisplaySizeChanged();
|
listener.onDisplaySizeChanged();
|
||||||
} else {
|
} else {
|
||||||
Size size = di.getSize();
|
SessionInfo si = new SessionInfo(di.getSize(), di.getRotation());
|
||||||
|
|
||||||
// The field is hidden on purpose, to read it with synchronization
|
// The field is hidden on purpose, to read it with synchronization
|
||||||
@SuppressWarnings("checkstyle:HiddenField")
|
@SuppressWarnings("checkstyle:HiddenField")
|
||||||
Size sessionDisplaySize = getSessionDisplaySize(); // synchronized
|
SessionInfo sessionInfo = getSessionInfo(); // synchronized
|
||||||
|
|
||||||
// .equals() also works if sessionDisplaySize == null
|
// .equals() also works if sessionDisplaySize == null
|
||||||
if (!size.equals(sessionDisplaySize)) {
|
if (!si.equals(sessionInfo)) {
|
||||||
// Reset only if the size is different
|
// Reset only if the size is different
|
||||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||||
Ln.v("DisplaySizeMonitor: requestReset(): " + sessionDisplaySize + " -> " + size);
|
Ln.v("DisplaySizeMonitor: requestReset(): " + sessionInfo + " -> " + si);
|
||||||
}
|
}
|
||||||
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
||||||
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
||||||
setSessionDisplaySize(size);
|
setSessionInfo(si);
|
||||||
listener.onDisplaySizeChanged();
|
listener.onDisplaySizeChanged();
|
||||||
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||||
Ln.v("DisplaySizeMonitor: Size not changed (" + size + "): do not requestReset()");
|
Ln.v("DisplaySizeMonitor: Size and rotation not changed (" + sessionInfo + "): do not requestReset()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||||
videoSize = displaySize;
|
videoSize = displaySize;
|
||||||
displayRotation = 0;
|
displayRotation = 0;
|
||||||
// Set the current display size to avoid an unnecessary call to invalidate()
|
// Set the current display size to avoid an unnecessary call to invalidate()
|
||||||
displaySizeMonitor.setSessionDisplaySize(displaySize);
|
displaySizeMonitor.setSessionInfo(displaySize, displayRotation);
|
||||||
} else {
|
} else {
|
||||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(virtualDisplay.getDisplay().getDisplayId());
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(virtualDisplay.getDisplay().getDisplayId());
|
||||||
displaySize = displayInfo.getSize();
|
displaySize = displayInfo.getSize();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Device;
|
import com.genymobile.scrcpy.device.Device;
|
||||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||||
import com.genymobile.scrcpy.device.Orientation;
|
import com.genymobile.scrcpy.device.Orientation;
|
||||||
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.opengl.AffineOpenGLFilter;
|
import com.genymobile.scrcpy.opengl.AffineOpenGLFilter;
|
||||||
import com.genymobile.scrcpy.opengl.OpenGLFilter;
|
import com.genymobile.scrcpy.opengl.OpenGLFilter;
|
||||||
|
@ -34,6 +35,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||||
private Orientation.Lock captureOrientationLock;
|
private Orientation.Lock captureOrientationLock;
|
||||||
private Orientation captureOrientation;
|
private Orientation captureOrientation;
|
||||||
private final float angle;
|
private final float angle;
|
||||||
|
private final Streamer streamer;
|
||||||
|
|
||||||
private DisplayInfo displayInfo;
|
private DisplayInfo displayInfo;
|
||||||
private Size videoSize;
|
private Size videoSize;
|
||||||
|
@ -46,7 +48,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||||
private AffineMatrix transform;
|
private AffineMatrix transform;
|
||||||
private OpenGLRunner glRunner;
|
private OpenGLRunner glRunner;
|
||||||
|
|
||||||
public ScreenCapture(VirtualDisplayListener vdListener, Options options) {
|
public ScreenCapture(VirtualDisplayListener vdListener, Streamer streamer, Options options) {
|
||||||
this.vdListener = vdListener;
|
this.vdListener = vdListener;
|
||||||
this.displayId = options.getDisplayId();
|
this.displayId = options.getDisplayId();
|
||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
assert displayId != Device.DISPLAY_ID_NONE;
|
||||||
|
@ -57,6 +59,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||||
assert captureOrientationLock != null;
|
assert captureOrientationLock != null;
|
||||||
assert captureOrientation != null;
|
assert captureOrientation != null;
|
||||||
this.angle = options.getAngle();
|
this.angle = options.getAngle();
|
||||||
|
this.streamer = streamer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,27 +80,35 @@ public class ScreenCapture extends SurfaceCapture {
|
||||||
}
|
}
|
||||||
|
|
||||||
Size displaySize = displayInfo.getSize();
|
Size displaySize = displayInfo.getSize();
|
||||||
displaySizeMonitor.setSessionDisplaySize(displaySize);
|
int displayRotation = displayInfo.getRotation();
|
||||||
|
displaySizeMonitor.setSessionInfo(displaySize, displayRotation);
|
||||||
|
|
||||||
if (captureOrientationLock == Orientation.Lock.LockedInitial) {
|
if (captureOrientationLock == Orientation.Lock.LockedInitial) {
|
||||||
// The user requested to lock the video orientation to the current orientation
|
// The user requested to lock the video orientation to the current orientation
|
||||||
captureOrientationLock = Orientation.Lock.LockedValue;
|
captureOrientationLock = Orientation.Lock.LockedValue;
|
||||||
captureOrientation = Orientation.fromRotation(displayInfo.getRotation());
|
captureOrientation = Orientation.fromRotation(displayRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoFilter filter = new VideoFilter(displaySize);
|
VideoFilter filter = new VideoFilter(displaySize);
|
||||||
|
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
boolean transposed = (displayInfo.getRotation() % 2) != 0;
|
boolean transposed = (displayRotation % 2) != 0;
|
||||||
filter.addCrop(crop, transposed);
|
filter.addCrop(crop, transposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
|
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
|
||||||
filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
|
filter.addOrientation(displayRotation, locked, captureOrientation);
|
||||||
filter.addAngle(angle);
|
filter.addAngle(angle);
|
||||||
|
|
||||||
transform = filter.getInverseTransform();
|
transform = filter.getInverseTransform();
|
||||||
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean isFlipped = captureOrientation.isFlipped();
|
||||||
|
streamer.writeVideoSession(videoSize, isFlipped, displayRotation);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Ln.e("Video Session failed to send", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -74,13 +74,13 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||||
boolean headerWritten = false;
|
boolean headerWritten = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
if (!headerWritten) {
|
||||||
|
streamer.writeVideoHeader();
|
||||||
|
headerWritten = true;
|
||||||
|
}
|
||||||
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||||
capture.prepare();
|
capture.prepare();
|
||||||
Size size = capture.getSize();
|
Size size = capture.getSize();
|
||||||
if (!headerWritten) {
|
|
||||||
streamer.writeVideoHeader(size);
|
|
||||||
headerWritten = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth());
|
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth());
|
||||||
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
|
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue