Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CodecMod/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CodecMod

This Module allows you to selectively disable audio/video hardware/software encoders/decoders.

Supports all codecs reported by Android through the MediaCodecList API.
20 changes: 20 additions & 0 deletions CodecMod/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
alias(libs.plugins.buildlogic.android.application)
}

android {
namespace = "com.programminghoch10.CodecMod"

defaultConfig {
minSdk = 16
targetSdk = 35
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}

dependencies {
implementation(libs.androidx.preference)
coreLibraryDesugaring(libs.android.desugarJdkLibs)
}
33 changes: 33 additions & 0 deletions CodecMod/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<application android:label="@string/app_name">
<activity
android:name=".SettingsActivity"
android:exported="true"
android:label="@string/title_activity_settings"
android:theme="@style/AppTheme"
>
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
</intent-filter>
</activity>

<meta-data
android:name="xposedmodule"
android:value="true"
/>
<meta-data
android:name="xposeddescription"
android:value="@string/description"
/>
<meta-data
android:name="xposedminversion"
android:value="93"
/>
</application>
</manifest>
1 change: 1 addition & 0 deletions CodecMod/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.programminghoch10.CodecMod.Hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.programminghoch10.CodecMod;

import static android.content.Context.MODE_WORLD_READABLE;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;

import java.util.LinkedList;
import java.util.List;

import de.robv.android.xposed.XSharedPreferences;

public class CodecStore {
static final boolean DEFAULT_VALUE = true;
private static final boolean REMOVE_DEFAULT_VALUE_FROM_CONFIG = true;
private static final String PREFERENCES = "codecs";
SharedPreferences sharedPreferences;
List<OnCodecPreferenceChangedListenerMeta> receivers = new LinkedList<>();

@SuppressLint("WorldReadableFiles")
CodecStore(Context context) {
this.sharedPreferences = context.getSharedPreferences(PREFERENCES, MODE_WORLD_READABLE);
}

CodecStore() {
this.sharedPreferences = new XSharedPreferences(BuildConfig.APPLICATION_ID, PREFERENCES);
}

static String getKey(MediaCodecInfoWrapper mediaCodecInfo) {
return "codec_" + mediaCodecInfo.getCanonicalName();
}

boolean getCodecPreference(MediaCodecInfoWrapper mediaCodecInfo) {
return sharedPreferences.getBoolean(getKey(mediaCodecInfo), DEFAULT_VALUE);
}

boolean setCodecPreference(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) {
boolean success;
if (REMOVE_DEFAULT_VALUE_FROM_CONFIG && enabled == DEFAULT_VALUE) {
success = sharedPreferences.edit().remove(getKey(mediaCodecInfo)).commit();
} else {
success = sharedPreferences.edit().putBoolean(getKey(mediaCodecInfo), enabled).commit();
}
if (!success)
return false;
dispatchOnCodecPreferenceChanged(mediaCodecInfo, enabled);
return true;
}

void registerOnCodecPreferenceChangedListener(MediaCodecInfoWrapper mediaCodecInfo, OnCodecPreferenceChangedListener onCodecPreferenceChangedListener) {
OnCodecPreferenceChangedListenerMeta listener = new OnCodecPreferenceChangedListenerMeta();
listener.mediaCodecInfo = mediaCodecInfo;
listener.callback = onCodecPreferenceChangedListener;
receivers.add(listener);
}

private void dispatchOnCodecPreferenceChanged(MediaCodecInfoWrapper mediaCodecInfo, boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
receivers.stream()
.filter(r -> getKey(r.mediaCodecInfo).equals(getKey(mediaCodecInfo)))
.forEach(r -> r.callback.onCodecPreferenceChanged(enabled));
} else {
for (OnCodecPreferenceChangedListenerMeta receiver : receivers) {
if (getKey(receiver.mediaCodecInfo).equals(getKey(mediaCodecInfo)))
receiver.callback.onCodecPreferenceChanged(enabled);
}
}
}

interface OnCodecPreferenceChangedListener {
void onCodecPreferenceChanged(boolean value);
}

private static class OnCodecPreferenceChangedListenerMeta {
MediaCodecInfoWrapper mediaCodecInfo;
OnCodecPreferenceChangedListener callback;
}
}
89 changes: 89 additions & 0 deletions CodecMod/src/main/java/com/programminghoch10/CodecMod/Hook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.programminghoch10.CodecMod;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Hook implements IXposedHookLoadPackage {

MediaCodecInfo[] getFilteredMediaCodecInfos(MediaCodecInfo[] unfilteredMediaCodecInfos) {
CodecStore codecStore = new CodecStore();
return Arrays.stream(unfilteredMediaCodecInfos)
.map(MediaCodecInfoWrapper::new)
.filter(codecStore::getCodecPreference)
.map(MediaCodecInfoWrapper::getOriginalMediaCodecInfo)
.toArray(MediaCodecInfo[]::new);
}

// helper function, only to be used on <LOLLIPOP
@SuppressLint("UseRequiresApi")
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
MediaCodecInfo[] getFilteredMediaCodecInfos() throws InvocationTargetException, IllegalAccessException {
List<MediaCodecInfo> mediaCodecs = new LinkedList<>();
final int codecCount = (int) XposedBridge.invokeOriginalMethod(XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecCount"), null, null);
for (int i = 0; i < codecCount; i++)
mediaCodecs.add((MediaCodecInfo) XposedBridge.invokeOriginalMethod(XposedHelpers.findMethodExact(MediaCodecList.class, "getCodecInfoAt"), null, new Object[]{i}));
return getFilteredMediaCodecInfos(mediaCodecs.toArray(MediaCodecInfo[]::new));
}

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfos", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
MediaCodecInfo[] mediaCodecInfos = (MediaCodecInfo[]) XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
if (mediaCodecInfos.length == 0) return mediaCodecInfos;
return getFilteredMediaCodecInfos(mediaCodecInfos);
}
});

// reimplementations of deprecated methods for compatibility
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos().length;
}
});
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
final int position = (int) param.args[0];
MediaCodecInfo[] mediaCodecInfos = (new MediaCodecList(MediaCodecList.REGULAR_CODECS)).getCodecInfos();
if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException();
return mediaCodecInfos[position];
}
});
} else {
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecCount", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return getFilteredMediaCodecInfos().length;
}
});
XposedHelpers.findAndHookMethod(MediaCodecList.class, "getCodecInfoAt", int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
final int position = (int) param.args[0];
MediaCodecInfo[] mediaCodecInfos = getFilteredMediaCodecInfos();
if (position < 0 || position >= mediaCodecInfos.length) throw new IllegalArgumentException();
return mediaCodecInfos[position];
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.programminghoch10.CodecMod;

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

import androidx.annotation.RequiresApi;

/**
* drop in replacement for MediaCodecInfo
* with compatibility checks for older SDKs
*
* @see MediaCodecInfo
*/
public class MediaCodecInfoWrapper {
private final MediaCodecInfo mediaCodecInfo;

MediaCodecInfoWrapper(MediaCodecInfo mediaCodecInfo) {
this.mediaCodecInfo = mediaCodecInfo;
}

public MediaCodecInfo getOriginalMediaCodecInfo() {
return mediaCodecInfo;
}

public String getCanonicalName() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return mediaCodecInfo.getCanonicalName();
}
return mediaCodecInfo.getName();
}

public boolean isAlias() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return mediaCodecInfo.isAlias();
}
return false;
}

public String getName() {
return mediaCodecInfo.getName();
}

@RequiresApi(api = Build.VERSION_CODES.Q)
public boolean isHardwareAccelerated() {
return mediaCodecInfo.isHardwareAccelerated();
}

@RequiresApi(api = Build.VERSION_CODES.Q)
public boolean isSoftwareOnly() {
return mediaCodecInfo.isSoftwareOnly();
}

@RequiresApi(api = Build.VERSION_CODES.Q)
public boolean isVendor() {
return mediaCodecInfo.isVendor();
}

public boolean isEncoder() {
return mediaCodecInfo.isEncoder();
}

public boolean isDecoder() {
return !isEncoder();
}

public String[] getSupportedTypes() {
return mediaCodecInfo.getSupportedTypes();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.programminghoch10.CodecMod;

import android.app.ActionBar;
import android.media.MediaCodecList;
import android.os.Build;
import android.os.Bundle;

import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class SettingsActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
}
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(
getSupportFragmentManager().getBackStackEntryCount() > 0
);
}
}

public static class SettingsFragment extends PreferenceFragmentCompat {
private static final boolean SHOW_ALIASES = true;

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
//getPreferenceManager().setSharedPreferencesName("codecs");
CodecStore codecStore = new CodecStore(requireContext());
PreferenceCategory decodersPreferenceCategory = findPreference("category_decoders");
PreferenceCategory encodersPreferenceCategory = findPreference("category_encoders");

List<MediaCodecInfoWrapper> mediaCodecs;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
mediaCodecs = Arrays.stream(mediaCodecList.getCodecInfos())
.map(MediaCodecInfoWrapper::new)
.toList();
} else {
mediaCodecs = new LinkedList<>();
for (int i = 0; i < MediaCodecList.getCodecCount(); i++)
mediaCodecs.add(new MediaCodecInfoWrapper(MediaCodecList.getCodecInfoAt(i)));
}
for (MediaCodecInfoWrapper mediaCodecInfo : mediaCodecs) {
if (mediaCodecInfo.isAlias() && !SHOW_ALIASES) continue;
SwitchPreference preference = new SwitchPreference(requireContext());
preference.setPersistent(false);
preference.setDefaultValue(CodecStore.DEFAULT_VALUE);
preference.setKey(CodecStore.getKey(mediaCodecInfo));
preference.setOnPreferenceChangeListener((p, n) -> codecStore.setCodecPreference(mediaCodecInfo, (Boolean) n));
codecStore.registerOnCodecPreferenceChangedListener(mediaCodecInfo, value -> {
if (preference.isChecked() != value) preference.setChecked(value);
});
preference.setTitle(mediaCodecInfo.getName()
+ (mediaCodecInfo.getName().equals(mediaCodecInfo.getCanonicalName()) ? "" : " (" + mediaCodecInfo.getCanonicalName() + ")"));
StringBuilder summaryBuilder = new StringBuilder();
summaryBuilder.append(String.format(getString(R.string.supported_types), Arrays.toString(mediaCodecInfo.getSupportedTypes())));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
summaryBuilder.append("\n");
summaryBuilder.append(String.format(getString(R.string.hardware_accelerated), mediaCodecInfo.isHardwareAccelerated()));
summaryBuilder.append("\n");
summaryBuilder.append(String.format(getString(R.string.software_only), mediaCodecInfo.isSoftwareOnly()));
if (SHOW_ALIASES) {
summaryBuilder.append("\n");
summaryBuilder.append(String.format(getString(R.string.alias), mediaCodecInfo.isAlias()));
}
summaryBuilder.append("\n");
summaryBuilder.append(String.format(getString(R.string.vendor), mediaCodecInfo.isVendor()));
}
preference.setSummary(summaryBuilder);
PreferenceCategory preferenceCategory = mediaCodecInfo.isEncoder() ? encodersPreferenceCategory : decodersPreferenceCategory;
preferenceCategory.addPreference(preference);
preference.setChecked(codecStore.getCodecPreference(mediaCodecInfo));
}
}
}
}
Loading
Loading