This commit is contained in:
Eugen Pechanec 2022-11-17 21:55:17 +03:00 committed by GitHub
commit 8be9ce0aae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 789 additions and 41 deletions

View file

@ -38,7 +38,7 @@ Its features include:
## Requirements
The Android device requires at least API 21 (Android 5.0).
The Android device requires at least API 19 (Android 4.4).
Make sure you [enable adb debugging][enable-adb] on your device(s).

View file

@ -18,6 +18,7 @@
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_DEVICE_ANDROID_DATA_PATH "/data/local/tmp"
static char *
get_server_path(void) {
@ -166,6 +167,9 @@ execute_server(struct sc_server *server,
cmd[count++] = "-s";
cmd[count++] = serial;
cmd[count++] = "shell";
cmd[count++] = "mkdir -p " SC_DEVICE_ANDROID_DATA_PATH "/dalvik-cache";
cmd[count++] = "&&";
cmd[count++] = "ANDROID_DATA=" SC_DEVICE_ANDROID_DATA_PATH;
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = "app_process";

View file

@ -152,7 +152,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
<property name="severity" value="info" />
</module>
<module name="UpperEll" />
</module>
</module>

View file

@ -16,3 +16,4 @@ org.gradle.jvmargs=-Xmx1536m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true

1
libcore/build.gradle Normal file
View file

@ -0,0 +1 @@
apply plugin: 'java-library'

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.io;
/**
* A checked exception thrown when {@link Os} methods fail. This exception contains the native
* errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
* callers need to adjust their behavior based on the exact failure.
*/
public final class ErrnoException extends Exception {
private final String functionName;
public final int errno;
public ErrnoException(String functionName, int errno) {
throw new AssertionError();
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.io;
public final class Libcore {
private Libcore() {
}
public static Os os = null;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.io;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
public interface Os {
String strerror(int errno);
int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.io;
public final class OsConstants {
private OsConstants() {
}
public static final int EINTR = placeholder();
public static String errnoName(int errno) {
return null;
}
// A hack to avoid these constants being inlined by javac...
private static int placeholder() { return 0; }
}

24
os-compat/build.gradle Normal file
View file

@ -0,0 +1,24 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 19
targetSdkVersion 31
}
androidResources {
namespace = "androidx.system"
}
buildFeatures {
aidl = false
androidResources = false
buildConfig = false
}
}
dependencies {
compileOnly rootProject.fileTree("thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar")
compileOnly project(':libcore')
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"

View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import java.io.IOException;
import java.net.SocketException;
/**
* A checked exception thrown when {@link OsCompat} methods fail. This exception contains the native
* errno value, for comparison against the constants in {@link OsConstantsCompat}, should sophisticated
* callers need to adjust their behavior based on the exact failure.
*/
public final class ErrnoException extends Exception {
private final String functionName;
private final int errno;
/**
* Constructs an instance with the given function name and errno value.
*/
public ErrnoException(String functionName, int errno) {
this.functionName = functionName;
this.errno = errno;
}
/**
* Constructs an instance with the given function name, errno value, and cause.
*/
public ErrnoException(String functionName, int errno, Throwable cause) {
super(cause);
this.functionName = functionName;
this.errno = errno;
}
/**
* The errno value, for comparison with the {@code E} constants in {@link OsConstantsCompat}.
*/
public int getErrno() {
return errno;
}
/**
* Converts the stashed function name and errno value to a human-readable string.
* We do this here rather than in the constructor so that callers only pay for
* this if they need it.
*/
@Override public String getMessage() {
String errnoName = OsConstantsCompat.errnoName(errno);
if (errnoName == null) {
errnoName = "errno " + errno;
}
String description = OsCompat.strerror(errno);
return functionName + " failed: " + errnoName + " (" + description + ")";
}
/**
* Throws an {@link IOException} with a message based on {@link #getMessage()} and with this
* instance as the cause.
*
* <p>This method always terminates by throwing the exception. Callers can write
* {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
*/
public IOException rethrowAsIOException() throws IOException {
throw new IOException(getMessage(), this);
}
/**
* Throws a {@link SocketException} with a message based on {@link #getMessage()} and with this
* instance as the cause.
*
* <p>This method always terminates by throwing the exception. Callers can write
* {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
*/
public SocketException rethrowAsSocketException() throws SocketException {
final SocketException newException = new SocketException(getMessage());
newException.initCause(this);
throw newException;
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
interface Os {
String strerror(int errno);
int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
// https://android.googlesource.com/platform/libcore/+/lollipop-mr1-release/luni/src/main/java/libcore/io/Posix.java#253
static void maybeUpdateBufferPosition(ByteBuffer buffer, int originalPosition, int bytesReadOrWritten) {
if (bytesReadOrWritten > 0) {
buffer.position(bytesReadOrWritten + originalPosition);
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import androidx.annotation.RequiresApi;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.system.Os.maybeUpdateBufferPosition;
@RequiresApi(21)
final class OsApi21 implements Os {
@Override
public String strerror(int errno) {
return android.system.Os.strerror(errno);
}
@Override
public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
try {
final int position = buffer.position();
final int bytesWritten = android.system.Os.write(fd, buffer);
if (SDK_INT < 22) {
maybeUpdateBufferPosition(buffer, position, bytesWritten);
}
return bytesWritten;
} catch (android.system.ErrnoException e) {
throw new ErrnoException("write", e.errno);
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import static android.os.Build.VERSION.SDK_INT;
/**
* Access to low-level system functionality. Most of these are system calls. Most users will want
* to use higher-level APIs where available, but this class provides access to the underlying
* primitives used to implement the higher-level APIs.
*
* <p>The corresponding constants can be found in {@link OsConstantsCompat}.
*/
public final class OsCompat {
private OsCompat() {
}
private static final Os IMPL;
static {
if (SDK_INT >= 21) {
IMPL = new OsApi21();
} else {
IMPL = new OsLibcore();
}
}
/**
* See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>.
*/
public static String strerror(int errno) {
return IMPL.strerror(errno);
}
/**
* See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
*/
public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
return IMPL.write(fd, buffer);
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
interface OsConstants {
String errnoName(int errno);
}

View file

@ -0,0 +1,11 @@
package androidx.system;
import androidx.annotation.RequiresApi;
@RequiresApi(21)
final class OsConstantsApi21 implements OsConstants {
@Override
public String errnoName(int errno) {
return android.system.OsConstants.errnoName(errno);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import android.system.Os;
import static android.os.Build.VERSION.SDK_INT;
public final class OsConstantsCompat {
private OsConstantsCompat() {
}
private static final OsConstants IMPL;
static {
if (SDK_INT >= 21) {
IMPL = new OsConstantsApi21();
} else {
IMPL = new OsConstantsLibcore();
}
}
/**
* Returns the string name of an errno value.
* For example, "EACCES". See {@link Os#strerror} for human-readable errno descriptions.
*/
public static String errnoName(int errno) {
return IMPL.errnoName(errno);
}
}

View file

@ -0,0 +1,8 @@
package androidx.system;
final class OsConstantsLibcore implements OsConstants {
@Override
public String errnoName(int errno) {
return libcore.io.OsConstants.errnoName(errno);
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.system;
import libcore.io.Libcore;
import libcore.io.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import static androidx.system.Os.maybeUpdateBufferPosition;
final class OsLibcore implements Os {
@Override
public String strerror(int errno) {
return Libcore.os.strerror(errno);
}
@Override
public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
try {
return ioFailureRetry("write", fd, () -> {
final int position = buffer.position();
final int bytesWritten = Libcore.os.write(fd, buffer);
maybeUpdateBufferPosition(buffer, position, bytesWritten);
return bytesWritten;
});
} catch (libcore.io.ErrnoException e) {
throw new ErrnoException("write", e.errno);
}
}
// https://android.googlesource.com/platform/libcore/+/lollipop-release/luni/src/main/native/libcore_io_Posix.cpp#128
// https://android.googlesource.com/platform/libcore/+/kitkat-release/luni/src/main/java/libcore/io/IoBridge.java#186
private static int ioFailureRetry(String functionName, FileDescriptor fd, SysCall syscall)
throws libcore.io.ErrnoException, InterruptedIOException {
if (!fd.valid()) {
throw UndeclaredExceptions.raise(new IOException("File descriptor closed"));
}
int rc = -1;
do {
int syscallErrno = 0;
try {
rc = syscall.call();
} catch (libcore.io.ErrnoException e) {
syscallErrno = e.errno;
}
if (rc == -1 && !fd.valid()) {
throw new InterruptedIOException(functionName + " interrupted");
}
if (rc == -1 && syscallErrno != OsConstants.EINTR) {
throw new libcore.io.ErrnoException(functionName, syscallErrno);
}
} while (rc == -1);
return rc;
}
@FunctionalInterface
private interface SysCall {
Integer call() throws libcore.io.ErrnoException;
}
// https://dzone.com/articles/throwing-undeclared-checked
private static final class UndeclaredExceptions extends RuntimeException {
private static Throwable sThrowable = null;
public static synchronized RuntimeException raise(Throwable throwable) {
if (throwable instanceof ReflectiveOperationException || throwable instanceof RuntimeException) {
throw new IllegalArgumentException("Unsupported exception: " + throwable.getClass());
}
sThrowable = throwable;
try {
return UndeclaredExceptions.class.newInstance();
} catch (ReflectiveOperationException e) {
return new RuntimeException(e);
} finally {
sThrowable = null;
}
}
private UndeclaredExceptions() throws Throwable {
throw sThrowable;
}
}
}

View file

@ -4,7 +4,7 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
minSdkVersion 19
targetSdkVersion 31
versionCode 12400
versionName "1.24"
@ -21,6 +21,8 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1'
compileOnly rootProject.fileTree("thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar")
implementation project(':os-compat')
}
apply from: "$project.rootDir/config/android-checkstyle.gradle"

View file

@ -20,12 +20,14 @@ BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes"
SERVER_DIR=$(dirname "$0")
ROOT_PROJECT_DIR=$(realpath $SERVER_DIR/..)
SERVER_BINARY=scrcpy-server
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
echo "Platform: android-$PLATFORM"
echo "Build-tools: $BUILD_TOOLS"
echo "Build dir: $BUILD_DIR"
echo "Root project dir: $ROOT_PROJECT_DIR"
rm -rf "$CLASSES_DIR" "$BUILD_DIR/$SERVER_BINARY" classes.dex
mkdir -p "$CLASSES_DIR/com/genymobile/scrcpy"
@ -48,8 +50,15 @@ cd "$SERVER_DIR/src/main/aidl"
echo "Compiling java sources..."
cd ../java
javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
classpath="$CLASSES_DIR"
classpath="$classpath:$ROOT_PROJECT_DIR/thirdparty/androidx/annotation/1.3.0/annotation-1.3.0.jar"
# https://stackoverflow.com/a/58768648/2444099
classpath="$classpath:$ANDROID_HOME/build-tools/$BUILD_TOOLS/core-lambda-stubs.jar"
javac -bootclasspath "$ANDROID_JAR" -cp "$classpath" -d "$CLASSES_DIR" \
-encoding UTF-8 \
-source 1.8 -target 1.8 \
$ROOT_PROJECT_DIR/os-compat/src/main/java/androidx/system/*.java \
$ROOT_PROJECT_DIR/libcore/src/main/java/libcore/io/*.java \
com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java
@ -61,6 +70,7 @@ then
# use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
@ -74,6 +84,7 @@ else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
androidx/system/*.class \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \

View file

@ -1,8 +1,7 @@
package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import androidx.system.ErrnoException;
import androidx.system.OsCompat;
import java.io.FileDescriptor;
import java.io.IOException;
@ -14,23 +13,12 @@ public final class IO {
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
// ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
// count the remaining bytes manually.
// See <https://github.com/Genymobile/scrcpy/issues/291>.
int remaining = from.remaining();
while (remaining > 0) {
try {
int w = Os.write(fd, from);
if (BuildConfig.DEBUG && w < 0) {
// w should not be negative, since an exception is thrown on error
throw new AssertionError("Os.write() returned a negative value (" + w + ")");
}
remaining -= w;
} catch (ErrnoException e) {
if (e.errno != OsConstants.EINTR) {
throw new IOException(e);
}
try {
while (from.hasRemaining()) {
OsCompat.write(fd, from);
}
} catch (ErrnoException e) {
throw new IOException(e);
}
}

View file

@ -0,0 +1,116 @@
package com.genymobile.scrcpy;
import android.media.MediaCodec;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.nio.ByteBuffer;
import static android.media.MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED;
import static android.os.Build.VERSION.SDK_INT;
/**
* Version of {@link MediaCodec} that backports {@link #getOutputBuffer} to Kitkat.
* The backported implementation isn't thread safe.
*/
abstract class MediaCodecCompat {
private final MediaCodec delegate;
MediaCodecCompat(@NonNull MediaCodec delegate) {
this.delegate = delegate;
}
@NonNull
protected MediaCodec getDelegate() {
return delegate;
}
@NonNull
static MediaCodecCompat wrap(@NonNull MediaCodec codec) {
if (SDK_INT >= 21) {
return new Platform(codec);
} else {
return new Backport(codec);
}
}
abstract int dequeueOutputBuffer(
@NonNull MediaCodec.BufferInfo info, long timeoutUs);
@Nullable
abstract ByteBuffer getOutputBuffer(int index);
abstract void releaseOutputBuffer(int index, boolean render);
@SuppressWarnings("deprecation")
private static class Backport extends MediaCodecCompat {
private ByteBuffer[] cachedOutputBuffers = null;
Backport(@NonNull MediaCodec delegate) {
super(delegate);
}
@Override
int dequeueOutputBuffer(
@NonNull MediaCodec.BufferInfo info, long timeoutUs) {
final int res = getDelegate().dequeueOutputBuffer(info, timeoutUs);
if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
cachedOutputBuffers = null;
}
return res;
}
@Nullable
@Override
ByteBuffer getOutputBuffer(int index) {
if (cachedOutputBuffers == null) {
cacheOutputBuffers();
}
if (cachedOutputBuffers == null) {
throw new IllegalStateException();
}
return cachedOutputBuffers[index];
}
@Override
void releaseOutputBuffer(int index, boolean render) {
cachedOutputBuffers = null;
getDelegate().releaseOutputBuffer(index, render);
}
private void cacheOutputBuffers() {
ByteBuffer[] buffers = null;
try {
buffers = getDelegate().getOutputBuffers();
} catch (IllegalStateException e) {
// we don't get buffers in async mode
}
cachedOutputBuffers = buffers;
}
}
@RequiresApi(21)
private static class Platform extends MediaCodecCompat {
Platform(@NonNull MediaCodec delegate) {
super(delegate);
}
@Override
int dequeueOutputBuffer(
@NonNull MediaCodec.BufferInfo info, long timeoutUs) {
return getDelegate().dequeueOutputBuffer(info, timeoutUs);
}
@Nullable
@Override
ByteBuffer getOutputBuffer(int index) {
return getDelegate().getOutputBuffer(index);
}
@Override
void releaseOutputBuffer(int index, boolean render) {
getDelegate().releaseOutputBuffer(index, render);
}
}
}

View file

@ -0,0 +1,87 @@
package com.genymobile.scrcpy;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.media.MediaCodecList.REGULAR_CODECS;
import static android.os.Build.VERSION.SDK_INT;
/**
* Version of {@link MediaCodecList} that backports {@link #getCodecInfos()} to Kitkat.
*/
abstract class MediaCodecListCompat {
MediaCodecListCompat() {
}
/**
* Returns the list of codecs that are suitable
* for regular (buffer-to-buffer) decoding or encoding.
*/
static MediaCodecListCompat regular() {
if (SDK_INT >= 21) {
return new Platform();
} else {
return new Backport();
}
}
/**
* Returns the list of {@link MediaCodecInfo} objects for the list
* of media-codecs.
*/
@NonNull
abstract MediaCodecInfo[] getCodecInfos();
@NonNull
final MediaCodecInfo[] getEncoderInfosForType(@NonNull String mimeType) {
List<MediaCodecInfo> result = new ArrayList<>();
MediaCodecInfo[] codecInfos = MediaCodecListCompat.regular().getCodecInfos();
for (MediaCodecInfo codecInfo : codecInfos) {
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
result.add(codecInfo);
}
}
return result.toArray(new MediaCodecInfo[result.size()]);
}
@SuppressWarnings("deprecation")
private static class Backport extends MediaCodecListCompat {
private final MediaCodecInfo[] mCodecInfos;
Backport() {
final int size = MediaCodecList.getCodecCount();
final MediaCodecInfo[] codecInfos = new MediaCodecInfo[size];
for (int i = 0; i < size; i++) {
codecInfos[i] = MediaCodecList.getCodecInfoAt(i);
}
this.mCodecInfos = codecInfos;
}
@NonNull
@Override
public final MediaCodecInfo[] getCodecInfos() {
return Arrays.copyOf(mCodecInfos, mCodecInfos.length);
}
}
@RequiresApi(21)
private static class Platform extends MediaCodecListCompat {
private final MediaCodecList mDelegate;
Platform() {
this.mDelegate = new MediaCodecList(REGULAR_CODECS);
}
@NonNull
@Override
MediaCodecInfo[] getCodecInfos() {
return mDelegate.getCodecInfos();
}
}
}

View file

@ -2,10 +2,10 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.annotation.SuppressLint;
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;
@ -14,8 +14,6 @@ import android.view.Surface;
import java.io.FileDescriptor;
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;
@ -25,6 +23,9 @@ public class ScreenEncoder implements Device.RotationListener {
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
@SuppressLint("InlinedApi")
public static final String MIMETYPE_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
// Keep the values in descending order
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
@ -144,7 +145,8 @@ public class ScreenEncoder implements Device.RotationListener {
return 0;
}
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
private boolean encode(MediaCodec platformCodec, FileDescriptor fd) throws IOException {
final MediaCodecCompat codec = MediaCodecCompat.wrap(platformCodec);
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@ -201,28 +203,18 @@ public class ScreenEncoder implements Device.RotationListener {
IO.writeFully(fd, headerBuffer);
}
private static MediaCodecInfo[] listEncoders() {
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(MediaFormat.MIMETYPE_VIDEO_AVC)) {
result.add(codecInfo);
}
}
return result.toArray(new MediaCodecInfo[result.size()]);
}
private static MediaCodec createCodec(String encoderName) throws IOException {
if (encoderName != null) {
Ln.d("Creating encoder by name: '" + encoderName + "'");
try {
return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) {
MediaCodecInfo[] encoders = listEncoders();
MediaCodecInfo[] encoders = MediaCodecListCompat.regular()
.getEncoderInfosForType(MIMETYPE_VIDEO_AVC);
throw new InvalidEncoderException(encoderName, encoders);
}
}
MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
MediaCodec codec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
Ln.d("Using encoder: '" + codec.getName() + "'");
return codec;
}
@ -246,7 +238,7 @@ public class ScreenEncoder implements Device.RotationListener {
private static MediaFormat createFormat(int bitRate, int maxFps, List<CodecOption> codecOptions) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
format.setString(MediaFormat.KEY_MIME, MIMETYPE_VIDEO_AVC);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);

View file

@ -1 +1,3 @@
include ':server'
include ':os-compat'
include ':libcore'

Binary file not shown.