diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..26c69b23 Binary files /dev/null and b/.DS_Store differ diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 885cd6ee..59d44034 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -63,17 +63,26 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) { 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 sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width, uint32_t *height) { - uint8_t data[8]; - ssize_t r = net_recv_all(demuxer->socket, data, 8); - if (r < 8) { + uint8_t data[12]; + ssize_t r = net_recv_all(demuxer->socket, data, 12); + if (r < 12) { return false; } - - *width = sc_read32be(data); - *height = sc_read32be(data + 4); + *width = sc_read32be(data + 1); + *height = sc_read32be(data + 5); + handle_video_session_packet(data); return true; } @@ -104,6 +113,11 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { if (r < SC_PACKET_HEADER_SIZE) { return false; } + if(header[0] == 0xff){ + handle_video_session_packet(header); + return true; + } + uint64_t pts_flags = sc_read64be(header); uint32_t len = sc_read32be(&header[8]); @@ -218,11 +232,11 @@ run_demuxer(void *data) { LOGE("Demuxer '%s': could not open codec", demuxer->name); goto finally_free_context; } - + if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) { goto finally_free_context; } - + // Config packets must be merged with the next non-config packet only for // H.26x bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264 diff --git a/app/src/screen.c b/app/src/screen.c index 1d694f12..a0c6483a 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -258,8 +258,7 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink, struct sc_screen *screen = DOWNCAST(sink); - if (ctx->width <= 0 || ctx->width > 0xFFFF - || ctx->height <= 0 || ctx->height > 0xFFFF) { + if (ctx->width <= 0 || ctx->width > 0xFFFF || ctx->height <= 0 || ctx->height > 0xFFFF) { LOGE("Invalid video size: %dx%d", ctx->width, ctx->height); return false; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 09cfd6cf..ab08f47d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -152,7 +152,7 @@ public final class Server { surfaceCapture = new NewDisplayCapture(controller, options); } else { assert options.getDisplayId() != Device.DISPLAY_ID_NONE; - surfaceCapture = new ScreenCapture(controller, options); + surfaceCapture = new ScreenCapture(controller, videoStreamer, options); } } else { surfaceCapture = new CameraCapture(options); diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/device/Streamer.java index f54d0567..db47cea2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Streamer.java @@ -8,6 +8,7 @@ import android.media.MediaCodec; import java.io.FileDescriptor; import java.io.IOException; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; 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) { - ByteBuffer buffer = ByteBuffer.allocate(12); + ByteBuffer buffer = ByteBuffer.allocate(4); 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.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(); IO.writeFully(fd, buffer); } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java b/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java index 3d7cccfe..a8abbd9f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java @@ -15,8 +15,35 @@ import android.os.Handler; import android.os.HandlerThread; import android.view.IDisplayWindowListener; +import java.util.Objects; + 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 { void onDisplaySizeChanged(); } @@ -34,7 +61,7 @@ public class DisplaySizeMonitor { private int displayId = Device.DISPLAY_ID_NONE; - private Size sessionDisplaySize; + private SessionInfo sessionInfo; private Listener listener; @@ -98,12 +125,16 @@ public class DisplaySizeMonitor { } } - private synchronized Size getSessionDisplaySize() { - return sessionDisplaySize; + private synchronized SessionInfo getSessionInfo() { + return sessionInfo; } - public synchronized void setSessionDisplaySize(Size sessionDisplaySize) { - this.sessionDisplaySize = sessionDisplaySize; + private synchronized void setSessionInfo(SessionInfo sessionInfo) { + this.sessionInfo = sessionInfo; + } + + public void setSessionInfo(Size size, int rotation) { + setSessionInfo(new SessionInfo(size, rotation)); } private void checkDisplaySizeChanged() { @@ -112,29 +143,29 @@ public class DisplaySizeMonitor { Ln.w("DisplayInfo for " + displayId + " cannot be retrieved"); // We can't compare with the current size, so reset unconditionally if (Ln.isEnabled(Ln.Level.VERBOSE)) { - Ln.v("DisplaySizeMonitor: requestReset(): " + getSessionDisplaySize() + " -> (unknown)"); + Ln.v("DisplaySizeMonitor: requestReset(): " + getSessionInfo() + " -> (unknown)"); } - setSessionDisplaySize(null); + setSessionInfo(null); listener.onDisplaySizeChanged(); } else { - Size size = di.getSize(); + SessionInfo si = new SessionInfo(di.getSize(), di.getRotation()); // The field is hidden on purpose, to read it with synchronization @SuppressWarnings("checkstyle:HiddenField") - Size sessionDisplaySize = getSessionDisplaySize(); // synchronized + SessionInfo sessionInfo = getSessionInfo(); // synchronized // .equals() also works if sessionDisplaySize == null - if (!size.equals(sessionDisplaySize)) { + if (!si.equals(sessionInfo)) { // Reset only if the size is different 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() // considers that the current size is the requested size (to avoid a duplicate requestReset()) - setSessionDisplaySize(size); + setSessionInfo(si); listener.onDisplaySizeChanged(); } 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()"); } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 792b3a8a..9290a07e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -114,7 +114,7 @@ public class NewDisplayCapture extends SurfaceCapture { videoSize = displaySize; displayRotation = 0; // Set the current display size to avoid an unnecessary call to invalidate() - displaySizeMonitor.setSessionDisplaySize(displaySize); + displaySizeMonitor.setSessionInfo(displaySize, displayRotation); } else { DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(virtualDisplay.getDisplay().getDisplayId()); displaySize = displayInfo.getSize(); diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index 5f4e1803..211ac555 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -7,6 +7,7 @@ import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.Orientation; +import com.genymobile.scrcpy.device.Streamer; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; import com.genymobile.scrcpy.opengl.OpenGLFilter; @@ -34,6 +35,7 @@ public class ScreenCapture extends SurfaceCapture { private Orientation.Lock captureOrientationLock; private Orientation captureOrientation; private final float angle; + private final Streamer streamer; private DisplayInfo displayInfo; private Size videoSize; @@ -46,7 +48,7 @@ public class ScreenCapture extends SurfaceCapture { private AffineMatrix transform; private OpenGLRunner glRunner; - public ScreenCapture(VirtualDisplayListener vdListener, Options options) { + public ScreenCapture(VirtualDisplayListener vdListener, Streamer streamer, Options options) { this.vdListener = vdListener; this.displayId = options.getDisplayId(); assert displayId != Device.DISPLAY_ID_NONE; @@ -57,6 +59,7 @@ public class ScreenCapture extends SurfaceCapture { assert captureOrientationLock != null; assert captureOrientation != null; this.angle = options.getAngle(); + this.streamer = streamer; } @Override @@ -77,27 +80,35 @@ public class ScreenCapture extends SurfaceCapture { } Size displaySize = displayInfo.getSize(); - displaySizeMonitor.setSessionDisplaySize(displaySize); + int displayRotation = displayInfo.getRotation(); + displaySizeMonitor.setSessionInfo(displaySize, displayRotation); if (captureOrientationLock == Orientation.Lock.LockedInitial) { // The user requested to lock the video orientation to the current orientation captureOrientationLock = Orientation.Lock.LockedValue; - captureOrientation = Orientation.fromRotation(displayInfo.getRotation()); + captureOrientation = Orientation.fromRotation(displayRotation); } VideoFilter filter = new VideoFilter(displaySize); if (crop != null) { - boolean transposed = (displayInfo.getRotation() % 2) != 0; + boolean transposed = (displayRotation % 2) != 0; filter.addCrop(crop, transposed); } boolean locked = captureOrientationLock != Orientation.Lock.Unlocked; - filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation); + filter.addOrientation(displayRotation, locked, captureOrientation); filter.addAngle(angle); transform = filter.getInverseTransform(); 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 diff --git a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java index 236a5f48..3c89850e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -74,13 +74,13 @@ public class SurfaceEncoder implements AsyncProcessor { boolean headerWritten = false; do { + if (!headerWritten) { + streamer.writeVideoHeader(); + headerWritten = true; + } reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled capture.prepare(); Size size = capture.getSize(); - if (!headerWritten) { - streamer.writeVideoHeader(size); - headerWritten = true; - } format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth()); format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());