Skip to content

Commit 8ca1634

Browse files
add CodecMod
1 parent 620f0ff commit 8ca1634

File tree

20 files changed

+445
-0
lines changed

20 files changed

+445
-0
lines changed

CodecMod/Readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# CodecMod
2+
3+
This Module allows you to selectively disable audio/video hardware/software encoders/decoders.
4+
5+
Supports all codecs reported by Android through the MediaCodecList API.

CodecMod/build.gradle.kts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
plugins {
2+
alias(libs.plugins.buildlogic.android.application)
3+
}
4+
5+
android {
6+
namespace = "com.programminghoch10.CodecMod"
7+
8+
defaultConfig {
9+
minSdk = 16
10+
targetSdk = 35
11+
}
12+
compileOptions {
13+
isCoreLibraryDesugaringEnabled = true
14+
}
15+
}
16+
17+
dependencies {
18+
implementation(libs.androidx.preference)
19+
coreLibraryDesugaring(libs.android.desugarJdkLibs)
20+
}

CodecMod/src/main/AndroidManifest.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest
3+
xmlns:android="http://schemas.android.com/apk/res/android">
4+
5+
<application android:label="@string/app_name">
6+
<activity
7+
android:name=".SettingsActivity"
8+
android:exported="true"
9+
android:label="@string/title_activity_settings"
10+
android:theme="@style/AppTheme"
11+
>
12+
<intent-filter>
13+
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
14+
<category android:name="android.intent.category.DEFAULT" />
15+
<action android:name="android.intent.action.MAIN" />
16+
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
17+
</intent-filter>
18+
</activity>
19+
20+
<meta-data
21+
android:name="xposedmodule"
22+
android:value="true"
23+
/>
24+
<meta-data
25+
android:name="xposeddescription"
26+
android:value="@string/description"
27+
/>
28+
<meta-data
29+
android:name="xposedminversion"
30+
android:value="93"
31+
/>
32+
</application>
33+
</manifest>

CodecMod/src/main/assets/xposed_init

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.programminghoch10.CodecMod.Hook
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import static android.content.Context.MODE_WORLD_READABLE;
4+
5+
import android.annotation.SuppressLint;
6+
import android.content.Context;
7+
import android.content.SharedPreferences;
8+
import android.os.Build;
9+
10+
import java.util.LinkedList;
11+
import java.util.List;
12+
13+
import de.robv.android.xposed.XSharedPreferences;
14+
15+
public class CodecStore {
16+
static final boolean DEFAULT_VALUE = true;
17+
private static final boolean REMOVE_DEFAULT_VALUE_FROM_CONFIG = true;
18+
private static final String PREFERENCES = "codecs";
19+
SharedPreferences sharedPreferences;
20+
List<OnCodecPreferenceChangedListenerMeta> receivers = new LinkedList<>();
21+
22+
@SuppressLint("WorldReadableFiles")
23+
CodecStore(Context context) {
24+
this.sharedPreferences = context.getSharedPreferences(PREFERENCES, MODE_WORLD_READABLE);
25+
}
26+
27+
CodecStore() {
28+
this.sharedPreferences = new XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES);
29+
}
30+
31+
static String getKey(MediaCodecInfoWrapper mediaCodecInfo) {
32+
return "codec_" + mediaCodecInfo.getCanonicalName();
33+
}
34+
35+
boolean getCodecPreference(MediaCodecInfoWrapper mediaCodecInfo) {
36+
return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE);
37+
}
38+
39+
boolean setCodecPreference(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) {
40+
boolean success;
41+
if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) {
42+
success = sharedPreferences.edit().remove(getKey(mediaCodecInfo)).commit();
43+
} else {
44+
success = sharedPreferences.edit().putBoolean(getKey(mediaCodecInfo), enabled).commit();
45+
}
46+
if (!success)
47+
return false;
48+
dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled);
49+
return true;
50+
}
51+
52+
void registerOnCodecPreferenceChangedListener(MediaCodecInfoWrapper mediaCodecInfo, OnCodecPreferenceChangedListener onCodecPreferenceChangedListener) {
53+
OnCodecPreferenceChangedListenerMeta listener = new OnCodecPreferenceChangedListenerMeta();
54+
listener.mediaCodecInfo = mediaCodecInfo;
55+
listener.callback = onCodecPreferenceChangedListener;
56+
receivers.add(listener);
57+
}
58+
59+
private void dispatchOnCodecPreferenceChanged(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) {
60+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
61+
receivers.stream()
62+
.filter(r -> getKey(r.mediaCodecInfo).equals(getKey(mediaCodecInfo)))
63+
.forEach(r -> r.callback.onCodecPreferenceChanged(enabled));
64+
} else {
65+
for (OnCodecPreferenceChangedListenerMeta receiver : receivers) {
66+
if (getKey(receiver.mediaCodecInfo).equals(getKey(mediaCodecInfo)))
67+
receiver.callback.onCodecPreferenceChanged(enabled);
68+
}
69+
}
70+
}
71+
72+
interface OnCodecPreferenceChangedListener {
73+
void onCodecPreferenceChanged(boolean value);
74+
}
75+
76+
private static class OnCodecPreferenceChangedListenerMeta {
77+
MediaCodecInfoWrapper mediaCodecInfo;
78+
OnCodecPreferenceChangedListener callback;
79+
}
80+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import android.annotation.SuppressLint;
4+
import android.annotation.TargetApi;
5+
import android.media.MediaCodecInfo;
6+
import android.media.MediaCodecList;
7+
import android.os.Build;
8+
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.util.Arrays;
11+
import java.util.LinkedList;
12+
import java.util.List;
13+
14+
import de.robv.android.xposed.IXposedHookLoadPackage;
15+
import de.robv.android.xposed.XC_MethodReplacement;
16+
import de.robv.android.xposed.XposedBridge;
17+
import de.robv.android.xposed.XposedHelpers;
18+
import de.robv.android.xposed.callbacks.XC_LoadPackage;
19+
20+
public class Hook implements IXposedHookLoadPackage {
21+
22+
MediaCodecInfo[] getFilteredMediaCodecInfos(MediaCodecInfo[] unfilteredMediaCodecInfos) {
23+
CodecStore codecStore = new CodecStore();
24+
return Arrays.stream(unfilteredMediaCodecInfos)
25+
.map(MediaCodecInfoWrapper::new)
26+
.filter(codecStore::getCodecPreference)
27+
.map(MediaCodecInfoWrapper::getOriginalMediaCodecInfo)
28+
.toArray(MediaCodecInfo[]::new);
29+
}
30+
31+
// helper function, only to be used on <LOLLIPOP
32+
@SuppressLint("UseRequiresApi")
33+
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
34+
MediaCodecInfo[] getFilteredMediaCodecInfos() throws InvocationTargetException, IllegalAccessException {
35+
List<MediaCodecInfo> mediaCodecs = new LinkedList<>();
36+
final int codecCount = (int) XposedBridge.invokeOriginalMethod(XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecCount"), null, null);
37+
for (int i = 0; i < codecCount; i++)
38+
mediaCodecs.add((MediaCodecInfo) XposedBridge.invokeOriginalMethod(XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecInfoAt"), null, new Object[]{i}));
39+
return getFilteredMediaCodecInfos(mediaCodecs.toArray(MediaCodecInfo[]::new));
40+
}
41+
42+
@Override
43+
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
44+
if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) return;
45+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
46+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfos", new XC_MethodReplacement() {
47+
@Override
48+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
49+
MediaCodecInfo[] mediaCodecInfos = (MediaCodecInfo[]) XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
50+
if (mediaCodecInfos.length == 0) return mediaCodecInfos;
51+
return getFilteredMediaCodecInfos(mediaCodecInfos);
52+
}
53+
});
54+
55+
// reimplementations of deprecated methods for compatibility
56+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() {
57+
@Override
58+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
59+
return (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos().length;
60+
}
61+
});
62+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() {
63+
@Override
64+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
65+
final int position = (int) param.args[0];
66+
MediaCodecInfo[] mediaCodecInfos = (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos();
67+
if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException();
68+
return mediaCodecInfos[position];
69+
}
70+
});
71+
} else {
72+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() {
73+
@Override
74+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
75+
return getFilteredMediaCodecInfos().length;
76+
}
77+
});
78+
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() {
79+
@Override
80+
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
81+
final int position = (int) param.args[0];
82+
MediaCodecInfo[] mediaCodecInfos = getFilteredMediaCodecInfos();
83+
if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException();
84+
return mediaCodecInfos[position];
85+
}
86+
});
87+
}
88+
}
89+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import android.media.MediaCodecInfo;
4+
import android.os.Build;
5+
6+
import androidx.annotation.RequiresApi;
7+
8+
/**
9+
* drop in replacement for MediaCodecInfo
10+
* with compatibility checks for older SDKs
11+
*
12+
* @see MediaCodecInfo
13+
*/
14+
public class MediaCodecInfoWrapper {
15+
private final MediaCodecInfo mediaCodecInfo;
16+
17+
MediaCodecInfoWrapper(MediaCodecInfo mediaCodecInfo) {
18+
this.mediaCodecInfo = mediaCodecInfo;
19+
}
20+
21+
public MediaCodecInfo getOriginalMediaCodecInfo() {
22+
return mediaCodecInfo;
23+
}
24+
25+
public String getCanonicalName() {
26+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
27+
return mediaCodecInfo.getCanonicalName();
28+
}
29+
return mediaCodecInfo.getName();
30+
}
31+
32+
public boolean isAlias() {
33+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
34+
return mediaCodecInfo.isAlias();
35+
}
36+
return false;
37+
}
38+
39+
public String getName() {
40+
return mediaCodecInfo.getName();
41+
}
42+
43+
@RequiresApi(api = Build.VERSION_CODES.Q)
44+
public boolean isHardwareAccelerated() {
45+
return mediaCodecInfo.isHardwareAccelerated();
46+
}
47+
48+
@RequiresApi(api = Build.VERSION_CODES.Q)
49+
public boolean isSoftwareOnly() {
50+
return mediaCodecInfo.isSoftwareOnly();
51+
}
52+
53+
@RequiresApi(api = Build.VERSION_CODES.Q)
54+
public boolean isVendor() {
55+
return mediaCodecInfo.isVendor();
56+
}
57+
58+
public boolean isEncoder() {
59+
return mediaCodecInfo.isEncoder();
60+
}
61+
62+
public boolean isDecoder() {
63+
return !isEncoder();
64+
}
65+
66+
public String[] getSupportedTypes() {
67+
return mediaCodecInfo.getSupportedTypes();
68+
}
69+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.programminghoch10.CodecMod;
2+
3+
import android.app.ActionBar;
4+
import android.media.MediaCodecList;
5+
import android.os.Build;
6+
import android.os.Bundle;
7+
8+
import androidx.fragment.app.FragmentActivity;
9+
import androidx.preference.PreferenceCategory;
10+
import androidx.preference.PreferenceFragmentCompat;
11+
import androidx.preference.SwitchPreference;
12+
13+
import java.util.Arrays;
14+
import java.util.LinkedList;
15+
import java.util.List;
16+
17+
public class SettingsActivity extends FragmentActivity {
18+
@Override
19+
protected void onCreate(Bundle savedInstanceState) {
20+
super.onCreate(savedInstanceState);
21+
setContentView(R.layout.settings_activity);
22+
if (savedInstanceState == null) {
23+
getSupportFragmentManager()
24+
.beginTransaction()
25+
.replace(R.id.settings, new SettingsFragment())
26+
.commit();
27+
}
28+
ActionBar actionBar = getActionBar();
29+
if (actionBar != null) {
30+
actionBar.setDisplayHomeAsUpEnabled(
31+
getSupportFragmentManager().getBackStackEntryCount() > 0
32+
);
33+
}
34+
}
35+
36+
public static class SettingsFragment extends PreferenceFragmentCompat {
37+
private static final boolean SHOW_ALIASES = true;
38+
39+
@Override
40+
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
41+
setPreferencesFromResource(R.xml.root_preferences, rootKey);
42+
//getPreferenceManager().setSharedPreferencesName("codecs");
43+
CodecStore codecStore = new CodecStore(requireContext());
44+
PreferenceCategory decodersPreferenceCategory = findPreference("category_decoders");
45+
PreferenceCategory encodersPreferenceCategory = findPreference("category_encoders");
46+
47+
List<MediaCodecInfoWrapper> mediaCodecs;
48+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
49+
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
50+
mediaCodecs = Arrays.stream(mediaCodecList.getCodecInfos())
51+
.map(MediaCodecInfoWrapper::new)
52+
.toList();
53+
} else {
54+
mediaCodecs = new LinkedList<>();
55+
for (int i = 0; i < MediaCodecList.getCodecCount(); i++)
56+
mediaCodecs.add(new MediaCodecInfoWrapper(MediaCodecList.getCodecInfoAt(i)));
57+
}
58+
for (MediaCodecInfoWrapper mediaCodecInfo : mediaCodecs) {
59+
if (mediaCodecInfo.isAlias() && !SHOW_ALIASES) continue;
60+
SwitchPreference preference = new SwitchPreference(requireContext());
61+
preference.setPersistent(false);
62+
preference.setDefaultValue(CodecStore.DEFAULT_VALUE);
63+
preference.setKey(CodecStore.getKey(mediaCodecInfo));
64+
preference.setOnPreferenceChangeListener((p, n) -> codecStore.setCodecPreference(mediaCodecInfo, (Boolean) n));
65+
codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfo, value -> {
66+
if (preference.isChecked() != value) preference.setChecked(value);
67+
});
68+
preference.setTitle(mediaCodecInfo.getName()
69+
+ (mediaCodecInfo.getName().equals(mediaCodecInfo.getCanonicalName()) ? "" : " (" + mediaCodecInfo.getCanonicalName() + ")"));
70+
StringBuilder summaryBuilder = new StringBuilder();
71+
summaryBuilder.append(String.format(getString(R.string.supported_types), Arrays.toString(mediaCodecInfo.getSupportedTypes())));
72+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
73+
summaryBuilder.append("\n");
74+
summaryBuilder.append(String.format(getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated()));
75+
summaryBuilder.append("\n");
76+
summaryBuilder.append(String.format(getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly()));
77+
if (SHOW_ALIASES) {
78+
summaryBuilder.append("\n");
79+
summaryBuilder.append(String.format(getString(R.string.alias), mediaCodecInfo.isAlias()));
80+
}
81+
summaryBuilder.append("\n");
82+
summaryBuilder.append(String.format(getString(R.string.vendor), mediaCodecInfo.isVendor()));
83+
}
84+
preference.setSummary(summaryBuilder);
85+
PreferenceCategory preferenceCategory = mediaCodecInfo.isEncoder() ? encodersPreferenceCategory : decodersPreferenceCategory;
86+
preferenceCategory.addPreference(preference);
87+
preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo));
88+
}
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)