Adjust the video session packet to be the same format as other data packets (12 headers + screen width, screen height, device orientation)

This commit is contained in:
gz0119 2025-03-10 20:51:09 +08:00
parent d892a9aac5
commit e91a708922
5 changed files with 111 additions and 7 deletions

View file

@ -13,6 +13,7 @@
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
#define PACKET_FLAG_VIDEO_SESSION (UINT64_C(1) << 61)
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
@ -76,6 +77,18 @@ sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
*height = sc_read32be(data + 4);
return true;
}
static void
handle_video_session_packet(AVPacket *packet) {
if (!packet || !packet->data || packet->size < 12) {
LOGE("Invalid packet data");
return;
}
const uint8_t *data = packet->data;
int width = sc_read32be(data);
int height = sc_read32be(data + 4);
int orientation = sc_read32be(data + 8);
LOGD("Orientation=%d, Width=%d, Height=%d, Size=%d", orientation, width, height, packet->size);
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
@ -120,6 +133,11 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
return false;
}
if (pts_flags & PACKET_FLAG_VIDEO_SESSION) {
handle_video_session_packet(packet);
return true;
}
if (pts_flags & SC_PACKET_FLAG_CONFIG) {
packet->pts = AV_NOPTS_VALUE;
} else {

View file

@ -24,6 +24,7 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.graphics.Point;
import java.util.ArrayList;
import java.util.List;
@ -210,7 +211,7 @@ public final class Device {
}
}
private static int getCurrentRotation(int displayId) {
public static int getCurrentRotation(int displayId) {
assert displayId != DISPLAY_ID_NONE;
if (displayId == 0) {
@ -220,6 +221,14 @@ public final class Device {
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
return displayInfo.getRotation();
}
public static Size getSize(int displayId){
assert displayId != DISPLAY_ID_NONE;
if (displayId == 0) {
return ServiceManager.getWindowManager().getSize(displayId);
}
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
return displayInfo.getSize();
}
public static List<DeviceApp> listApps() {
List<DeviceApp> apps = new ArrayList<>();

View file

@ -13,16 +13,17 @@ import java.nio.ByteOrder;
import java.util.Arrays;
public final class Streamer {
private static final long PACKET_FLAG_CONFIG = 1L << 63;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
public static final long PACKET_FLAG_VIDEO_SESSION = 1L << 61;
private final FileDescriptor fd;
private final Codec codec;
private final boolean sendCodecMeta;
private final boolean sendFrameMeta;
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private final int HEADER_PACKET_SIZE = 12;
private final ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_PACKET_SIZE);
public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) {
this.fd = fd;
@ -46,7 +47,7 @@ public final class Streamer {
public void writeVideoHeader(Size videoSize) throws IOException {
if (sendCodecMeta) {
ByteBuffer buffer = ByteBuffer.allocate(12);
ByteBuffer buffer = ByteBuffer.allocate(HEADER_PACKET_SIZE);
buffer.putInt(codec.getId());
buffer.putInt(videoSize.getWidth());
buffer.putInt(videoSize.getHeight());

View file

@ -3,9 +3,11 @@ package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.Options;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.device.Streamer;
import com.genymobile.scrcpy.device.Orientation;
import com.genymobile.scrcpy.util.Codec;
import com.genymobile.scrcpy.util.CodecOption;
import com.genymobile.scrcpy.util.CodecUtils;
@ -25,6 +27,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
public class SurfaceEncoder implements AsyncProcessor {
@ -44,6 +47,16 @@ public class SurfaceEncoder implements AsyncProcessor {
private final float maxFps;
private final boolean downsizeOnError;
private int orientation = -100;
private final int displayId;
private boolean sendCodeMeta;
private boolean sendFrameMeta;
private int maxSize;
private Size originalSize;
private Size displaySize;
private Orientation captureOrientation;
private Orientation.Lock captureOrientationLock;
private boolean firstFrameSent;
private int consecutiveErrors;
@ -60,13 +73,22 @@ public class SurfaceEncoder implements AsyncProcessor {
this.codecOptions = options.getVideoCodecOptions();
this.encoderName = options.getVideoEncoder();
this.downsizeOnError = options.getDownsizeOnError();
this.displayId = options.getDisplayId();
this.sendCodeMeta = options.getSendCodecMeta();
this.sendFrameMeta = options.getSendFrameMeta();
this.originalSize = Device.getSize(this.displayId);
int maxSize = options.getMaxSize();
int max = maxSize == 0 ? chooseMaxSizeFallback(this.originalSize) : maxSize;
this.displaySize = this.originalSize.limit(max).round8();
this.captureOrientation = options.getCaptureOrientation();
this.captureOrientationLock = options.getCaptureOrientationLock();
}
private void streamCapture() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
capture.init(reset);
try {
@ -182,7 +204,7 @@ public class SurfaceEncoder implements AsyncProcessor {
return true;
}
private static int chooseMaxSizeFallback(Size failedSize) {
public static int chooseMaxSizeFallback(Size failedSize) {
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
for (int value : MAX_SIZE_FALLBACK) {
if (value < currentMaxSize) {
@ -194,6 +216,28 @@ public class SurfaceEncoder implements AsyncProcessor {
return 0;
}
private Size getSize(int orientation){
switch (orientation) {
case 0:
case 4:
return this.displaySize;
case 1:
case 3:
return new Size(this.displaySize.getHeight(),this.displaySize.getWidth());
}
return new Size(0,0);
}
private ByteBuffer getCurrentVideoSession(int orientation){
ByteBuffer buffer = ByteBuffer.allocate(20);
Size size = getSize(orientation);
int width = size.getWidth();
int height = size.getHeight();
buffer.putInt(width);
buffer.putInt(height);
buffer.putInt(orientation);
buffer.flip();
return buffer;
}
private void encode(MediaCodec codec, Streamer streamer) throws IOException {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@ -201,6 +245,16 @@ public class SurfaceEncoder implements AsyncProcessor {
do {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
try {
// If the frame width and height do not change, it will not be re-encoded
// Therefore, it is necessary to monitor each frame.
// For example: horizontal (1->3), vertical (0->4)
int orientation = Device.getCurrentRotation(displayId);
if(this.orientation != orientation){
this.orientation = orientation;
ByteBuffer videoSession = getCurrentVideoSession(orientation);
streamer.writePacket(videoSession,Streamer.PACKET_FLAG_VIDEO_SESSION,false,false);
}
eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
// On EOS, there might be data or not, depending on bufferInfo.size
if (outputBufferId >= 0 && bufferInfo.size > 0) {
@ -212,7 +266,6 @@ public class SurfaceEncoder implements AsyncProcessor {
firstFrameSent = true;
consecutiveErrors = 0;
}
streamer.writePacket(codecBuffer, bufferInfo);
}
} finally {

View file

@ -2,12 +2,15 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.device.Size;
import android.annotation.TargetApi;
import android.graphics.Point;
import android.os.Build;
import android.os.IInterface;
import android.view.IDisplayWindowListener;
// import java.awt.Point;
import java.lang.reflect.Method;
public final class WindowManager {
@ -42,6 +45,26 @@ public final class WindowManager {
this.manager = manager;
}
private Method getGetSizeMethod;
private Method getGetSizeMethod() throws NoSuchMethodException {
if (getGetSizeMethod == null) {
Class<?> cls = manager.getClass();
getGetSizeMethod = cls.getMethod("getInitialDisplaySize",int.class, Point.class);
}
return getGetSizeMethod;
}
public Size getSize(int displayId) {
try {
Method method = getGetSizeMethod();
Point size = new Point();
method.invoke(manager, displayId, size);
return new Size(size.x, size.y);
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return new Size(0,0);
}
}
private Method getGetRotationMethod() throws NoSuchMethodException {
if (getRotationMethod == null) {
Class<?> cls = manager.getClass();