Skip to content

Commit

Permalink
Added logic to choose profile level matching the streaming properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tzah Mazuz committed Apr 17, 2020
1 parent 34040b5 commit 5a1dd3a
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 14 deletions.
66 changes: 63 additions & 3 deletions server/src/main/java/com/genymobile/scrcpy/CodecOptions.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,78 @@
package com.genymobile.scrcpy;

import android.media.MediaCodecInfo;
import android.os.Build;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class CodecOptions {
static final String PROFILE_OPTION = "profile";
static final String LEVEL_OPTION = "level";

public static final String PROFILE_OPTION = "profile";
public static final String LEVEL_OPTION = "level";

private static final LinkedHashMap<Integer, String> levelsTable = new LinkedHashMap<Integer, String>() {
{
// Adding all possible level and their properties
// 3rd property, bitrate was added but not sure if needed for now.
// Source: https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
put(MediaCodecInfo.CodecProfileLevel.AVCLevel1, "485,99,64");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel1b, "485,99,128");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel11, "3000,396,192");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel12, "6000,396,384");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel13, "11880,396,768");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel2, "11880,396,2000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel21, "19800,792,4000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel22, "20250,1620,4000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel3, "40500,1620,10000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel31, "108000,3600,14000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel32, "216000,5120,20000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel4, "245760,8192,20000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel41, "245760,8192,50000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel42, "522240,8704,50000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel5, "589824,22080,135000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel51, "983040,36864,240000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel52, "2073600,36864,240000");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaCodecInfo.CodecProfileLevel.AVCLevel6, "4177920,139264,240000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel61, "8355840,139264,480000");
put(MediaCodecInfo.CodecProfileLevel.AVCLevel62, "16711680,139264,800000");
}
}
};

private HashMap<String, String> options;

CodecOptions(HashMap<String, String> options) {
this.options = options;
}

Object parseValue(String profileOption) {
/**
* The purpose of this function is to return the lowest possible codec profile level
* that supports the given width/height/bitrate of the stream
* @param width of the device
* @param height of the device
* @param bitRate at which we stream
* @return the lowest possible level that should support the given properties.
*/
public static int calculateLevel(int width, int height, int bitRate) {
// Calculations source: https://stackoverflow.com/questions/32100635/vlc-2-2-and-levels
int macroblocks = (int)( Math.ceil(width/16.0) * Math.ceil(height/16.0) );
int macroblocks_s = macroblocks * 60;
for (Map.Entry<Integer, String> entry : levelsTable.entrySet()) {
String[] levelProperties = entry.getValue().split(",");
int levelMacroblocks_s = Integer.parseInt(levelProperties[0]);
int levelMacroblocks = Integer.parseInt(levelProperties[1]);
if(macroblocks_s > levelMacroblocks_s) continue;
if(macroblocks > levelMacroblocks) continue;
Ln.i("Level selected based on screen size calculation: " + entry.getKey());
return entry.getKey();
}
return 0;
}

public Object parseValue(String profileOption) {
String value = options.get(profileOption);
switch (profileOption) {
case PROFILE_OPTION:
Expand Down
39 changes: 28 additions & 11 deletions server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ public void streamScreen(Device device, FileDescriptor fd) throws IOException {
try {
do {
MediaCodec codec = createCodec();
setCodecProfile(codec, format);
IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();
Rect videoRect = screenInfo.getVideoSize().toRect();
int videoRotation = device.getVideoRotation(screenInfo.getRotation());
setSize(format, videoRotation, videoRect.width(), videoRect.height());
setCodecProfile(codec, format, videoRect);
configure(codec, format);
Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, videoRect);
Expand Down Expand Up @@ -144,25 +144,42 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo,
IO.writeFully(fd, headerBuffer);
}

private void setCodecProfile(MediaCodec codec, MediaFormat format) {
private void setCodecProfile(MediaCodec codec, MediaFormat format, Rect videoRect) {
int profile = (int)codecOptions.parseValue(CodecOptions.PROFILE_OPTION);
int level = (int)codecOptions.parseValue(CodecOptions.LEVEL_OPTION);
int suggestedLevel = CodecOptions.calculateLevel(videoRect.width(), videoRect.height(), bitRate);
boolean profileSupported = false;

if(profile == 0) return;
for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) {
for (MediaCodecInfo.CodecProfileLevel profileLevel :
codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) {
if(profileLevel.profile == profile) {
level = Math.max(level, profileLevel.level);
profileSupported = true;
break;
}
}
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).
if(profileSupported) {
// Profile (SDK Level 21).
format.setInteger(MediaFormat.KEY_PROFILE, profile);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.setInteger(MediaFormat.KEY_LEVEL, level);
if(level != 0) {
if(suggestedLevel != 0 && level != suggestedLevel)
Ln.w("Requested codec profile level is different from the suggested level");
} else {
level = suggestedLevel; // If no level was given, use the pre calculated level.
}
// Level (SDK Level 23).
// We ask again because suggested level can be 0 and we cant set the level to 0,
// in that case we let the encoder choose the level.
if (level != 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
format.setInteger(MediaFormat.KEY_LEVEL, level);
}
}
} else {
Ln.w("Device doesn't support the requested codec profile.\n" +
"Profile and level will be chosen automatically.");
}

}

private static MediaCodec createCodec() throws IOException {
Expand Down

0 comments on commit 5a1dd3a

Please sign in to comment.