Changing codec-profile -> codec-options

This commit is contained in:
Tzah Mazuz 2020-04-14 18:31:54 +03:00
parent eaef1c1f47
commit c265d6306e
5 changed files with 86 additions and 25 deletions

View file

@ -0,0 +1,25 @@
package com.genymobile.scrcpy;
import java.util.HashMap;
public class CodecOptions {
static final String PROFILE_OPTION = "profile";
static final String LEVEL_OPTION = "level";
private HashMap<String, String> options;
CodecOptions(HashMap<String, String> options) {
this.options = options;
}
Object parseValue(String profileOption) {
String value = options.get(profileOption);
switch (profileOption) {
case PROFILE_OPTION:
case LEVEL_OPTION:
return NumberUtils.tryParseInt(value);
default:
return null;
}
}
}

View file

@ -0,0 +1,16 @@
package com.genymobile.scrcpy;
public class NumberUtils {
public static int tryParseInt(final String str) {
return tryParseInt(str, 0);
}
public static int tryParseInt(final String str, int defaultValue) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
return defaultValue;
}
}
}

View file

@ -11,7 +11,7 @@ public class Options {
private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly
private boolean control;
private int codecProfile;
private CodecOptions codecOptions;
public int getMaxSize() {
return maxSize;
@ -77,11 +77,11 @@ public class Options {
this.control = control;
}
public int getCodecProfile() {
return codecProfile;
public CodecOptions getCodecOptions() {
return codecOptions;
}
public void setCodecProfile(int codecProfile) {
this.codecProfile = codecProfile;
public void setCodecOptions(CodecOptions codecOptions) {
this.codecOptions = codecOptions;
}
}

View file

@ -15,6 +15,8 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
public class ScreenEncoder implements Device.RotationListener {
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
@ -30,21 +32,21 @@ public class ScreenEncoder implements Device.RotationListener {
private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval;
private int codecProfile;
private boolean sendFrameMeta;
private long ptsOrigin;
private CodecOptions codecOptions;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile, int iFrameInterval) {
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, CodecOptions codecOptions, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
this.maxFps = maxFps;
this.lockedVideoOrientation = lockedVideoOrientation;
this.codecProfile = codecProfile;
this.codecOptions = codecOptions;
this.iFrameInterval = iFrameInterval;
}
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecProfile, DEFAULT_I_FRAME_INTERVAL);
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, CodecOptions codecOptions) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecOptions, DEFAULT_I_FRAME_INTERVAL);
}
@Override
@ -142,30 +144,35 @@ public class ScreenEncoder implements Device.RotationListener {
IO.writeFully(fd, headerBuffer);
}
private void setCodecProfile(MediaCodec codec, MediaFormat format) throws IOException {
if(codecProfile == 0) return;
int level = 0;
for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType("video/avc").profileLevels) {
if(profileLevel.profile == codecProfile) {
private void setCodecProfile(MediaCodec codec, MediaFormat format) {
int profile = (int)codecOptions.parseValue(CodecOptions.PROFILE_OPTION);
int level = (int)codecOptions.parseValue(CodecOptions.LEVEL_OPTION);
if(profile == 0) return;
for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) {
if(profileLevel.profile == profile) {
level = Math.max(level, profileLevel.level);
}
}
if(level == 0) throw new IOException("Device doesn't support the requested codec profile.");
// Profile (SDK Level 21) and Level (SDK Level 23).
format.setInteger(MediaFormat.KEY_PROFILE, codecProfile);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.setInteger(MediaFormat.KEY_LEVEL, level);
if(level == 0) {
Ln.w("Device doesn't support the requested codec profile.\n" +
"Profile and level will be chosen automatically.");
} else {
// Profile (SDK Level 21) and Level (SDK Level 23).
format.setInteger(MediaFormat.KEY_PROFILE, profile);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.setInteger(MediaFormat.KEY_LEVEL, level);
}
}
}
private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType("video/avc");
return MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
}
@SuppressWarnings("checkstyle:MagicNumber")
private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "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

@ -6,6 +6,7 @@ import android.os.Build;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
public final class Server {
@ -20,7 +21,7 @@ public final class Server {
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(),
options.getLockedVideoOrientation(), options.getCodecProfile());
options.getLockedVideoOrientation(), options.getCodecOptions());
if (options.getControl()) {
Controller controller = new Controller(device, connection);
@ -98,8 +99,8 @@ public final class Server {
int lockedVideoOrientation = Integer.parseInt(args[4]);
options.setLockedVideoOrientation(lockedVideoOrientation);
int codecProfile = Integer.parseInt(args[5]);
options.setCodecProfile(codecProfile);
CodecOptions codecOptions = parseCodecOptions(args[5]);
options.setCodecOptions(codecOptions);
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[6]);
@ -134,6 +135,18 @@ public final class Server {
return new Rect(x, y, x + width, y + height);
}
private static CodecOptions parseCodecOptions(String codecOptions) {
HashMap<String, String> codecOptionsMap = new HashMap<>();
if (!"-".equals(codecOptions)) {
String[] pairs = codecOptions.split(",");
for (String pair : pairs) {
String[] option = pair.split("=");
codecOptionsMap.put(option[0], option.length > 1 ? option[1] : null);
}
}
return new CodecOptions(codecOptionsMap);
}
private static void unlinkSelf() {
try {
new File(SERVER_PATH).delete();