├── install ├── .gitignore ├── files │ ├── .gitignore │ ├── 50-afrd │ ├── afrd.perm │ └── updater-script ├── README.md └── mk-update ├── android ├── settings.gradle ├── .gitignore ├── app │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── raw-ru │ │ │ │ └── .gitignore │ │ │ ├── drawable │ │ │ │ ├── no_signal.png │ │ │ │ ├── viewback.xml │ │ │ │ ├── ic_play.xml │ │ │ │ ├── ic_stop.xml │ │ │ │ ├── side_nav_bar.xml │ │ │ │ ├── ic_apply.xml │ │ │ │ ├── ic_clear.xml │ │ │ │ ├── ic_status.xml │ │ │ │ ├── ic_log.xml │ │ │ │ ├── ic_save.xml │ │ │ │ ├── ic_menu_manage.xml │ │ │ │ ├── ic_reset.xml │ │ │ │ ├── text_divider.xml │ │ │ │ ├── ic_afrd_tv.xml │ │ │ │ └── ic_afrd.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── raw │ │ │ │ └── .gitignore │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── arrays.xml │ │ │ ├── xml │ │ │ │ ├── backup_files.xml │ │ │ │ └── preferences.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── layout │ │ │ │ ├── pref_value.xml │ │ │ │ ├── fragment_faq.xml │ │ │ │ ├── fragment_prefs.xml │ │ │ │ ├── fragment_log.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── fragment_status.xml │ │ │ ├── ic_launcher-web.png │ │ │ ├── java │ │ │ └── ru │ │ │ │ └── cobra │ │ │ │ └── zap │ │ │ │ ├── xprefs │ │ │ │ ├── XListPreference.java │ │ │ │ └── XEditTextPreference.java │ │ │ │ └── afrd │ │ │ │ ├── gui │ │ │ │ ├── BootCompleteReceiver.java │ │ │ │ ├── ActionActivity.java │ │ │ │ ├── StatusFragment.java │ │ │ │ ├── SettingsFragment.java │ │ │ │ ├── AboutFragment.java │ │ │ │ ├── LogFragment.java │ │ │ │ └── AFRService.java │ │ │ │ ├── FailureDetector.java │ │ │ │ ├── jfun.java │ │ │ │ ├── Control.java │ │ │ │ └── Status.java │ │ │ ├── AndroidManifest.xml │ │ │ └── images │ │ │ └── text-divider.svg │ ├── proguard-rules.pro │ └── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── local.properties ├── build.gradle ├── gradle.properties ├── android.iml ├── gradlew.bat └── gradlew ├── mk ├── afrd.png ├── jni ├── Application.mk └── Android.mk ├── .gitignore ├── cfg_parse ├── Makefile ├── README ├── config.ini ├── cfg_parse.h ├── main.c └── cfg_parse.c ├── mkad ├── mstime.c ├── androp.h ├── colorspace.h ├── crc32.h ├── cfg.c ├── release ├── GNUmakefile ├── crc32.c ├── androp.c ├── mka ├── uevent_filter.h ├── hdcp.c ├── config ├── afrd-minix7-.ini ├── afrd-android8+.ini └── afrd-android7-.ini ├── sysfs.c ├── uevent_filter.c ├── strfun.c ├── mstime.h ├── shmem.c ├── cutils └── properties.h ├── apisock.c ├── ChangeLog-en.txt ├── ChangeLog-ru.txt ├── modes.c ├── afrd_tv.svg └── FAQ-en.txt /install/.gitignore: -------------------------------------------------------------------------------- 1 | /UPDATE*.zip 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /mk: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | make MODE=debug $* 4 | -------------------------------------------------------------------------------- /afrd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/afrd.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /captures 5 | -------------------------------------------------------------------------------- /install/files/.gitignore: -------------------------------------------------------------------------------- 1 | /arm64-v8a 2 | /armeabi-v7a 3 | /config 4 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /debug 4 | zap.jks 5 | -------------------------------------------------------------------------------- /install/files/50-afrd: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | /system/xbin/afrd -D 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/raw-ru/.gitignore: -------------------------------------------------------------------------------- 1 | /changelog.txt 2 | /copying.txt 3 | /faq.txt 4 | -------------------------------------------------------------------------------- /jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_OPTIM := release 2 | APP_ABI := armeabi-v7a arm64-v8a 3 | APP_PLATFORM := android-21 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /libs 2 | /obj 3 | /out 4 | /*.ini 5 | /releases 6 | /hotkeys 7 | /settings 8 | /unused 9 | /screenshot 10 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/no_signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/drawable/no_signal.png -------------------------------------------------------------------------------- /install/files/afrd.perm: -------------------------------------------------------------------------------- 1 | /system/xbin/afrd 0 0 0755 u:object_r:system_file:s0 2 | /system/etc/afrd.ini 0 0 0644 u:object_r:system_file:s0 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anpaza/afrd/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/raw/.gitignore: -------------------------------------------------------------------------------- 1 | /changelog.txt 2 | /copying.txt 3 | /faq.txt 4 | /afrd_arm64_v8a 5 | /afrd_armeabi_v7a 6 | /afrd_7.ini 7 | /afrd_8.ini 8 | /afrd_minix7.ini 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #635753 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/backup_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 11 02:41:13 MSK 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/viewback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /install/README.md: -------------------------------------------------------------------------------- 1 | This directory contains a script to generate UPDATE*.zip-style 2 | afrd package which can be installed via System Recovery mode. 3 | 4 | To build it, you need the AFCK toolkit from here: 5 | https://github.com/anpaza/afck 6 | 7 | Before building the installer, please build the executable first 8 | (by running mka). 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_apply.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /cfg_parse/Makefile: -------------------------------------------------------------------------------- 1 | all: cfg_parse.c main.c cfg_parse.h 2 | gcc -Wall -Wextra -ansi -pedantic -O2 -pipe -fomit-frame-pointer -march=native -c cfg_parse.c 3 | gcc -Wall -O2 -pipe -fomit-frame-pointer -march=native -c main.c 4 | gcc -Wall -Wextra -ansi -pedantic -O2 -pipe -fomit-frame-pointer -march=native -o test *.o 5 | 6 | clean: 7 | rm -f test *.o config_new.ini 8 | -------------------------------------------------------------------------------- /mkad: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DEBUG="NDK_DEBUG=1" 4 | #V=1 5 | 6 | export NDK=/usr/local/app/Android/SDK/ndk-bundle 7 | $NDK/ndk-build $DEBUG $* || exit $? 8 | 9 | adb push obj/local/armeabi-v7a/afrd /data/local/zap/afrd 10 | adb shell su -c mv /data/local/zap/afrd /data/user/0/ru.cobra.zap.afrd/cache/afrd 11 | 12 | #/usr/local/app/Android/SDK/ndk-bundle/prebuilt/linux-x86_64/bin/gdb obj/local/armeabi-v7a/afrd 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_status.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2B332B 4 | #393533 5 | #303030 6 | #3F3F3F 7 | #1D1D1C 8 | #202422 9 | 10 | -------------------------------------------------------------------------------- /android/local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Mon Mar 11 02:41:14 MSK 2019 8 | ndk.dir=/usr/local/app/Android/SDK/ndk-bundle 9 | sdk.dir=/usr/local/app/Android/SDK 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_log.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 8dp 6 | 176dp 7 | 16dp 8 | 316dp 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/pref_value.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /mstime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * Millisecond timers 8 | */ 9 | 10 | #include "mstime.h" 11 | #include 12 | #include 13 | 14 | mstime_t g_mstime; 15 | 16 | mstime_t mstime_get () 17 | { 18 | struct timeval tv; 19 | gettimeofday (&tv, NULL); 20 | return tv.tv_sec * 1000 + (tv.tv_usec / 1000); 21 | } 22 | -------------------------------------------------------------------------------- /jni/Android.mk: -------------------------------------------------------------------------------- 1 | # Android makefile for building an bionic-linked binary of the afrd 2 | 3 | LOCAL_PATH := $(call my-dir) 4 | include $(CLEAR_VARS) 5 | 6 | LOCAL_MODULE := afrd 7 | LOCAL_SRC_FILES := $(addprefix ../,main.c afrd.c sysfs.c cfg_parse/cfg_parse.c \ 8 | cfg.c modes.c mstime.c uevent_filter.c colorspace.c strfun.c shmem.c \ 9 | apisock.c crc32.c androp.c hdcp.c) 10 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/../cfg_parse 11 | LOCAL_CFLAGS := -DBDATE="\"$(shell date +"%Y-%m-%d %H:%M:%S")\"" 12 | 13 | include $(BUILD_EXECUTABLE) 14 | -------------------------------------------------------------------------------- /cfg_parse/README: -------------------------------------------------------------------------------- 1 | cfg_parse 1.0 2 | Greg Kennedy 2012 3 | kennedy.greg@gmail.com 4 | 5 | cfg_parse is a simple command-line parser 6 | 7 | I got sick of the search for something that handles key-value pairs 8 | gracefully and simply, so I rolled my own. 9 | 10 | Inclusion in your own projects is easy: simply copy cfg_parse.h/c to your 11 | src folder, and compile them right in 12 | 13 | See main.c for usage examples. 14 | 15 | This software is released into the public domain: no warranty (expressed 16 | or implied), use at your own risk, etc. etc. 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_reset.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /androp.h: -------------------------------------------------------------------------------- 1 | /* Support for ANDROid Properties */ 2 | #ifndef __ANDROP_H__ 3 | #define __ANDROP_H__ 4 | 5 | #ifdef ANDROID 6 | 7 | // Initialize the storage for property values 8 | extern void androp_init (); 9 | 10 | // Free static storage allocated for property values 11 | extern void androp_fini (); 12 | 13 | // Get a pointer to a statically allocated buffer with property value 14 | extern const char *androp_get (const char *key); 15 | 16 | #else 17 | 18 | #define androp_init() 19 | #define androp_fini() 20 | #define androp_get(key) "" 21 | 22 | #endif 23 | 24 | #endif /* __ANDROP_H__ */ 25 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # Uncomment this to preserve the line number information for 9 | # debugging stack traces. 10 | -keepattributes SourceFile,LineNumberTable 11 | 12 | # If you keep the line number information, uncomment this to 13 | # hide the original source file name. 14 | #-renamesourcefileattribute SourceFile 15 | -------------------------------------------------------------------------------- /cfg_parse/config.ini: -------------------------------------------------------------------------------- 1 | ## Just throwing some things at the cfg-parser. 2 | # It should gracefully handle a number of things. 3 | # Here are some comment lines. 4 | # A 5 | # B=C 6 | # D++++======Q+++++ 7 | # Now we have some newlines. 8 | 9 | 10 | 11 | # Next up: a line with no equate. 12 | Q 13 | THIS IS A TEST 14 | HI HI HCRH 15 | 16 | # Now for some sample equates 17 | A=B 18 | C=12345 19 | X = 88888 234 5 20 | D=D=D 21 | DELETE_ME = PLEASE 22 | E=1 # COMMENT 23 | = 7 # VALUE WITH NO KEY 24 | 7= # KEY WITH NO VALUE 25 | INFINITY=1 26 | 27 | INFINITY =2 28 | 29 | # THE END 30 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.2' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /colorspace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | #ifndef __COLORSPACE_H__ 9 | #define __COLORSPACE_H__ 10 | 11 | #include 12 | 13 | /// load colorspace-related stuff from config file 14 | extern void colorspace_init (); 15 | /// free all memory occupied by colorspace stuff 16 | extern void colorspace_fini (); 17 | /// refresh current list of supported color spaces 18 | extern bool colorspace_refresh (); 19 | /// select and apply color space dependent on video mode 20 | extern bool colorspace_apply (const char *mode); 21 | 22 | #endif /* __COLORSPACE_H__ */ 23 | -------------------------------------------------------------------------------- /crc32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * nano ZIP-compatible CRC32 implementation 8 | */ 9 | 10 | #ifndef __CRC32_H__ 11 | #define __CRC32_H__ 12 | 13 | #include 14 | 15 | /// start value for CRC32 16 | #define CRC32_START ((uint32_t)0xffffffff) 17 | 18 | /// precompute the CRC32 table 19 | extern void crc32_init (); 20 | /// update the CRC32 by memory block contents 21 | extern uint32_t crc32_update (uint32_t crc, const void *data, unsigned size); 22 | /// return the final CRC32 value 23 | extern uint32_t crc32_finish (uint32_t crc); 24 | 25 | #endif /* __CRC32_H__ */ 26 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /cfg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "afrd.h" 14 | 15 | const char *cfg_get_str (const char *key, const char *defval) 16 | { 17 | const char *ret; 18 | 19 | #if 0 20 | ret = settings_afrd_get (key); 21 | if (ret) 22 | return ret; 23 | #endif 24 | 25 | ret = cfg_get (g_cfg, key); 26 | if (ret) 27 | return ret; 28 | 29 | return defval; 30 | } 31 | 32 | int cfg_get_int (const char *key, int defval) 33 | { 34 | const char *ret = cfg_get_str (key, NULL); 35 | if (!ret) 36 | return defval; 37 | 38 | return atoi (ret); 39 | } 40 | -------------------------------------------------------------------------------- /install/mk-update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROG="$0" 4 | VERSION=`sed ../main.c -ne '/const char \*g_version/{' -e 's/^.*= *"//' -e 's/".*$//' -e p -e '}'` 5 | 6 | if test -z "$VERSION" ; then 7 | echo "$PROG: failed to get afrd version number" 8 | exit 1 9 | fi 10 | 11 | copy() { 12 | SRC="$1" 13 | DST="$2" 14 | if ! test -f "$SRC" ; then 15 | echo "$PROG: no required file $SRC" 16 | exit 1 17 | fi 18 | test -d "$DST" || mkdir -p "$DST" 19 | cp -a "$SRC" "$DST" 20 | } 21 | 22 | copy ../libs/armeabi-v7a/afrd files/armeabi-v7a 23 | copy ../libs/arm64-v8a/afrd files/arm64-v8a 24 | copy ../config/afrd-android7-.ini files/config 25 | copy ../config/afrd-android8+.ini files/config 26 | 27 | ../../afck/tools/upd-maker -o UPDATE-afrd-$VERSION.zip -s files/updater-script \ 28 | files/50-afrd files/afrd.perm \ 29 | files/armeabi-v7a files/arm64-v8a files/config 30 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script will build a release APK from scratch. 4 | # 5 | 6 | test -z "$NDK" && export NDK=/usr/local/app/Android/SDK/ndk-bundle 7 | 8 | # abort on first error 9 | set -e 10 | 11 | ./mka clean 12 | ./mka 13 | 14 | pushd android &>/dev/null 15 | ./gradlew clean 16 | ./gradlew assembleRelease 17 | popd &>/dev/null 18 | 19 | OUT=android/app/build/outputs/apk/release 20 | APK=$(find $OUT -name '*.apk') 21 | if test -z "$APK" ; then 22 | echo "No built APK file in $OUT" 23 | exit 24 | fi 25 | 26 | VERSION=`sed main.c -ne '/const char \*g_version/{' -e 's/^.*= *"//' -e 's/".*$//' -e p -e '}'` 27 | SUFFIX=`sed main.c -ne '/const char \*g_ver_sfx/{' -e 's/^.*= *"//' -e 's/".*$//' -e p -e '}'` 28 | 29 | mkdir -p release 30 | RELEASE=releases/ru.cobra.zap.afrd-${VERSION}${SUFFIX}.apk 31 | mv $APK $RELEASE 32 | 33 | echo "Finished $RELEASE" 34 | -------------------------------------------------------------------------------- /android/android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cfg_parse/cfg_parse.h: -------------------------------------------------------------------------------- 1 | /* config file parser */ 2 | /* Greg Kennedy 2012 */ 3 | 4 | #ifndef CFG_STRUCT_H_ 5 | #define CFG_STRUCT_H_ 6 | 7 | #define CFG_MAX_LINE 1024 8 | 9 | struct cfg_struct; 10 | 11 | /* Create a cfg_struct */ 12 | struct cfg_struct * cfg_init(); 13 | 14 | /* Free a cfg_struct */ 15 | void cfg_free(struct cfg_struct *); 16 | 17 | 18 | /* Load into cfg from a file */ 19 | int cfg_load(struct cfg_struct *, const char *); 20 | 21 | /* Save complete cfg to file */ 22 | int cfg_save(struct cfg_struct *, const char *); 23 | 24 | 25 | /* Get value from cfg_struct by key */ 26 | const char * cfg_get(struct cfg_struct *, const char *); 27 | 28 | /* Set key,value in cfg_struct */ 29 | void cfg_set(struct cfg_struct *, const char *, const char *); 30 | 31 | /* Delete key (+value) from cfg_struct */ 32 | void cfg_delete(struct cfg_struct *, const char *); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/xprefs/XListPreference.java: -------------------------------------------------------------------------------- 1 | package ru.cobra.zap.xprefs; 2 | 3 | import android.content.Context; 4 | import android.preference.ListPreference; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import ru.cobra.zap.afrd.gui.R; 10 | 11 | public class XListPreference extends ListPreference 12 | { 13 | public XListPreference (Context context) 14 | { 15 | this (context, null); 16 | } 17 | 18 | public XListPreference (Context context, AttributeSet attrs) 19 | { 20 | super (context, attrs); 21 | setWidgetLayoutResource (R.layout.pref_value); 22 | } 23 | 24 | @Override 25 | protected void onBindView (View view) 26 | { 27 | super.onBindView (view); 28 | 29 | final TextView value = view.findViewById (R.id.value); 30 | value.setText (getEntry ()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/xprefs/XEditTextPreference.java: -------------------------------------------------------------------------------- 1 | package ru.cobra.zap.xprefs; 2 | 3 | import android.content.Context; 4 | import android.preference.EditTextPreference; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import ru.cobra.zap.afrd.gui.R; 10 | 11 | public class XEditTextPreference extends EditTextPreference 12 | { 13 | public XEditTextPreference (Context context) 14 | { 15 | this (context, null); 16 | } 17 | 18 | public XEditTextPreference (Context context, AttributeSet attrs) 19 | { 20 | super (context, attrs); 21 | setWidgetLayoutResource (R.layout.pref_value); 22 | } 23 | 24 | @Override 25 | protected void onBindView (View view) 26 | { 27 | super.onBindView (view); 28 | 29 | final TextView value = view.findViewById (R.id.value); 30 | value.setText (getText ()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/BootCompleteReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.content.BroadcastReceiver; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.os.Build; 14 | 15 | public class BootCompleteReceiver extends BroadcastReceiver 16 | { 17 | @Override 18 | public void onReceive (Context context, Intent intent) 19 | { 20 | if (Intent.ACTION_BOOT_COMPLETED.equals (intent.getAction ())) 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 22 | context.startForegroundService (new Intent (context, AFRService.class)); 23 | else 24 | context.startService (new Intent (context, AFRService.class)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/mode_use_fract_auto 5 | @string/mode_use_fract_use 6 | @string/mode_use_fract_int 7 | 8 | 9 | 0 10 | 1 11 | 2 12 | 13 | 14 | @string/menu_status 15 | @string/menu_log 16 | @string/menu_settings 17 | @string/menu_about 18 | 19 | 20 | @string/menu_desc_status 21 | @string/menu_desc_log 22 | @string/menu_desc_settings 23 | @string/menu_desc_about 24 | 25 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | # release or debug 4 | MODE = debug 5 | 6 | CC = $(CROSS_COMPILE)gcc -c 7 | LD = $(CROSS_COMPILE)gcc 8 | 9 | CFLAGS.release = -s -O2 -Wall 10 | CFLAGS.debug = -g -Wall 11 | 12 | LDFLAGS.release = -s 13 | LDFLAGS.debug = -g 14 | 15 | CFLAGS.local = $(CFLAGS.$(MODE)) -Icfg_parse -DBDATE="\"$(shell date +"%Y-%m-%d %H:%M:%S")\"" 16 | LDFLAGS.local = $(LDFLAGS.$(MODE)) 17 | 18 | OUT = out/$(CROSS_COMPILE)$(MODE)/ 19 | 20 | all: $(OUT)afrd 21 | 22 | clean: 23 | rm -rf $(OUT) 24 | 25 | $(OUT)%.o: %.c $(OUT).stamp.dir 26 | $(CC) $(CFLAGS.local) $(CFLAGS) -o $@ $< 27 | 28 | $(OUT)%.o: cfg_parse/%.c $(OUT).stamp.dir 29 | $(CC) $(CFLAGS.local) $(CFLAGS) -o $@ $< 30 | 31 | $(OUT).stamp.dir: 32 | mkdir -p $(@D) 33 | touch $@ 34 | 35 | AFRD_SRC = main.c afrd.c sysfs.c cfg_parse.c cfg.c modes.c mstime.c uevent_filter.c \ 36 | colorspace.c strfun.c shmem.c apisock.c crc32.c androp.c 37 | 38 | $(OUT)afrd: $(addprefix $(OUT),$(AFRD_SRC:.c=.o)) 39 | $(LD) $(LDFLAGS.local) $(LDFLAGS) -o $@ $^ 40 | -------------------------------------------------------------------------------- /crc32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * nano ZIP-compatible CRC32 implementation 8 | */ 9 | 10 | #include "crc32.h" 11 | 12 | static uint32_t crc32_table [256]; 13 | 14 | void crc32_init () 15 | { 16 | for (unsigned i = 0; i < 256; i++) { 17 | uint32_t val = i; 18 | for (unsigned j = 0; j < 8; j++) 19 | val = (val & 1) ? (val >> 1) ^ 0xedb88320 : (val >> 1); 20 | crc32_table [i] = val; 21 | } 22 | } 23 | 24 | static inline uint32_t crc32_update_byte (uint32_t crc, uint8_t val) 25 | { 26 | return crc32_table [(crc ^ val) & 0xff] ^ (crc >> 8); 27 | } 28 | 29 | uint32_t crc32_update (uint32_t crc, const void *data, unsigned size) 30 | { 31 | const uint8_t *cur = (const uint8_t *)data; 32 | const uint8_t *end = cur + size; 33 | while (cur < end) 34 | crc = crc32_update_byte (crc, *cur++); 35 | return crc; 36 | } 37 | 38 | uint32_t crc32_finish (uint32_t crc) 39 | { 40 | return ~crc; 41 | } 42 | -------------------------------------------------------------------------------- /androp.c: -------------------------------------------------------------------------------- 1 | /* Support for Android properties */ 2 | 3 | #ifdef ANDROID 4 | 5 | #include 6 | #include 7 | 8 | #include "cutils/properties.h" 9 | #include "androp.h" 10 | 11 | // every prop is stored as "name\0value\0" 12 | // to have a static storage to return a pointer to 13 | static char **stor = NULL; 14 | // number of stored props 15 | static int stor_size = 0; 16 | 17 | void androp_init () 18 | { 19 | } 20 | 21 | void androp_fini () 22 | { 23 | if (stor_size) { 24 | for (int i = 0; i < stor_size; i++) 25 | free (stor [i]); 26 | free (stor); 27 | stor_size = 0; 28 | stor = NULL; 29 | } 30 | } 31 | 32 | const char *androp_get (const char *key) 33 | { 34 | char *val; 35 | int key_len = strlen (key); 36 | 37 | /* replace cached entry or create new */ 38 | int i; 39 | for (i = 0; i < stor_size; i++) 40 | if (!strcmp (stor [i], key)) 41 | goto found; 42 | 43 | stor_size++; 44 | stor = realloc (stor, stor_size * sizeof (char *)); 45 | stor [i] = malloc (key_len + 1 + PROPERTY_VALUE_MAX + 1); 46 | memcpy (stor [i], key, key_len + 1); 47 | found: 48 | val = stor [i] + key_len + 1; 49 | if (__system_property_get (key, val) <= 0) { 50 | *val = 0; 51 | return ""; 52 | } 53 | 54 | return val; 55 | } 56 | 57 | #endif /* ANDROID */ 58 | -------------------------------------------------------------------------------- /cfg_parse/main.c: -------------------------------------------------------------------------------- 1 | // driver test program for cfg_parse 2 | 3 | #include "cfg_parse.h" 4 | 5 | #include 6 | 7 | int main(int argc, char **argv) 8 | { 9 | // Pointer to a cfg_struct structure 10 | struct cfg_struct *cfg; 11 | 12 | // Initialize config struct 13 | cfg = cfg_init(); 14 | 15 | // Specifying some defaults 16 | cfg_set(cfg,"KEY","VALUE"); 17 | cfg_set(cfg,"KEY_A","DEFAULT_VALUE_A"); 18 | 19 | // "Required" file 20 | if (cfg_load(cfg,"config.ini") < 0) 21 | { 22 | fprintf(stderr,"Unable to load cfg.ini\n"); 23 | return -1; 24 | } 25 | 26 | // Several "optional" files can be added as well 27 | // Each subsequent call upserts values already in 28 | // the cfg structure. 29 | cfg_load(cfg,"/usr/local/etc/config.ini"); 30 | cfg_load(cfg,"~/.config"); 31 | 32 | // Retrieve the value for key INFINITY, and print 33 | printf("INFINITY = %s\n",cfg_get(cfg,"INFINITY")); 34 | 35 | // Retrieve the value for key "KEY", and print 36 | printf("KEY = %s\n",cfg_get(cfg,"KEY")); 37 | 38 | // Delete the key-value pair for "DELETE_ME" 39 | cfg_delete(cfg,"DELETE_ME"); 40 | 41 | // Dump cfg-struct to disk. 42 | cfg_save(cfg,"config_new.ini"); 43 | 44 | // All done, clean up. 45 | cfg_free(cfg); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /mka: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test -z "$NDK" && export NDK=/usr/local/app/Android/SDK/ndk-bundle 4 | $NDK/ndk-build $* || exit $? 5 | 6 | test -n "$*" && exit 0 7 | 8 | ANDRAW=android/app/src/main/res/raw 9 | for afrd in libs/*/afrd ; do 10 | arch=$(basename $(dirname $afrd) | tr '-' '_') 11 | cp $afrd $ANDRAW/afrd_$arch 12 | done 13 | 14 | VERSION=`sed main.c -ne '/const char \*g_version/{' -e 's/^.*= *"//' -e 's/".*$//' -e p -e '}'` 15 | SUFFIX=`sed main.c -ne '/const char \*g_ver_sfx/{' -e 's/^.*= *"//' -e 's/".*$//' -e p -e '}'` 16 | BUILD=android/app/build.gradle 17 | sed -e "s/\( *versionName *'\).*\('\)/\1$VERSION\2/" \ 18 | -e "s/\( *versionNameSuffix *'\).*\('\)/\1$SUFFIX\2/" \ 19 | "${BUILD}" > "${BUILD}~" 20 | if ! cmp -s "${BUILD}~" "${BUILD}" ; then 21 | mv -f "${BUILD}~" "${BUILD}" 22 | echo "Updated Android version number to $VERSION$SUFFIX" 23 | else 24 | rm -f "${BUILD}~" 25 | fi 26 | 27 | cp config/afrd-android7-.ini android/app/src/main/res/raw/afrd_7.ini 28 | cp config/afrd-android8+.ini android/app/src/main/res/raw/afrd_8.ini 29 | cp config/afrd-minix7-.ini android/app/src/main/res/raw/afrd_minix7.ini 30 | cp ChangeLog-en.txt android/app/src/main/res/raw/changelog.txt 31 | cp ChangeLog-ru.txt android/app/src/main/res/raw-ru/changelog.txt 32 | cp FAQ-en.txt android/app/src/main/res/raw/faq.txt 33 | cp FAQ-ru.txt android/app/src/main/res/raw-ru/faq.txt 34 | cp COPYING.txt android/app/src/main/res/raw/copying.txt 35 | cp COPYING-ru.txt android/app/src/main/res/raw-ru/copying.txt 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_faq.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | 16 | 17 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /install/files/updater-script: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ui_print "--<< Auto FrameRate Daemon installer >>--" 4 | 5 | mnt system 6 | 7 | INITD=/system/etc/init.d 8 | test -d $INITD || INITD=/system/su.d 9 | if ! test -d $INITD ; then 10 | ui_print "Your system is not configured for init.d support!" 11 | ui_print "Please configure your system before installing afrd!" 12 | ui_print "FAILING INSTALLATION" 13 | exit 1 14 | fi 15 | 16 | if test -f /sys/class/switch/hdmi/cable.0/state ; then 17 | ui_print "Kernel version 4.x detected, supposing Android 8 and up" 18 | package_extract_file config/afrd-android8+.ini /system/etc/afrd.ini 19 | elif test -f /sys/class/switch/hdmi/state ; then 20 | ui_print "Kernel version 3.x detected, supposing Android 6 or 7" 21 | package_extract_file config/afrd-android7-.ini /system/etc/afrd.ini 22 | else 23 | ui_print "Unknown kernel type, failing installation" 24 | exit 1 25 | fi 26 | 27 | if test -f /system/xbin/afrd ; then 28 | ui_print "UNINSTALLING existing afrd" 29 | rm -f /system/xbin/afrd /system/etc/afrd.ini $INITD/50-afrd 30 | else 31 | ARCH=`getprop ro.product.cpu.abi` 32 | ui_print "INSTALLING binaries for arch $ARCH" 33 | 34 | ui_print "INSTALLING autostart script to $INITD" 35 | package_extract_file 50-afrd $INITD 36 | package_extract_file $ARCH/afrd /system/xbin 37 | 38 | ui_print "SETTING UP access rights, modes and contexts..." 39 | package_extract_file afrd.perm $TMP 40 | perm $TMP/afrd.perm 41 | set_perm 0 0 0755 u:object_r:system_file:s0 $INITD/50-afrd 42 | fi 43 | 44 | ui_print "Done" 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 14 | 19 | 20 | 21 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/ActionActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.app.Activity; 11 | import android.content.Intent; 12 | import android.os.Bundle; 13 | 14 | import ru.cobra.zap.afrd.Config; 15 | import ru.cobra.zap.afrd.Control; 16 | 17 | public class ActionActivity extends Activity 18 | { 19 | static final String INTENT_START = "ru.cobra.afrd.START"; 20 | static final String INTENT_STOP = "ru.cobra.afrd.STOP"; 21 | 22 | @Override 23 | protected void onCreate (Bundle savedInstanceState) 24 | { 25 | super.onCreate (savedInstanceState); 26 | 27 | Intent intent = getIntent (); 28 | String action = intent.getAction (); 29 | 30 | if (action != null) 31 | if (action.equals (INTENT_START)) 32 | enableAfrd (true); 33 | else if (action.equals (INTENT_STOP)) 34 | enableAfrd (false); 35 | 36 | finishAndRemoveTask (); 37 | } 38 | 39 | private void enableAfrd (boolean state) 40 | { 41 | Control control = new Control (this); 42 | Config config = new Config (); 43 | if (!config.load (control.mIni)) 44 | return; 45 | 46 | if (state == config.getBoolean ("enable", true)) 47 | return; 48 | 49 | config.put ("enable", state); 50 | config.save (control.mIni); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId 'ru.cobra.zap.afrd' 7 | minSdkVersion 23 8 | compileSdkVersion 27 9 | versionCode 2 10 | versionName '0.3.2' 11 | targetSdkVersion 28 12 | versionNameSuffix '' 13 | project.ext.set ('archivesBaseName', "${applicationId}-${versionName}${versionNameSuffix}".toString ()) 14 | } 15 | signingConfigs { 16 | zap { 17 | storeFile file("zap.jks") 18 | keyAlias "key0" 19 | storePassword "123456" 20 | keyPassword "654321" 21 | } 22 | } 23 | buildTypes { 24 | release { 25 | minifyEnabled true 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | signingConfig signingConfigs.zap 28 | } 29 | debug { 30 | } 31 | } 32 | buildToolsVersion '28.0.3' 33 | productFlavors { 34 | } 35 | lintOptions { 36 | abortOnError false 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(include: ['*.jar'], dir: 'libs') 42 | implementation 'eu.chainfire:libsuperuser:1.0.0.+' 43 | } 44 | 45 | android { 46 | tasks.withType(JavaCompile) { 47 | configure(options) { 48 | options.encoding = 'UTF-8' 49 | options.debug = true 50 | options.failOnError = true 51 | options.warnings = true 52 | options.compilerArgs << '-Xlint:deprecation' << '-Xlint:unchecked' 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /uevent_filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | #ifndef __UEVENT_FILTER_H__ 9 | #define __UEVENT_FILTER_H__ 10 | 11 | #include 12 | 13 | typedef struct 14 | { 15 | // Attribute names 16 | const char *attr [16]; 17 | // Regular expressions for attribute values 18 | regex_t rex [16]; 19 | // Regex value 20 | const char *rexval [16]; 21 | // Filter name 22 | char *name; 23 | // Original filter string, modified for our needs 24 | char *filter; 25 | // Number of attributes 26 | int size; 27 | // Number of matches since last reset 28 | int matches; 29 | } uevent_filter_t; 30 | 31 | /// Initialize an uEvent filter object from filter expression string 32 | extern bool uevent_filter_init (uevent_filter_t *uevf, const char *name, const char *filter); 33 | /// Finalize an uEvent filter 34 | extern void uevent_filter_fini (uevent_filter_t *uevf); 35 | /// Load filter expression from config file 36 | extern bool uevent_filter_load (uevent_filter_t *uevf, const char *kw); 37 | /// Reset uEvent filter before doing any matches 38 | extern void uevent_filter_reset (uevent_filter_t *uevf); 39 | /// Match attribute against the filter 40 | extern bool uevent_filter_match (uevent_filter_t *uevf, const char *attr, const char *value); 41 | /// Check if all attributes were matched 42 | extern bool uevent_filter_matched (uevent_filter_t *uevf); 43 | 44 | /// Helper function 45 | void strip_trailing_spaces (char *eol, const char *start); 46 | /// The list of spaces characters 47 | extern const char *spaces; 48 | 49 | #endif /* __UEVENT_FILTER_H__ */ 50 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/text_divider.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /hdcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * Video mode handling & switching 8 | */ 9 | 10 | #include "afrd.h" 11 | 12 | // 0 - not supported, 1 - HDCP 1.4, 2 - HDCP 2.2 13 | int g_hdcp_enabled = 0; 14 | 15 | void hdcp_init () 16 | { 17 | char *hdcp = sysfs_get_str (g_hdmi_dev, "hdcp_mode"); 18 | char *cur = hdcp + strspn (hdcp, spaces); 19 | strip_trailing_spaces (strchr (cur, 0), cur); 20 | g_hdcp_enabled = 0; 21 | if (!strcmp (cur, "off")) { 22 | g_hdcp_enabled = 0; 23 | trace (1, "HDCP is not enabled\n"); 24 | } 25 | else if (!strcmp (cur, "14")) { 26 | g_hdcp_enabled = 1; 27 | trace (1, "HDCP 1.4 is enabled\n"); 28 | } 29 | else if (!strcmp (cur, "22")) { 30 | g_hdcp_enabled = 2; 31 | trace (1, "HDCP 2.2 is enabled\n"); 32 | } 33 | else 34 | trace (1, "Unrecognized HDCP mode: %s\n", cur); 35 | free (hdcp); 36 | } 37 | 38 | void hdcp_fini () 39 | { 40 | g_hdcp_enabled = 0; 41 | } 42 | 43 | void hdcp_restore (bool force) 44 | { 45 | static const char hdcp_mode [][3] = 46 | { 47 | "0", "14", "22" 48 | }; 49 | 50 | if (force && (g_hdcp_enabled == 0)) 51 | g_hdcp_enabled = 1; 52 | 53 | if ((g_hdcp_enabled == 0) || g_blackened) 54 | return; 55 | 56 | const char *mode = hdcp_mode [g_hdcp_enabled]; 57 | sysfs_set_str (g_hdmi_dev, "hdcp_mode", mode); 58 | trace (1, "Setting HDCP mode to %s\n", mode); 59 | } 60 | 61 | void hdcp_check () 62 | { 63 | if ((g_hdcp_enabled == 0) || g_blackened) 64 | return; 65 | 66 | char *auth = sysfs_read (DEFAULT_HDCP_AUTHENTICATED); 67 | char *cur = auth + strspn (auth, spaces); 68 | strip_trailing_spaces (strchr (cur, 0), cur); 69 | bool disabled = (strcmp (cur, "0") == 0); 70 | free (auth); 71 | 72 | if (disabled) 73 | hdcp_restore (false); 74 | } 75 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 13 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /config/afrd-minix7-.ini: -------------------------------------------------------------------------------- 1 | # config file for AmLogic kernel ~3.14.29 used in Android 6 and 7 2 | 3 | # The global on/off switch 4 | enable=1 5 | 6 | # the path to log file 7 | log.file=/data/local/afrd.log 8 | # enable logging to file 9 | log.enable=0 10 | 11 | # the sysfs directory with HDMI driver attributes 12 | hdmi.sysfs=/sys/class/amhdmitx/amhdmitx0 13 | # the flag to check if HDMI is enabled 14 | hdmi.state=/sys/class/switch/hdmi/state 15 | 16 | # current video mode 17 | mode.path=/sys/class/display/mode 18 | # prefer exact framerate match if 1 (avoids using double frame rates) 19 | mode.prefer.exact=0 20 | # fractional rate usage; 0 - auto; 1 - prefer fractional; 2 - prefer integer rates 21 | mode.use.fract=0 22 | # blacklist some refresh rates, if needed; closest match will be used 23 | #mode.blacklist.rates=23.976 24 | # extra video modes not listed in EDID info of your TV/display 25 | #mode.extra=1080p25hz 1080p50hz 26 | 27 | # choose which color space is used depending on video mode 28 | # mode regex = (rgb|444|422|420),(8bit,10bit,12bit,16bit),(full|limit) 29 | cs.select=2160p[4-9].*=420 .*=444 30 | # the sysfs attribute containing list of supported color spaces 31 | cs.list.path=/sys/class/amhdmitx/amhdmitx0/dc_cap 32 | # the sysfs attribute containing current color space 33 | cs.path=/sys/class/amhdmitx/amhdmitx0/attr 34 | 35 | # delay display mode switch by this amount of milliseconds 36 | switch.delay.on=300 37 | switch.delay.off=10000 38 | # if we can't determine movie frame rate, delay this much and retry 39 | switch.delay.retry=200 40 | # if we can't determine framerate for this long, stop trying 41 | switch.timeout=3000 42 | # disable screen at start of movie playback to minimize flicker 43 | switch.blackout=150 44 | # ignore event if delay between off/on is less than this 45 | switch.ignore=200 46 | # delay HDMI plug in/out events by this time (set to 0 to disable) 47 | switch.hdmi=2000 48 | 49 | # filter keywords for FRAME_RATE_HINT uevents 50 | uevent.filter.frhint=ACTION=change SUBSYSTEM=(video|video4linux) 51 | # filter keywords for vdec uevents 52 | uevent.filter.vdec=ACTION=(add|remove) DEVPATH=/devices/vdec.[0-9]+/.* SUBSYSTEM=platform 53 | # filter keywords for HDMI on/off 54 | uevent.filter.hdmi=ACTION=change DEVPATH=/devices/virtual/switch/hdmi SWITCH_NAME=hdmi 55 | # filter for HDCP HDMI off event 56 | uevent.filter.hdcp=ACTION=change SWITCH_NAME=hdcp SWITCH_STATE=0 57 | -------------------------------------------------------------------------------- /config/afrd-android8+.ini: -------------------------------------------------------------------------------- 1 | # config file for AmLogic kernel ~4.9.76 used in Android 8 and 9 2 | 3 | # The global on/off switch 4 | enable=1 5 | 6 | # the path to log file 7 | log.file=/data/local/afrd.log 8 | # enable logging to file 9 | log.enable=0 10 | 11 | # the sysfs directory with HDMI driver attributes 12 | hdmi.sysfs=/sys/class/amhdmitx/amhdmitx0 13 | # the flag to check if HDMI is enabled 14 | hdmi.state=/sys/class/switch/hdmi/cable.0/state 15 | 16 | # current video mode 17 | mode.path=/sys/class/display/mode 18 | # prefer exact framerate match if 1 (avoids using double frame rates) 19 | mode.prefer.exact=0 20 | # fractional rate usage; 0 - auto; 1 - prefer fractional; 2 - prefer integer rates 21 | mode.use.fract=0 22 | # blacklist some refresh rates, if needed; closest match will be used 23 | #mode.blacklist.rates=23.976 24 | # extra video modes not listed in EDID info of your TV/display 25 | #mode.extra=1080p25hz 1080p50hz 26 | 27 | # choose which color space is used depending on video mode 28 | # mode regex = (rgb|444|422|420),(8bit,10bit,12bit,16bit),(full|limit) 29 | cs.select=2160p[4-9].*=420 .*=444 30 | # the sysfs attribute containing list of supported color spaces 31 | cs.list.path=/sys/class/amhdmitx/amhdmitx0/dc_cap 32 | # the sysfs attribute containing current color space 33 | cs.path=/sys/class/amhdmitx/amhdmitx0/attr 34 | 35 | # delay display mode switch by this amount of milliseconds 36 | switch.delay.on=300 37 | switch.delay.off=10000 38 | # if we can't determine movie frame rate, delay this much and retry 39 | switch.delay.retry=200 40 | # if we can't determine framerate for this long, stop trying 41 | switch.timeout=3000 42 | # disable screen at start of movie playback to minimize flicker 43 | switch.blackout=150 44 | # ignore event if delay between off/on is less than this 45 | switch.ignore=200 46 | # delay HDMI plug in/out events by this time (set to 0 to disable) 47 | switch.hdmi=2000 48 | 49 | # video decoder status 50 | vdec.sysfs=/sys/class/vdec 51 | # filter keywords for FRAMERATE_HINT uevents 52 | uevent.filter.frhint=ACTION=change SUBSYSTEM=amhdmitx DEVNAME=amhdmitx0 53 | # filter keywords for vdec uevents 54 | uevent.filter.vdec=ACTION=(add|remove) DEVPATH=/devices/platform/vdec/.* SUBSYSTEM=platform 55 | # filter keywords for HDMI on/off 56 | uevent.filter.hdmi=ACTION=change DEVPATH=/devices/virtual/amhdmitx/amhdmitx0/hdmi DEVTYPE=hdmi 57 | # filter for HDCP HDMI off event 58 | uevent.filter.hdcp=ACTION=change DEVTYPE=hdcp STATE=HDMI=0 59 | -------------------------------------------------------------------------------- /config/afrd-android7-.ini: -------------------------------------------------------------------------------- 1 | # config file for AmLogic kernel ~3.14.29 used in Android 6 and 7 2 | 3 | # The global on/off switch 4 | enable=1 5 | 6 | # the path to log file 7 | log.file=/data/local/afrd.log 8 | # enable logging to file 9 | log.enable=0 10 | 11 | # the sysfs directory with HDMI driver attributes 12 | hdmi.sysfs=/sys/class/amhdmitx/amhdmitx0 13 | # the flag to check if HDMI is enabled 14 | hdmi.state=/sys/class/switch/hdmi/state 15 | 16 | # current video mode 17 | mode.path=/sys/class/display/mode 18 | # prefer exact framerate match if 1 (avoids using double frame rates) 19 | mode.prefer.exact=0 20 | # fractional rate usage; 0 - auto; 1 - prefer fractional; 2 - prefer integer rates 21 | mode.use.fract=0 22 | # blacklist some refresh rates, if needed; closest match will be used 23 | #mode.blacklist.rates=23.976 24 | # extra video modes not listed in EDID info of your TV/display 25 | #mode.extra=1080p25hz 1080p50hz 26 | 27 | # choose which color space is used depending on video mode 28 | # mode regex = (rgb|444|422|420),(8bit,10bit,12bit,16bit),(full|limit) 29 | cs.select=2160p[4-9].*=420 .*=444 30 | # the sysfs attribute containing list of supported color spaces 31 | cs.list.path=/sys/class/amhdmitx/amhdmitx0/dc_cap 32 | # the sysfs attribute containing current color space 33 | cs.path=/sys/class/amhdmitx/amhdmitx0/attr 34 | 35 | # delay display mode switch by this amount of milliseconds 36 | switch.delay.on=300 37 | switch.delay.off=10000 38 | # if we can't determine movie frame rate, delay this much and retry 39 | switch.delay.retry=200 40 | # if we can't determine framerate for this long, stop trying 41 | switch.timeout=3000 42 | # disable screen at start of movie playback to minimize flicker 43 | switch.blackout=150 44 | # ignore event if delay between off/on is less than this 45 | switch.ignore=200 46 | # delay HDMI plug in/out events by this time (set to 0 to disable) 47 | switch.hdmi=2000 48 | 49 | # video decoder status 50 | vdec.sysfs=/sys/class/vdec 51 | # filter keywords for FRAME_RATE_HINT uevents 52 | uevent.filter.frhint=ACTION=change SUBSYSTEM=tv DEVNAME=tv 53 | # FRAME_RATE_HINT is very unreliable with HEVC decoder 54 | frhint.vdec.blacklist=amvdec_h265 55 | # filter keywords for vdec uevents 56 | uevent.filter.vdec=ACTION=(add|remove) DEVPATH=/devices/vdec.[0-9]+/.* SUBSYSTEM=platform 57 | # filter keywords for HDMI on/off 58 | uevent.filter.hdmi=ACTION=change DEVPATH=/devices/virtual/switch/hdmi SWITCH_NAME=hdmi 59 | # filter for HDCP HDMI off event 60 | uevent.filter.hdcp=ACTION=change SWITCH_NAME=hdcp SWITCH_STATE=0 61 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sysfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "afrd.h" 17 | 18 | char *sysfs_read (const char *device_attr) 19 | { 20 | int h, n; 21 | char tmp [4096]; 22 | 23 | h = open (device_attr, O_RDONLY); 24 | if (h < 0) 25 | goto error; 26 | 27 | n = read (h, tmp, sizeof (tmp) - 1); 28 | tmp [n] = 0; 29 | 30 | close (h); 31 | return strdup (tmp); 32 | 33 | error: 34 | trace (1, "failed to read sysfs attr from %s\n", device_attr); 35 | 36 | if (h >= 0) 37 | close (h); 38 | return NULL; 39 | } 40 | 41 | char *sysfs_get_str (const char *device, const char *attr) 42 | { 43 | char *ret; 44 | if (attr) { 45 | char tmp [200]; 46 | snprintf (tmp, sizeof (tmp), "%s/%s", device, attr); 47 | ret = sysfs_read (tmp); 48 | } else 49 | ret = sysfs_read (device); 50 | 51 | if (!ret) 52 | return NULL; 53 | 54 | // remove trailing spaces 55 | char *eol = strchr (ret, 0); 56 | while ((eol > ret) && strchr ("\r\n\t ", eol [-1])) 57 | eol--; 58 | *eol = 0; 59 | return ret; 60 | } 61 | 62 | int sysfs_get_int (const char *device, const char *attr) 63 | { 64 | int val; 65 | char *vals = sysfs_get_str (device, attr); 66 | if (!vals) 67 | return -1; 68 | 69 | /* may be something like HDMI=1 */ 70 | char *eq = strchr (vals, '='); 71 | if (eq != NULL) 72 | vals = eq + 1; 73 | 74 | val = strtol (vals, NULL, 0); 75 | free (vals); 76 | 77 | return val; 78 | } 79 | 80 | int sysfs_write (const char *device_attr, const char *value) 81 | { 82 | int h, n; 83 | 84 | h = open (device_attr, O_TRUNC | O_WRONLY); 85 | if (h < 0) 86 | goto error; 87 | 88 | n = strlen (value); 89 | if (write (h, value, n) != n) 90 | goto error; 91 | 92 | close (h); 93 | return 0; 94 | 95 | error: 96 | trace (1, "failed to write [%s] into %s\n", value, device_attr); 97 | 98 | if (h >= 0) 99 | close (h); 100 | return -1; 101 | } 102 | 103 | int sysfs_set_str (const char *device, const char *attr, const char *value) 104 | { 105 | if (attr) { 106 | char tmp [200]; 107 | snprintf (tmp, sizeof (tmp), "%s/%s", device, attr); 108 | return sysfs_write (tmp, value); 109 | } else 110 | return sysfs_write (device, value); 111 | } 112 | 113 | int sysfs_set_int (const char *device, const char *attr, int value) 114 | { 115 | char tmp [11]; 116 | snprintf (tmp, sizeof (tmp), "%d", value); 117 | return sysfs_set_str (device, attr, tmp); 118 | } 119 | 120 | int sysfs_exists (const char *device_attr) 121 | { 122 | return access (device_attr, F_OK); 123 | } 124 | -------------------------------------------------------------------------------- /uevent_filter.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | #include "afrd.h" 9 | #include "uevent_filter.h" 10 | 11 | static bool append_rex (uevent_filter_t *uevf, char *str) 12 | { 13 | if (uevf->size >= ARRAY_SIZE (uevf->attr)) { 14 | trace (1, "\tmaximum number of filters reached"); 15 | return false; 16 | } 17 | 18 | str += strspn (str, spaces); 19 | if (!*str) 20 | return false; 21 | 22 | char *eq = strchr (str, '='); 23 | if (!eq) 24 | return false; 25 | 26 | strip_trailing_spaces (eq, str); 27 | eq++; 28 | eq += strspn (eq, spaces); 29 | 30 | strip_trailing_spaces (strchr (eq, 0), eq); 31 | 32 | trace (2, "\t+ %s=(%s)\n", str, eq); 33 | 34 | uevf->attr [uevf->size] = str; 35 | uevf->rexval [uevf->size] = eq; 36 | if (regcomp (&uevf->rex [uevf->size], eq, REG_EXTENDED) != 0) { 37 | trace (1, "\t ignoring bad regex: %s\n", eq); 38 | return false; 39 | } 40 | 41 | uevf->size++; 42 | return true; 43 | } 44 | 45 | bool uevent_filter_init (uevent_filter_t *uevf, const char *name, const char *filter) 46 | { 47 | memset (uevf, 0, sizeof (*uevf)); 48 | 49 | uevf->name = strdup (name); 50 | uevf->filter = strdup (filter); 51 | char *cur = uevf->filter; 52 | while (*cur) { 53 | cur += strspn (cur, spaces); 54 | char *next = cur + strcspn (cur, spaces); 55 | if (*next) 56 | *next++ = 0; 57 | 58 | append_rex (uevf, cur); 59 | 60 | cur = next; 61 | } 62 | 63 | return (uevf->size > 0); 64 | } 65 | 66 | void uevent_filter_fini (uevent_filter_t *uevf) 67 | { 68 | if (uevf->name) 69 | free (uevf->name); 70 | if (uevf->filter) 71 | free (uevf->filter); 72 | 73 | for (int i = 0; i < uevf->size; i++) 74 | regfree (&uevf->rex [i]); 75 | 76 | memset (uevf, 0, sizeof (*uevf)); 77 | } 78 | 79 | bool uevent_filter_load (uevent_filter_t *uevf, const char *kw) 80 | { 81 | const char *val = cfg_get_str (kw, NULL); 82 | if (!val) 83 | return false; 84 | 85 | trace (1, "\tloading filter %s\n", kw); 86 | return uevent_filter_init (uevf, kw, val); 87 | } 88 | 89 | void uevent_filter_reset (uevent_filter_t *uevf) 90 | { 91 | uevf->matches = 0; 92 | } 93 | 94 | bool uevent_filter_match (uevent_filter_t *uevf, const char *attr, const char *value) 95 | { 96 | for (int i = 0; i < uevf->size; i++) 97 | if (strcmp (uevf->attr [i], attr) == 0) { 98 | regmatch_t match [1]; 99 | if (regexec (&uevf->rex [i], value, 1, match, 0) == REG_NOMATCH) 100 | continue; 101 | // must match whole line 102 | if (match [0].rm_so != 0 || match [0].rm_eo != strlen (value)) 103 | continue; 104 | 105 | trace (3, "\t matched filter %s\n", uevf->name); 106 | 107 | uevf->matches++; 108 | return true; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | bool uevent_filter_matched (uevent_filter_t *uevf) 115 | { 116 | return uevf->size && (uevf->matches == uevf->size); 117 | } 118 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 12 | 14 | 19 | 22 | 27 | 30 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 44 | 45 | -------------------------------------------------------------------------------- /strfun.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * useful functions for handling strings 8 | */ 9 | 10 | #include "afrd.h" 11 | 12 | const char *spaces = " \t\r\n"; 13 | 14 | int strskip (const char *str, const char *starts) 15 | { 16 | size_t sl1 = strlen (str); 17 | size_t sl2 = strlen (starts); 18 | if (sl1 < sl2) 19 | return 0; 20 | 21 | if (memcmp (str, starts, sl2) == 0) 22 | return sl2; 23 | 24 | return 0; 25 | } 26 | 27 | void strip_trailing_spaces (char *eol, const char *start) 28 | { 29 | while (eol > start) { 30 | eol--; 31 | if (strchr (spaces, *eol) == NULL) { 32 | eol++; 33 | break; 34 | } 35 | } 36 | 37 | *eol = 0; 38 | } 39 | 40 | int parse_int (char **line) 41 | { 42 | int v = 0, d; 43 | 44 | while (*line && (d = **line)) { 45 | d -= '0'; 46 | if ((d < 0) || (d > 9)) 47 | break; 48 | 49 | v = (v * 10) + d; 50 | (*line)++; 51 | } 52 | 53 | return v; 54 | } 55 | 56 | bool strlist_load (strlist_t *list, const char *key, const char *desc) 57 | { 58 | list->size = 0; 59 | list->data = NULL; 60 | 61 | const char *str = cfg_get_str (key, NULL); 62 | if (!str) 63 | return false; 64 | 65 | if (desc) 66 | trace (1, "\tloading %s\n", desc); 67 | 68 | char *tmp = strdup (str); 69 | char *cur = tmp; 70 | while (*cur) { 71 | cur += strspn (cur, spaces); 72 | char *next = cur + strcspn (cur, spaces); 73 | if (*next) 74 | *next++ = 0; 75 | 76 | if (desc) 77 | trace (2, "\t+ %s\n", cur); 78 | 79 | list->size++; 80 | list->data = realloc (list->data, list->size * sizeof (char *)); 81 | list->data [list->size - 1] = strdup (cur); 82 | 83 | cur = next; 84 | } 85 | 86 | free (tmp); 87 | return true; 88 | } 89 | 90 | void strlist_free (strlist_t *list) 91 | { 92 | for (int i = 0; i < list->size; i++) 93 | free (list->data [i]); 94 | list->size = 0; 95 | } 96 | 97 | bool strlist_contains (strlist_t *list, const char *str) 98 | { 99 | for (int i = 0; i < list->size; i++) 100 | if (!strcmp (list->data [i], str)) 101 | return true; 102 | 103 | return false; 104 | } 105 | 106 | unsigned long find_ulong (const char *str, const char *prefix, bool *ok) 107 | { 108 | if (!*ok) 109 | return false; 110 | 111 | const char *pfx = strstr (str, prefix); 112 | if (!pfx) 113 | goto fail; 114 | 115 | char *tmp; 116 | pfx += strlen (prefix); 117 | unsigned long val = strtoul (pfx, &tmp, 10); 118 | if (tmp > pfx) 119 | return val; 120 | 121 | fail: *ok = false; 122 | return 0; 123 | } 124 | 125 | unsigned long long find_ulonglong (const char *str, const char *prefix, bool *ok) 126 | { 127 | if (!*ok) 128 | return false; 129 | 130 | const char *pfx = strstr (str, prefix); 131 | if (!pfx) 132 | goto fail; 133 | 134 | char *tmp; 135 | pfx += strlen (prefix); 136 | unsigned long val = strtoull (pfx, &tmp, 10); 137 | if (tmp > pfx) 138 | return val; 139 | 140 | fail: *ok = false; 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/FailureDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd; 9 | 10 | import android.util.Log; 11 | 12 | /** 13 | * Detector for steady failure conditions. 14 | * 15 | * If some heavy process is constantly failing, it's better to stop trying 16 | * than to continue doing that forever. 17 | * 18 | * This class implements the neccessary timings for this. 19 | */ 20 | public class FailureDetector 21 | { 22 | /// The name of this failure condition 23 | private String mName; 24 | /// true if we should give up after many failures 25 | private boolean mGiveUp = false; 26 | /// The time when previous failure occured 27 | private long mFailureLastStamp = 0; 28 | /// Number of consecutive failures 29 | private int mFailureCount = 0; 30 | /// Max number of failures to shift into the 'give up' state 31 | private final int mMaxFailures = 3; 32 | /// the minimal interval between failures to take them into account 33 | private final int mFailureMinInterval = 1000; 34 | /// the maximal interval between failures to take them into account 35 | private final int mFailureMaxInterval = 10000; 36 | 37 | public FailureDetector (String name) 38 | { 39 | mName = name; 40 | } 41 | 42 | /** 43 | * Report a successful operation to the failure detector. 44 | * All failure counters are reset. 45 | */ 46 | public void success () 47 | { 48 | if (mGiveUp) 49 | Log.i ("afrd", mName + ": resetting failure condition"); 50 | mFailureLastStamp = 0; 51 | mFailureCount = 0; 52 | mGiveUp = false; 53 | } 54 | 55 | /** 56 | * Report that the operation failed. 57 | * This counts failures and shifts into the 'give up' state if needed 58 | */ 59 | public void failure () 60 | { 61 | long now = System.currentTimeMillis (); 62 | if (mFailureLastStamp == 0) 63 | { 64 | mFailureLastStamp = now; 65 | mFailureCount = 1; 66 | return; 67 | } 68 | 69 | // don't account for failures happening too often 70 | if (now - mFailureLastStamp < mFailureMinInterval) 71 | return; 72 | 73 | // and don't count failures happening very seldom 74 | if (now - mFailureLastStamp > mFailureMaxInterval) 75 | { 76 | mFailureLastStamp = now; 77 | return; 78 | } 79 | 80 | mFailureLastStamp = now; 81 | mFailureCount++; 82 | if (mFailureCount >= mMaxFailures) 83 | { 84 | Log.e ("afrd", mName + ": Too many failures, giving up"); 85 | mGiveUp = true; 86 | } 87 | } 88 | 89 | /** 90 | * Shift into the 'give up' state immediately. 91 | * This is called if a fatal error condition has been detected by the caller 92 | * and it is 100% sure there is no sense in trying anymore. 93 | */ 94 | public void fatal () 95 | { 96 | success (); 97 | mGiveUp = true; 98 | } 99 | 100 | /** 101 | * Check if there's no sense to try again 102 | */ 103 | public boolean giveUp () 104 | { 105 | return mGiveUp; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /android/app/src/main/images/text-divider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 57 | 61 | 67 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /mstime.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * Millisecond timers 8 | */ 9 | 10 | #ifndef __MSTIME_H__ 11 | #define __MSTIME_H__ 12 | 13 | /* 14 | * This simple library provides a way to work with time intervals 15 | * with millisecond accuracy (well, it all depends on operating system 16 | * scheduler, of course). 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | /// Keep same behaviour on 32- and 64-bit machines 23 | typedef uint32_t mstime_t; 24 | 25 | /// The global current time variable; call mstime_update() to refresh. 26 | extern mstime_t g_mstime; 27 | 28 | /** 29 | * Get the current millisecond time. 30 | * This value has no real sense, it's only mea is to count time intervals. 31 | */ 32 | extern mstime_t mstime_get (); 33 | 34 | /** 35 | * Update the global g_mstime variable. 36 | * Must be called on every millisecond before you're going to work with timers. 37 | */ 38 | static inline void mstime_update () 39 | { 40 | g_mstime = mstime_get (); 41 | } 42 | 43 | /** 44 | * Arm a one-shot timer. After the timer is armed, the ost_expired() function 45 | * will return true after given amount of milliseconds. 46 | * 47 | * Uses the g_mstime global variable. 48 | * 49 | * @arg timer 50 | * A pointer to the variable that holds the timer. 51 | * @arg ms 52 | * Number of milliseconds to wait. 53 | */ 54 | static inline void mstime_arm (mstime_t *timer, uint32_t ms) 55 | { 56 | mstime_t t = g_mstime + ms; 57 | if (!t) t = 1; 58 | *timer = t; 59 | } 60 | 61 | /** 62 | * Check if timer is enabled 63 | */ 64 | static inline bool mstime_enabled (mstime_t *timer) 65 | { 66 | return (*timer != 0); 67 | } 68 | 69 | /** 70 | * Disable the timer 71 | */ 72 | static inline void mstime_disable (mstime_t *timer) 73 | { 74 | *timer = 0; 75 | } 76 | 77 | /** 78 | * Return number of milliseconds until mstime_expired() will return true. 79 | * 80 | * Uses the g_mstime global variable. 81 | * 82 | * @arg timer 83 | * A pointer to the variable that holds the timer. 84 | * @return 85 | * -1 if timer is disabled, 86 | * 0 if timer is expired, 87 | * otherwise milliseconds until expiration. 88 | */ 89 | static inline int mstime_left (mstime_t *timer) 90 | { 91 | if (!mstime_enabled (timer)) 92 | return -1; 93 | 94 | mstime_t diff = *timer - g_mstime; 95 | if (((int32_t)diff) >= 0) 96 | return diff; 97 | 98 | return 0; 99 | } 100 | 101 | /** 102 | * Check if timer is enabled and has expired. 103 | * If timer expires, it is disabled, so for any given armed timer 104 | * the function will return true only once. 105 | * 106 | * Uses the g_mstime global variable. 107 | * 108 | * @arg timer 109 | * A pointer to the variable that holds the timer. 110 | * @return 111 | * true if timer expired, false if not expired or disabled. 112 | */ 113 | static inline bool mstime_expired (mstime_t *timer) 114 | { 115 | if (!mstime_enabled (timer)) 116 | return false; 117 | 118 | if (mstime_left (timer) > 0) 119 | return false; 120 | 121 | mstime_disable (timer); 122 | return true; 123 | } 124 | 125 | /** 126 | * Check if timer is disabled or expired. 127 | * If timer expires, it is disabled, so this function will return 128 | * true only while the timer is running. 129 | * 130 | * Uses the g_mstime global variable. 131 | * 132 | * @arg timer 133 | * A pointer to the variable that holds the timer. 134 | * @return 135 | * true if timer still runs, false if expired or disabled. 136 | */ 137 | static inline bool mstime_running (mstime_t *timer) 138 | { 139 | if (!mstime_enabled (timer)) 140 | return false; 141 | 142 | if (mstime_left (timer) > 0) 143 | return true; 144 | 145 | mstime_disable (timer); 146 | return false; 147 | } 148 | 149 | #endif /* __MSTIME_H__ */ 150 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/StatusFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.animation.LayoutTransition; 11 | import android.app.Fragment; 12 | import android.content.Context; 13 | import android.os.Bundle; 14 | import android.os.Handler; 15 | import android.view.LayoutInflater; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.widget.LinearLayout; 19 | import android.widget.TextView; 20 | import android.widget.ToggleButton; 21 | 22 | import ru.cobra.zap.afrd.Status; 23 | 24 | public class StatusFragment extends Fragment 25 | { 26 | private boolean mAttached = false; 27 | private TextView mVersion, mBuildDate, mCurrentHz, mOriginalHz; 28 | private ToggleButton mEnabled, mModified, mBlackened; 29 | private View mNoSignal; 30 | private Handler mTimer = new Handler (); 31 | private Status mAfrdStatus = new Status (); 32 | 33 | static StatusFragment create () 34 | { 35 | StatusFragment fragment = new StatusFragment (); 36 | Bundle args = new Bundle (); 37 | fragment.setArguments (args); 38 | return fragment; 39 | } 40 | 41 | @Override 42 | public void onCreate (Bundle savedInstanceState) 43 | { 44 | super.onCreate (savedInstanceState); 45 | } 46 | 47 | @Override 48 | public View onCreateView (LayoutInflater inflater, ViewGroup container, 49 | Bundle savedInstanceState) 50 | { 51 | // Inflate the layout for this fragment 52 | View root = inflater.inflate (R.layout.fragment_status, container, false); 53 | mVersion = root.findViewById (R.id.textVersion); 54 | mBuildDate = root.findViewById (R.id.textBuildDate); 55 | mCurrentHz = root.findViewById (R.id.textCurrentHz); 56 | mOriginalHz = root.findViewById (R.id.textOriginalHz); 57 | mEnabled = root.findViewById (R.id.toggleEnabled); 58 | mModified = root.findViewById (R.id.toggleModified); 59 | mBlackened = root.findViewById (R.id.toggleBlackened); 60 | mNoSignal = root.findViewById (R.id.no_signal); 61 | 62 | LinearLayout status_layout = root.findViewById (R.id.status_layout); 63 | LayoutTransition lt = new LayoutTransition (); 64 | lt.setDuration (1000); 65 | status_layout.setLayoutTransition (lt); 66 | 67 | return root; 68 | } 69 | 70 | @Override 71 | public void onAttach (Context context) 72 | { 73 | super.onAttach (context); 74 | mAttached = true; 75 | mTimer.post (new Runnable () 76 | { 77 | @Override 78 | public void run () 79 | { 80 | update (); 81 | mTimer.postDelayed (this, 500); 82 | } 83 | }); 84 | } 85 | 86 | @Override 87 | public void onDetach () 88 | { 89 | super.onDetach (); 90 | mAttached = false; 91 | mAfrdStatus.close (); 92 | } 93 | 94 | private void update () 95 | { 96 | if (!mAttached) 97 | return; 98 | 99 | if (!mAfrdStatus.ok () && !mAfrdStatus.open ()) 100 | { 101 | mNoSignal.setVisibility (View.VISIBLE); 102 | return; 103 | } 104 | 105 | if (!mAfrdStatus.refresh ()) 106 | return; 107 | 108 | mNoSignal.setVisibility (View.GONE); 109 | 110 | mEnabled.setChecked (mAfrdStatus.mEnabled); 111 | mModified.setChecked (mAfrdStatus.mModified); 112 | mBlackened.setChecked (mAfrdStatus.mBlackened); 113 | mVersion.setText (mAfrdStatus.mVersion); 114 | mBuildDate.setText (mAfrdStatus.mBuildDate); 115 | mCurrentHz.setText (Status.hz2str (getResources (), mAfrdStatus.mCurrentHz)); 116 | mOriginalHz.setText (Status.hz2str (getResources (), mAfrdStatus.mOriginalHz)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /shmem.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * Shared memory functions for afrd 8 | */ 9 | 10 | #include "afrd.h" 11 | #include "crc32.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static int g_shmem_h; 20 | static char *g_shmem_path; 21 | // pointer to shared memory 22 | static afrd_shmem_t *g_shmem; 23 | // true if shmem is open for reading 24 | static bool g_shmem_read; 25 | // local copy of the statistics 26 | afrd_shmem_t g_afrd_stats; 27 | 28 | bool shmem_init (bool read) 29 | { 30 | g_shmem_read = read; 31 | 32 | crc32_init (); 33 | 34 | char shmem_path [200]; 35 | // place shared memory file in same dir where pid file is 36 | char *pidfile = strdup (g_pidfile); 37 | char *dn = dirname (pidfile); 38 | if (*dn && (access (dn, F_OK) != 0)) 39 | mkdir (dn, 0755); 40 | snprintf (shmem_path, sizeof (shmem_path), "%s/afrd.ipc", dn); 41 | free (pidfile); 42 | 43 | if (g_shmem_path) 44 | free (g_shmem_path); 45 | g_shmem_path = strdup (shmem_path); 46 | 47 | if (read) 48 | g_shmem_h = open (g_shmem_path, O_RDONLY | O_CLOEXEC); 49 | else 50 | g_shmem_h = open (g_shmem_path, O_CREAT | O_RDWR | O_CLOEXEC, 0644); 51 | 52 | if (g_shmem_h < 0) { 53 | trace (0, "failed to open shared memory %s\n", g_shmem_path); 54 | shmem_fini (); 55 | return false; 56 | } 57 | 58 | memset (&g_afrd_stats, 0, sizeof (afrd_shmem_t)); 59 | if (!read) { 60 | g_afrd_stats.size = sizeof (afrd_shmem_t); 61 | strncpy (g_afrd_stats.bdate, g_bdate, sizeof (g_afrd_stats.bdate)); 62 | strncpy (g_afrd_stats.ver_sfx, g_ver_sfx, sizeof (g_afrd_stats.ver_sfx)); 63 | 64 | // we can safely assume version format "%d.%d.%d" 65 | char *cur = (char *)g_version; 66 | g_afrd_stats.ver_major = strtoul (cur, &cur, 10); 67 | cur++; 68 | g_afrd_stats.ver_minor = strtoul (cur, &cur, 10); 69 | cur++; 70 | g_afrd_stats.ver_micro = strtoul (cur, &cur, 10); 71 | 72 | write (g_shmem_h, &g_afrd_stats, sizeof (afrd_shmem_t)); 73 | fsync (g_shmem_h); 74 | } 75 | 76 | g_shmem = (afrd_shmem_t *)mmap (NULL, sizeof (afrd_shmem_t), 77 | PROT_READ | (read ? 0 : PROT_WRITE), MAP_SHARED, g_shmem_h, 0); 78 | if (!g_shmem) { 79 | trace (0, "failed to mmap file %s\n", g_shmem_path); 80 | shmem_fini (); 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | 87 | void shmem_fini () 88 | { 89 | if (g_shmem) { 90 | // force clients to re-open the shm 91 | g_shmem->size = 0; 92 | g_shmem->crc32++; 93 | msync (g_shmem, sizeof (afrd_shmem_t), MS_SYNC); 94 | 95 | munmap (g_shmem, sizeof (afrd_shmem_t)); 96 | g_shmem = NULL; 97 | } 98 | 99 | close (g_shmem_h); 100 | if (!g_shmem_read) 101 | unlink (g_shmem_path); 102 | 103 | if (g_shmem_path) { 104 | free (g_shmem_path); 105 | g_shmem_path = NULL; 106 | } 107 | } 108 | 109 | void shmem_emerg () 110 | { 111 | unlink (g_shmem_path); 112 | 113 | if (g_shmem) { 114 | // force clients to re-open the shm 115 | g_shmem->size = 0; 116 | g_shmem->crc32++; 117 | msync (g_shmem, sizeof (afrd_shmem_t), MS_SYNC); 118 | } 119 | } 120 | 121 | void shmem_update () 122 | { 123 | if (!g_shmem || g_shmem_read) 124 | return; 125 | 126 | g_afrd_stats.crc32 = g_afrd_stats.crc32_copy = 127 | crc32_finish (crc32_update (CRC32_START, 128 | (uint8_t *)&g_afrd_stats.size, 129 | sizeof (afrd_shmem_t) - sizeof (uint32_t) * 2)); 130 | 131 | memcpy (g_shmem, &g_afrd_stats, sizeof (afrd_shmem_t)); 132 | msync (g_shmem, sizeof (afrd_shmem_t), MS_SYNC); 133 | } 134 | 135 | bool shmem_read () 136 | { 137 | if (!g_shmem || !g_shmem_read) 138 | return false; 139 | 140 | if (g_shmem->size != sizeof (afrd_shmem_t)) 141 | return false; 142 | 143 | memcpy (&g_afrd_stats, g_shmem, sizeof (afrd_shmem_t)); 144 | if (g_afrd_stats.crc32 != g_afrd_stats.crc32_copy) 145 | return false; 146 | 147 | if (g_afrd_stats.crc32 != crc32_finish (crc32_update (CRC32_START, 148 | (uint8_t *)&g_afrd_stats.size, 149 | sizeof (afrd_shmem_t) - sizeof (uint32_t) * 2))) 150 | return false; 151 | 152 | return true; 153 | } 154 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.content.Context; 11 | import android.content.SharedPreferences; 12 | import android.os.Bundle; 13 | import android.preference.EditTextPreference; 14 | import android.preference.ListPreference; 15 | import android.preference.MultiSelectListPreference; 16 | import android.preference.Preference; 17 | import android.preference.PreferenceFragment; 18 | import android.preference.PreferenceGroup; 19 | import android.preference.PreferenceScreen; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import java.util.Vector; 25 | 26 | /** 27 | * This fragment implements a editor for AFRd settings. 28 | */ 29 | public class SettingsFragment extends PreferenceFragment 30 | implements SharedPreferences.OnSharedPreferenceChangeListener, 31 | MainActivity.FragmentBack, MainActivity.FragmentPref 32 | { 33 | private MainActivity mMain; 34 | Vector mPrefStack = new Vector<> (); 35 | 36 | static SettingsFragment create () 37 | { 38 | return new SettingsFragment (); 39 | } 40 | 41 | @Override 42 | public void onCreate (Bundle savedInstanceState) 43 | { 44 | super.onCreate (savedInstanceState); 45 | 46 | getPreferenceManager ().setSharedPreferencesName ("ini_options"); 47 | 48 | // Load the preferences from an XML resource 49 | for (int t = 0; t < 2; t++) 50 | try 51 | { 52 | addPreferencesFromResource (R.xml.preferences); 53 | break; 54 | } 55 | catch (Exception ignored) 56 | { 57 | mMain.resetPrefs (); 58 | } 59 | } 60 | 61 | @Override 62 | public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 63 | { 64 | View root = inflater.inflate (R.layout.fragment_prefs, container, false); 65 | 66 | View b = root.findViewById (R.id.buttonPrefsReset); 67 | if (b != null) 68 | b.setOnClickListener (new View.OnClickListener () 69 | { 70 | @Override 71 | public void onClick (View v) 72 | { 73 | mMain.resetPrefs (); 74 | } 75 | }); 76 | 77 | return root; 78 | } 79 | 80 | @Override 81 | public void onResume () 82 | { 83 | super.onResume (); 84 | 85 | getPreferenceManager ().getSharedPreferences () 86 | .registerOnSharedPreferenceChangeListener (this); 87 | } 88 | 89 | @Override 90 | public void onPause () 91 | { 92 | super.onPause (); 93 | 94 | getPreferenceScreen ().getSharedPreferences () 95 | .unregisterOnSharedPreferenceChangeListener (this); 96 | } 97 | 98 | @Override 99 | public void onAttach (Context context) 100 | { 101 | super.onAttach (context); 102 | if (context instanceof MainActivity) 103 | mMain = (MainActivity)context; 104 | } 105 | 106 | @Override 107 | public void onDetach () 108 | { 109 | super.onDetach (); 110 | mMain = null; 111 | } 112 | 113 | @Override 114 | public void onSharedPreferenceChanged (SharedPreferences sharedPreferences, String key) 115 | { 116 | if (mMain != null) 117 | mMain.applyPrefs (); 118 | }; 119 | 120 | @Override 121 | public boolean onBackPressed () 122 | { 123 | if (mPrefStack.size () > 0) 124 | { 125 | int last = mPrefStack.size () - 1; 126 | PreferenceScreen ps = mPrefStack.elementAt (last); 127 | mPrefStack.removeElementAt (last); 128 | setPreferenceScreen (ps); 129 | return true; 130 | } 131 | 132 | return false; 133 | } 134 | 135 | @Override 136 | public boolean onPreferenceStartFragment (PreferenceFragment caller, Preference pref) 137 | { 138 | if (pref instanceof PreferenceScreen) 139 | { 140 | mPrefStack.add (caller.getPreferenceScreen ()); 141 | caller.setPreferenceScreen ((PreferenceScreen) pref); 142 | } 143 | return false; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/jfun.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd; 9 | 10 | import android.annotation.SuppressLint; 11 | import android.content.Context; 12 | import android.util.Log; 13 | 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.lang.reflect.Method; 19 | import java.util.Arrays; 20 | 21 | /** 22 | * Here's where shit hits the fun. 23 | * Java has a remarkably wretched functionality when it comes to low-level 24 | * data types such as byte or C strings. This crippled class is meant to fill 25 | * some gaps in this horrible java world. 26 | */ 27 | class jfun 28 | { 29 | /** 30 | * Compare two C strings until first 0 is encountered in either of the strings. 31 | * Returns 0 if strings are equal, or a positive value if first different 32 | * value in str1 is larger than same value in str2, or a negative value. 33 | * 34 | * @param arr1 The byte array containing first string 35 | * @param ofs1 the offset of first string within first byte array 36 | * @param arr2 The byte array containing second string 37 | * @param ofs2 the offset of second string within second byte array 38 | * @return 0 if strings are equal, or a positive or negative value. 39 | */ 40 | static int strcmp (byte [] arr1, int ofs1, byte [] arr2, int ofs2) 41 | { 42 | while (ofs1 < arr1.length && ofs2 < arr2.length) 43 | { 44 | int b1 = arr1 [ofs1++] & 0xff; 45 | int b2 = arr2 [ofs2++] & 0xff; 46 | int diff = b1 - b2; 47 | if (b1 == 0 || b2 == 0) 48 | return diff; 49 | if (diff != 0) 50 | return diff; 51 | } 52 | return 0; 53 | } 54 | 55 | static String cstr (byte[] data) 56 | { 57 | return cstr (data, 0); 58 | } 59 | 60 | static String cstr (byte[] data, int ofs) 61 | { 62 | int cur = ofs; 63 | while (cur < data.length && data [cur] != '\0') 64 | cur++; 65 | 66 | // String(byte[], int, int) is deprecated in API27 67 | // and StringBuilder does not support byte[] - what a mess 68 | return new String (Arrays.copyOfRange (data, ofs, cur)); 69 | } 70 | 71 | static String cstr (byte[] data, int ofs, int maxlen) 72 | { 73 | int cur = ofs; 74 | int end = ofs + maxlen; 75 | while (cur < end && data [cur] != '\0') 76 | cur++; 77 | 78 | // String(byte[], int, int) is deprecated in API27 79 | // and StringBuilder does not support byte[] - what a mess 80 | return new String (Arrays.copyOfRange (data, ofs, cur)); 81 | } 82 | 83 | static void logExc (String func, Exception exc) 84 | { 85 | Log.e ("afrd", String.format ("Exception in %s: %s", func, exc.getMessage ())); 86 | } 87 | 88 | static boolean extractFile (Context ctx, int res_id, File outf) 89 | { 90 | try 91 | { 92 | InputStream is = ctx.getResources ().openRawResource (res_id); 93 | FileOutputStream fs = new FileOutputStream (outf); 94 | 95 | byte[] buff = new byte[4096]; 96 | int n; 97 | while ((n = is.read (buff)) != -1) 98 | fs.write (buff, 0, n); 99 | 100 | fs.close (); 101 | is.close (); 102 | 103 | return true; 104 | } 105 | catch (IOException exc) 106 | { 107 | logExc ("extractFile", exc); 108 | } 109 | 110 | return false; 111 | } 112 | 113 | // android.os.SystemProperties is not available anymore, so we have to use reflection 114 | static String getProperty (String name) 115 | { 116 | try 117 | { 118 | @SuppressLint ("PrivateApi") @SuppressWarnings("rawtypes") 119 | Class SystemProperties = Class.forName("android.os.SystemProperties"); 120 | @SuppressWarnings("unchecked") 121 | Method get = SystemProperties.getMethod ("get", String.class); 122 | if (get != null) 123 | return (String) get.invoke (SystemProperties, new Object[] {name}); 124 | } 125 | catch (Exception e) 126 | { 127 | logExc ("getProperty", e); 128 | } 129 | return ""; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_afrd_tv.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 25 | 29 | 33 | 42 | 51 | 52 | -------------------------------------------------------------------------------- /cutils/properties.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __CUTILS_PROPERTIES_H 18 | #define __CUTILS_PROPERTIES_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | /* System properties are *small* name value pairs managed by the 30 | ** property service. If your data doesn't fit in the provided 31 | ** space it is not appropriate for a system property. 32 | ** 33 | ** WARNING: system/bionic/include/sys/system_properties.h also defines 34 | ** these, but with different names. (TODO: fix that) 35 | */ 36 | #define PROPERTY_KEY_MAX PROP_NAME_MAX 37 | #define PROPERTY_VALUE_MAX PROP_VALUE_MAX 38 | 39 | /* property_get: returns the length of the value which will never be 40 | ** greater than PROPERTY_VALUE_MAX - 1 and will always be zero terminated. 41 | ** (the length does not include the terminating zero). 42 | ** 43 | ** If the property read fails or returns an empty value, the default 44 | ** value is used (if nonnull). 45 | */ 46 | int property_get(const char *key, char *value, const char *default_value); 47 | 48 | /* property_get_bool: returns the value of key coerced into a 49 | ** boolean. If the property is not set, then the default value is returned. 50 | ** 51 | * The following is considered to be true (1): 52 | ** "1", "true", "y", "yes", "on" 53 | ** 54 | ** The following is considered to be false (0): 55 | ** "0", "false", "n", "no", "off" 56 | ** 57 | ** The conversion is whitespace-sensitive (e.g. " off" will not be false). 58 | ** 59 | ** If no property with this key is set (or the key is NULL) or the boolean 60 | ** conversion fails, the default value is returned. 61 | **/ 62 | int8_t property_get_bool(const char *key, int8_t default_value); 63 | 64 | /* property_get_int64: returns the value of key truncated and coerced into a 65 | ** int64_t. If the property is not set, then the default value is used. 66 | ** 67 | ** The numeric conversion is identical to strtoimax with the base inferred: 68 | ** - All digits up to the first non-digit characters are read 69 | ** - The longest consecutive prefix of digits is converted to a long 70 | ** 71 | ** Valid strings of digits are: 72 | ** - An optional sign character + or - 73 | ** - An optional prefix indicating the base (otherwise base 10 is assumed) 74 | ** -- 0 prefix is octal 75 | ** -- 0x / 0X prefix is hex 76 | ** 77 | ** Leading/trailing whitespace is ignored. Overflow/underflow will cause 78 | ** numeric conversion to fail. 79 | ** 80 | ** If no property with this key is set (or the key is NULL) or the numeric 81 | ** conversion fails, the default value is returned. 82 | **/ 83 | int64_t property_get_int64(const char *key, int64_t default_value); 84 | 85 | /* property_get_int32: returns the value of key truncated and coerced into an 86 | ** int32_t. If the property is not set, then the default value is used. 87 | ** 88 | ** The numeric conversion is identical to strtoimax with the base inferred: 89 | ** - All digits up to the first non-digit characters are read 90 | ** - The longest consecutive prefix of digits is converted to a long 91 | ** 92 | ** Valid strings of digits are: 93 | ** - An optional sign character + or - 94 | ** - An optional prefix indicating the base (otherwise base 10 is assumed) 95 | ** -- 0 prefix is octal 96 | ** -- 0x / 0X prefix is hex 97 | ** 98 | ** Leading/trailing whitespace is ignored. Overflow/underflow will cause 99 | ** numeric conversion to fail. 100 | ** 101 | ** If no property with this key is set (or the key is NULL) or the numeric 102 | ** conversion fails, the default value is returned. 103 | **/ 104 | int32_t property_get_int32(const char *key, int32_t default_value); 105 | 106 | /* property_set: returns 0 on success, < 0 on failure 107 | */ 108 | int property_set(const char *key, const char *value); 109 | 110 | int property_list(void (*propfn)(const char *key, const char *value, void *cookie), void *cookie); 111 | 112 | #if defined(__BIONIC_FORTIFY) 113 | 114 | extern int __property_get_real(const char *, char *, const char *) 115 | __asm__(__USER_LABEL_PREFIX__ "property_get"); 116 | __errordecl(__property_get_too_small_error, "property_get() called with too small of a buffer"); 117 | 118 | __BIONIC_FORTIFY_INLINE 119 | int property_get(const char *key, char *value, const char *default_value) { 120 | size_t bos = __bos(value); 121 | if (bos < PROPERTY_VALUE_MAX) { 122 | __property_get_too_small_error(); 123 | } 124 | return __property_get_real(key, value, default_value); 125 | } 126 | 127 | #endif 128 | 129 | #ifdef __cplusplus 130 | } 131 | #endif 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /apisock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * afrd API through localhost:50505 8 | */ 9 | 10 | #include "afrd.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | static int g_apisock = -1; 23 | 24 | bool apisock_init () 25 | { 26 | g_apisock = socket (AF_INET, SOCK_DGRAM, 0); 27 | if (g_apisock == -1) { 28 | trace (0, "Failed to create socket\n"); 29 | return false; 30 | } 31 | 32 | int opt = 1; 33 | setsockopt (g_apisock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof (opt)); 34 | 35 | struct sockaddr_in addr; 36 | memset (&addr, 0, sizeof (addr)); 37 | addr.sin_family = AF_INET; 38 | addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 39 | addr.sin_port = htons (AFRD_API_PORT); 40 | if (bind (g_apisock, (const struct sockaddr *)&addr, sizeof (addr)) < 0) { 41 | trace (0, "Failed to bind socket to port %d\n", AFRD_API_PORT); 42 | apisock_fini (); 43 | return false; 44 | } 45 | 46 | //fcntl (g_apisock, F_SETFL, O_NONBLOCK); 47 | 48 | trace (1, "AFRd API available at 127.0.0.1:%d UDP\n", AFRD_API_PORT); 49 | 50 | return true; 51 | } 52 | 53 | void apisock_fini () 54 | { 55 | if (g_apisock != -1) { 56 | close (g_apisock); 57 | g_apisock = -1; 58 | } 59 | } 60 | 61 | int apisock_prep_poll (struct pollfd *pfd, int pfd_count) 62 | { 63 | if (g_apisock == -1) 64 | if (!apisock_init ()) 65 | return 0; 66 | 67 | int n = 0; 68 | 69 | if (pfd_count) { 70 | pfd->fd = g_apisock; 71 | pfd->events = POLLIN; 72 | pfd->revents = 0; 73 | pfd_count--; 74 | pfd++; 75 | n++; 76 | } 77 | 78 | return n; 79 | } 80 | 81 | static bool apisock_is_cmd (char **cmd, const char *kw) 82 | { 83 | char *cur = *cmd; 84 | int kwl = strlen (kw); 85 | if (strncmp (cur, kw, kwl)) 86 | return false; 87 | 88 | int space = cur [kwl]; 89 | if (space && !strchr (spaces, space)) 90 | return false; 91 | 92 | cur += kwl; 93 | cur += strspn (cur, spaces); 94 | *cmd = cur; 95 | return true; 96 | } 97 | 98 | static void apisock_cmd (char *cmd, int fd, struct sockaddr *src_addr, socklen_t addrlen) 99 | { 100 | while (*cmd) { 101 | cmd += strspn (cmd, spaces); 102 | char *eol = strchr (cmd, '\n'); 103 | char *next; 104 | if (eol) 105 | next = eol + 1; 106 | else 107 | next = eol = strchr (cmd, 0); 108 | 109 | strip_trailing_spaces (eol, cmd); 110 | trace (2, "API command: [%s]\n", cmd); 111 | 112 | if (apisock_is_cmd (&cmd, "help")) { 113 | static const char *help = 114 | "help\n\tdisplay this help text\n" 115 | "frame_rate_hint \n\ttell afrd the video starting in <1.0 seconds will use /1000 frames per second (e.g. 23976 = 23.976 fps)\n" 116 | "refresh_rate \n\ttell afrd to set display refresh rate as close to /1000 Hz as possible, no arg to restore original rate\n" 117 | "color_space \n\toverride colorspace, empty arg to restore default behavior\n" 118 | "status\n\tget current afrd status\n" 119 | "reconf\n\ttell afrd to reload configuration file as soon as possible\n"; 120 | sendto (fd, help, strlen (help), 0, src_addr, addrlen); 121 | } else if (apisock_is_cmd (&cmd, "frame_rate_hint")) { 122 | int fr = parse_int (&cmd); 123 | cmd += strspn (cmd, spaces); 124 | if (!*cmd) 125 | afrd_frame_rate_hint ((fr * 256) / 1000); 126 | } else if (apisock_is_cmd (&cmd, "status")) { 127 | char status [200]; 128 | int sl = snprintf (status, sizeof (status), 129 | "stamp:%d\n" 130 | "enabled:%d\n" 131 | "active:%d\n" 132 | "blackened:%d\n" 133 | "version:%d.%d.%d\n" 134 | "build:%s\n" 135 | "current hz:%d\n" 136 | "original hz:%d\n", 137 | g_afrd_stats.crc32, 138 | g_afrd_stats.enabled ? 1 : 0, 139 | g_afrd_stats.switched ? 1 : 0, 140 | g_afrd_stats.blackened ? 1 : 0, 141 | g_afrd_stats.ver_major, g_afrd_stats.ver_minor, g_afrd_stats.ver_micro, 142 | g_afrd_stats.bdate, 143 | g_afrd_stats.current_hz * 1000 / 256, 144 | g_afrd_stats.original_hz * 1000 / 256); 145 | sendto (fd, status, sl, 0, src_addr, addrlen); 146 | } else if (apisock_is_cmd (&cmd, "reconf")) { 147 | afrd_reconf (); 148 | } else if (apisock_is_cmd (&cmd, "refresh_rate")) { 149 | int fr = parse_int (&cmd); 150 | cmd += strspn (cmd, spaces); 151 | if (!*cmd) 152 | afrd_refresh_rate ((fr * 256) / 1000); 153 | } else if (apisock_is_cmd (&cmd, "color_space")) { 154 | afrd_override_colorspace (&cmd); 155 | } else { 156 | trace (2, "\t> unknown command\n"); 157 | cmd = strchr (cmd, 0); 158 | } 159 | 160 | if (*cmd) 161 | trace (2, "\t> bad args\n"); 162 | 163 | cmd = next; 164 | } 165 | } 166 | 167 | void apisock_handle (struct pollfd *pfd, int pfd_count) 168 | { 169 | for (; pfd_count; pfd++, pfd_count--) { 170 | if (pfd->fd == g_apisock) { 171 | if (pfd->revents & (POLLHUP | POLLERR | POLLNVAL)) 172 | apisock_fini (); 173 | else if (pfd->revents & POLLIN) { 174 | char cmd [1024]; 175 | struct sockaddr src_addr; 176 | socklen_t addrlen = sizeof (src_addr); 177 | int n = recvfrom (g_apisock, cmd, sizeof (cmd) - 1, 0, 178 | &src_addr, &addrlen); 179 | if (n > 0) { 180 | cmd [n] = 0; 181 | apisock_cmd (cmd, pfd->fd, &src_addr, addrlen); 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/AboutFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.app.Fragment; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.Button; 17 | import android.widget.LinearLayout; 18 | import android.widget.TextView; 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.ArrayList; 24 | 25 | public class AboutFragment extends Fragment 26 | { 27 | private final ArrayList mAbouts = new ArrayList<> (); 28 | private int mAboutsIndex; 29 | private boolean mAboutsShow; 30 | private Handler mTimer = new Handler (); 31 | private TextView mLicenseText; 32 | private LinearLayout mAboutBottom; 33 | private Button mShowAll; 34 | private boolean mLicenseDisplayed = false; 35 | 36 | public static AboutFragment create () 37 | { 38 | return new AboutFragment (); 39 | } 40 | 41 | @Override 42 | public View onCreateView (LayoutInflater inflater, ViewGroup container, 43 | Bundle savedInstanceState) 44 | { 45 | View root = inflater.inflate (R.layout.fragment_about, container, false); 46 | 47 | // prepare the authors animation 48 | final LinearLayout thanks = root.findViewById (R.id.about_thanks); 49 | mAbouts.clear (); 50 | mAboutsIndex = 1000; 51 | mAboutsShow = true; 52 | for (int i = 0; i < thanks.getChildCount (); i++) 53 | { 54 | final View c = thanks.getChildAt (i); 55 | c.setVisibility (View.GONE); 56 | if (c instanceof LinearLayout) 57 | mAbouts.add (c); 58 | } 59 | 60 | animate (); 61 | 62 | TextView whatsnew = root.findViewById (R.id.whatsnew); 63 | whatsnew.setText (readWhatsnew (R.raw.changelog)); 64 | 65 | mLicenseText = root.findViewById (R.id.license); 66 | mAboutBottom = root.findViewById (R.id.about_bottom); 67 | 68 | mShowAll = root.findViewById (R.id.butLicense); 69 | mShowAll.setOnClickListener (new View.OnClickListener () 70 | { 71 | @Override 72 | public void onClick (View v) 73 | { 74 | toggleLicense (); 75 | } 76 | }); 77 | 78 | mLicenseText.setOnClickListener (new View.OnClickListener () 79 | { 80 | @Override 81 | public void onClick (View v) 82 | { 83 | toggleLicense (); 84 | } 85 | }); 86 | 87 | return root; 88 | } 89 | 90 | private void toggleLicense () 91 | { 92 | mLicenseDisplayed = !mLicenseDisplayed; 93 | if (mLicenseDisplayed) 94 | { 95 | mLicenseText.setText (readLicense (R.raw.copying)); 96 | mShowAll.setText (R.string.about_hidelic); 97 | mAboutBottom.setVisibility (View.GONE); 98 | } 99 | else 100 | { 101 | mLicenseText.setText (getString (R.string.about_gpl3)); 102 | mShowAll.setText (R.string.about_showlic); 103 | mAboutBottom.setVisibility (View.VISIBLE); 104 | } 105 | } 106 | 107 | private String readFile (int resid) 108 | { 109 | try 110 | { 111 | InputStream is = getResources ().openRawResource (resid); 112 | byte[] buff = new byte[is.available ()]; 113 | if (is.read (buff) < 0) 114 | return ""; 115 | 116 | return new String (buff, StandardCharsets.UTF_8); 117 | } 118 | catch (IOException ignored) 119 | { 120 | } 121 | 122 | return ""; 123 | } 124 | 125 | private String readWhatsnew (int resid) 126 | { 127 | String ret = readFile (resid); 128 | // Remove newlines not followed by another newline 129 | return ret.replaceAll (" *([^\n])\n +", "$1 "); 130 | } 131 | 132 | private String readLicense (int resid) 133 | { 134 | String ret = readFile (resid); 135 | // Remove newlines not followed by a line starting with space 136 | return ret.replaceAll ("(.+) *\n([^ \t\n])", "$1 $2"); 137 | } 138 | 139 | private void animate () 140 | { 141 | int delay; 142 | 143 | if (mAboutsShow) 144 | { 145 | mAboutsIndex += 1; 146 | if (mAboutsIndex >= mAbouts.size ()) 147 | { 148 | mAboutsIndex = -1; 149 | delay = 10000; 150 | } 151 | else 152 | { 153 | View v = mAbouts.get (mAboutsIndex); 154 | v.setVisibility (View.VISIBLE); 155 | mAboutsShow = false; 156 | delay = 5000; 157 | } 158 | } 159 | else 160 | { 161 | View v = mAbouts.get (mAboutsIndex); 162 | v.setVisibility (View.GONE); 163 | mAboutsShow = true; 164 | delay = 1000; 165 | } 166 | 167 | mTimer.postDelayed (new Runnable () 168 | { 169 | @Override 170 | public void run () 171 | { 172 | animate (); 173 | } 174 | }, delay); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /ChangeLog-en.txt: -------------------------------------------------------------------------------- 1 | 0.3.2 Fri Apr 26 23:09:17 MSK 2019 2 | 3 | Implemented HDMI re-enabling after HDCP turns off. Usually happens 4 | after TV refresh rate switch and can lead to "black screen". 5 | 6 | Enhanced framerate detection via vdec_chunks; earlier fps computations 7 | could be influenced by a serious precision error. This should reduce 8 | the number of failed fps detections. 9 | 10 | Added limited support for Minux Neo U9-H. The Minix kernel has broken 11 | most of the information sources used by AFRd to determine movie framerate, 12 | except one, but that source is not very stable. So, on these firmwares 13 | (even ported to other devices) AFRd will work much worse than on 14 | "normal chinese" firmware. 15 | 16 | 0.3.1 Fri Apr 12 09:31:15 MSK 2019 17 | 18 | Changed copying conditions (license) from 'none' to GPL-3. 19 | Added full license text to application (on the 'About' page). 20 | 21 | Added the switch.hdmi option that allows to adjust the handling 22 | of HDMI hotplug events, or disable it altogether. 23 | 24 | Fixed a bug that would cause application notification sometimes 25 | to display the wrong current refresh rate 26 | 27 | Added display of current value in preferences editor. 28 | 29 | Now supporting switching colorspaces on kernel 3.14.29 30 | (Android 6 & 7)! 31 | 32 | Fixed black screen on refresh rate change on boxes with built-in 33 | HDCP 1.4 keys. This also was the cause of "black screen" at system 34 | startup for many users. 35 | 36 | Fixed framerate not switching in some circumstances (for example, 37 | HDVideBox + MX Player) because of the added in previous version 38 | option switch.ignore. 39 | 40 | 0.3.0 Sun Apr 7 12:54:16 MSK 2019 41 | 42 | Now daemon checks if another daemon is already running and won't 43 | run a second copy. 44 | 45 | Fixed some bugs that could lead to afrd crashes. 46 | 47 | Installation and set-up via Android application! 48 | 49 | Notifications fixed in Android 6 & 7 50 | 51 | New option to show display refresh rate after changes. 52 | 53 | Added the obligatory 'About' screen. 54 | 55 | Added the 'Frequently Asked Questions' screen. 56 | 57 | Removed dependency of Android setting key_hdmi_selfadaptation, 58 | now there are many other ways to disable afrd. 59 | 60 | Substantially improved frame rate detection algorithm. 61 | 62 | Added option frhint.vdec.blacklist so that afrd will ignore 63 | FRAME_RATE_HINTs from certain video decoders. This allowed 64 | to solve an old problem on Android 6 and 7 with the H265 65 | decoder, which generates wrong frame rates. 66 | 67 | Added user-mode API so that co-operative video player software 68 | can tell afrd the expected framerate of the starting video, 69 | and more (see docs). 70 | 71 | When started as daemon, afrd will now switch to root namespace 72 | to avoid problems with the 'use separate namespaces' option of SuperSU. 73 | 74 | Added switch.ignore config option and the equivalent setting in 75 | Android application to ignore video rewinds. 76 | 77 | 0.2.4 Sun Mar 10 18:22:32 2019 +0300 78 | 79 | Added option switch.timeout that sets the timeout for framerate switch. 80 | If movie frame rate can't be determined for so much time, framerate 81 | switching fails. 82 | 83 | Added option switch.blackout to blackout the screen at start of playback. 84 | This removes unneeded flicker at playback start time. 85 | 86 | AFRd installer via System Recovery! 87 | 88 | 0.2.2 Fri Mar 8 00:34:39 2019 +0300 89 | 90 | Substantially enhanced frame rate detection of the starting movie. 91 | * After start afrd will wait 300ms. 92 | * After that every 100ms afrd guesses fps by querying video decoder 93 | status, if it is present. 94 | * Also video decoder statistics (chunk pps times) are queried and 95 | another guess is made off that. 96 | * If three times in a row these values are less than 0.1% apart, 97 | afrd considers the guess correct. 98 | 99 | (Re-)implemented support for Android kernels < 7.0 100 | 101 | Now it is possible to choose either only fractional frame rates 102 | (23.976, 29.97, 59.94), only integer (24, 30, 60) or all. 103 | 104 | Now it is possible to choose whenever afrd will prefer larger refresh 105 | rates (e.g. 50hz for 25fps, 60hz for 30fps) or use exactly same rate. 106 | 107 | HDMI hot-plugging events now supported; TV autodetection will be re-run. 108 | 109 | Now you can blacklist some of the refresh rates. 110 | 111 | Now you can change color space, color depth and color range for every 112 | supported video mode. This effectively makes AFR possible in 4k! 113 | 114 | Setting switch.delay.off to 0 will disable restoration of the original 115 | refresh rate at all. 116 | 117 | Added option switch.delay.retry to set the delay before repeated tries 118 | to detect frame rate, if previous attempt fails. 119 | 120 | 0.2.0 Sat Feb 23 02:20:21 2019 +0300 121 | 122 | Implemented refresh rate switching by hardware decoder activation. 123 | This allows afrd to work without kernel support and makes it a universal 124 | tool for most newer AmLogic based devices. 125 | 126 | Switching by FRAME_RATE_HINT events is still there and has higher priority. 127 | 128 | Tried to get rid of extraneous rate switches during ff/rewind. 129 | 130 | 0.1.1 Tue Jan 1 17:48:50 2019 +0300 131 | 132 | Added X96 support via patched kernel 4.9 for S905X2. 133 | 134 | Added fractional fps support (23.976, 29.97, 59.94). 135 | 136 | 0.1.0 Sun Aug 20 21:59:45 2017 +0300 137 | 138 | First release of the auto framerate daemon as part of the custom kernel 139 | for Android TV box X92/S912. 140 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_afrd.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 16 | 24 | 31 | 38 | 47 | 54 | 63 | 70 | 77 | 85 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /ChangeLog-ru.txt: -------------------------------------------------------------------------------- 1 | 0.3.2 Птн Апр 26 23:09:17 МСК 2019 2 | 3 | Исправлено перевключение экрана после отваливания HDCP (одна из причин 4 | "чёрных экранов"). 5 | 6 | Исправлена проблема, из-за которой vdec_chunks неточно вычислял частоту 7 | при малом количестве сэмплов. Должно уменьшить количество неудачных 8 | определений частоты. 9 | 10 | Добавлена ограниченная поддержка Minux Neo U9-H. В ядре Minix сломаны 11 | все источники информации, которыми пользуется afrd для определения кино, 12 | кроме одного, да и тот работает не всегда. Поэтому на этих прошивках 13 | AFRd будет работать гораздо менее надёжно, чем на других. 14 | 15 | 0.3.1 Птн Апр 12 09:31:15 МСК 2019 16 | 17 | Сменил условия распространения (лицензию) с "никакие" на GPL-3. 18 | В приложение добавлен полный текст лицензии (на странице "О программе"). 19 | 20 | Добавлена опция switch.hdmi, которая позволяет управлять задержкой 21 | обработки событий горячего подключения HDMI кабеля, или отключить 22 | её совсем. 23 | 24 | Исправлена ошибка, из-за которой в шторке иногда неверно отображалась 25 | текущая частота вертикальной развёртки. 26 | 27 | Добавлено отображение текущего значения при редактировании настроек. 28 | 29 | Поддерживается переключение цветовых пространств на ядрах 3.14.29 30 | (Android 6 и 7)! 31 | 32 | Поправлен "чёрный экран" при переключении частоты на устройствах 33 | со встроенными ключами HDCP 1.4. У многих это также проявлялось как 34 | "чёрный экран" при старте системы. 35 | 36 | Исправлено игнорирование переключения частоты в некоторых комбинациях 37 | проигрывателей (например, HDVideoBox + MX Player) из-за добавленной 38 | в предыдущей версии опции switch.ignore. 39 | 40 | 0.3.0 Вск Апр 7 12:54:16 МСК 2019 41 | 42 | Исправил возможность двойного запуска демона. 43 | 44 | Исправлен ряд ошибок, могущих привести к падению демона 45 | (так ему и надо, нечистой силе!) 46 | 47 | Установка и настройка через Android приложение! 48 | 49 | Уведомления исправлены в Android 6 и 7 50 | 51 | Новая опция для отображения частоты развёртки после смены. 52 | 53 | Добавлен обязательный экран "О программе". 54 | 55 | Добавлен экран "Часто задаваемые вопросы". 56 | 57 | Убрал зависимость от настройки Android key_hdmi_selfadaptation, 58 | теперь есть много других способов выключить afrd. 59 | 60 | Существенно улучшен алгоритм распознавания частоты кадров. 61 | 62 | Добавлена опция frhint.vdec.blacklist для того, чтобы afrd игнорировал 63 | FRAME_RATE_HINT от определённых декодеров видео. Это позволило решить 64 | давнюю проблему на Android 6 и 7 с кодеком H265, который выдаёт 65 | ложную частоту кадров. 66 | 67 | Добавлено пользовательское API для того, чтобы сотрудничающие программы 68 | могли сообщить afrd ожидаемую частоту кадров стартующего видео, а также 69 | для других функций (см. документацию). 70 | 71 | При запуске afrd теперь переключается в корневое пространство имён, 72 | чтобы избежать проблем с опцией "создавать поимённое разделение 73 | пространств" в SuperSU (какой балбес только переводил SuperSU, 74 | должно быть "создавать отдельное пространство имён"). 75 | 76 | Добавлена опция switch.ignore и эквивалентная настройка в Android 77 | приложении чтобы игнорировать перемотку видео. 78 | 79 | 0.2.4 Вск Мар 10 18:22:32 2019 +0300 80 | 81 | Новая опция switch.timeout которая задаёт тайм-аут попыток определения 82 | fps видео. 83 | 84 | Новая опция fps.blackout, которая задаёт интервал, через который экран 85 | будет выключен при включении видео, во избежание излишних хлопаний. 86 | 87 | Инсталлятор AFRd через System Recovery. 88 | 89 | 0.2.2 Птн Мар 8 00:34:39 2019 +0300 90 | 91 | Существенно улучшено распознавание частоты кадров запущенного видео. 92 | * После старта видео afrd выжидает 300мс. 93 | * Через каждые 100мс вычисляется fps по данным из 94 | /sys/class/vdec/dump_vdec_blocks (если они есть) 95 | * Параллельно вычитывается fps из /sys/class/vdec/vdec_status 96 | * Если три раза подряд показания этих двух параметров совпали 97 | с точностью 0.1%, afrd считает, что это истиный fps видео. 98 | 99 | Реализована поддержа ядер от андроида <= 7.0 100 | 101 | Добавлена возможность через конфиг задать выбор только дробных режимов 102 | (23.976, 29.97, 59.94), только целочисленных (24, 30, 60) или всех. 103 | 104 | Добавлена возможность предпочитать наиболее точную частоту обновления или 105 | наоборот, предпочитать кратно более высокие частоты (25 -> 50, 30 -> 60 итд). 106 | 107 | Добавлена обработка горячего подключения/отключения HDMI. 108 | 109 | Добавлена опция запрета использования определённых частот. 110 | 111 | Добавлена возможность выбора цветого пространства, глубины цвета и диапазона 112 | значений. Теперь AFR работает в 4k режимах. 113 | 114 | Установка параметра switch.delay.off в 0 отключает восстановление исходного 115 | режима экрана 116 | 117 | Добавлена опция switch.delay.retry для установки паузы перед повторными 118 | попытками получить частоту видео. 119 | 120 | 0.2.0 Сбт Фев 23 02:20:21 2019 +0300 121 | 122 | Сделано переключение частоты экрана по активации драйвера аппаратного 123 | декодера. Это позволяет работать без поддержки afrd в ядре и выводит 124 | afrd на уровень универсального инструмента для всех платформ на базе 125 | новых процессоров от AmLogic. 126 | 127 | Переключение по событию FRAME_RATE_HINT (модифицированные ядра и старые 128 | ядра от AmLogic (~лето-осень 2017)) осталось и имеет преимущество. 129 | 130 | Постарался избавиться от хлопания экраном при перемотке. 131 | 132 | 0.1.1 Втр Янв 1 17:48:50 2019 +0300 133 | 134 | Добавлена поддержка X96 через исправления в ядре 4.9 для S905X2. 135 | 136 | Добавлена поддержка дробных частот развёртки (23.976, 29.97, 59.94 Гц). 137 | 138 | 0.1.0 Вск Авг 20 21:59:45 2017 +0300 139 | 140 | Первый публичный выпуск afrd в составе улучшенного ядра для Android TV 141 | приставки X92/S912. 142 | -------------------------------------------------------------------------------- /cfg_parse/cfg_parse.c: -------------------------------------------------------------------------------- 1 | /* config file parser */ 2 | /* greg kennedy 2012 */ 3 | 4 | #include "cfg_parse.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | /* Configuration list structures */ 13 | struct cfg_node 14 | { 15 | char *key; 16 | char *value; 17 | 18 | struct cfg_node *next; 19 | }; 20 | 21 | struct cfg_struct 22 | { 23 | struct cfg_node *head; 24 | }; 25 | 26 | /* Helper functions */ 27 | /* A malloc() wrapper which handles null return values */ 28 | static void *cfg_malloc(unsigned int size) 29 | { 30 | void *temp = malloc(size); 31 | if (temp == NULL) 32 | { 33 | fprintf(stderr,"CFG_PARSE ERROR: MALLOC(%u) returned NULL (errno=%d)\n",size,errno); 34 | exit(EXIT_FAILURE); 35 | } 36 | return temp; 37 | } 38 | 39 | /* Returns a duplicate of input str, without leading / trailing whitespace 40 | Input str *MUST* be null-terminated, or disaster will result */ 41 | static char *cfg_trim(const char *str) 42 | { 43 | char *tstr = NULL; 44 | char *temp = (char *)str; 45 | 46 | int temp_len; 47 | 48 | /* advance start pointer to first non-whitespace char */ 49 | while (*temp == ' ' || *temp == '\t' || *temp == '\n') 50 | temp ++; 51 | 52 | /* calculate length of output string, minus whitespace */ 53 | temp_len = strlen(temp); 54 | while (temp_len > 0 && (temp[temp_len-1] == ' ' || temp[temp_len-1] == '\t' || temp[temp_len-1] == '\n')) 55 | temp_len --; 56 | 57 | /* copy portion of string to new string */ 58 | tstr = (char *)malloc(temp_len + 1); 59 | tstr[temp_len] = '\0'; 60 | memcpy(tstr,temp,temp_len); 61 | 62 | return tstr; 63 | } 64 | 65 | /* Load into cfg from a file. Maximum line size is CFG_MAX_LINE-1 bytes... */ 66 | int cfg_load(struct cfg_struct *cfg, const char *filename) 67 | { 68 | char buffer[CFG_MAX_LINE], *delim; 69 | FILE *fp = fopen(filename, "r"); 70 | if (fp == NULL) return -1; 71 | 72 | while (!feof(fp)) 73 | { 74 | if (fgets(buffer,CFG_MAX_LINE,fp) != NULL) 75 | { 76 | /* locate first # sign and terminate string there (comment) */ 77 | delim = strchr(buffer, '#'); 78 | if (delim != NULL) *delim = '\0'; 79 | 80 | /* locate first = sign and prepare to split */ 81 | delim = strchr(buffer, '='); 82 | if (delim != NULL) 83 | { 84 | *delim = '\0'; 85 | delim ++; 86 | 87 | cfg_set(cfg,buffer,delim); 88 | } 89 | } 90 | } 91 | 92 | fclose(fp); 93 | return 0; 94 | } 95 | 96 | /* Save complete cfg to file */ 97 | int cfg_save(struct cfg_struct *cfg, const char *filename) 98 | { 99 | struct cfg_node *temp = cfg->head; 100 | 101 | FILE *fp = fopen(filename, "w"); 102 | if (fp == NULL) return -1; 103 | 104 | while (temp != NULL) 105 | { 106 | if (fprintf(fp,"%s=%s\n",temp->key,temp->value) < 0) { 107 | fclose(fp); 108 | return -2; 109 | } 110 | temp = temp->next; 111 | } 112 | fclose(fp); 113 | return 0; 114 | } 115 | 116 | /* Get option from cfg_struct */ 117 | const char * cfg_get(struct cfg_struct *cfg, const char *key) 118 | { 119 | struct cfg_node *temp = cfg->head; 120 | 121 | char *tkey = cfg_trim(key); 122 | 123 | while (temp != NULL) 124 | { 125 | if (strcmp(tkey, temp->key) == 0) 126 | { 127 | free(tkey); 128 | return temp->value; 129 | } 130 | temp = temp->next; 131 | } 132 | 133 | free(tkey); 134 | return NULL; 135 | } 136 | 137 | /* Set option in cfg_struct */ 138 | void cfg_set(struct cfg_struct *cfg, const char *key, const char *value) 139 | { 140 | char *tkey, *tvalue; 141 | 142 | struct cfg_node *temp = cfg->head; 143 | 144 | /* Trim key. */ 145 | tkey = cfg_trim(key); 146 | 147 | /* Exclude empty key */ 148 | if (strcmp(tkey,"") == 0) { free(tkey); return; } 149 | 150 | /* Trim value. */ 151 | tvalue = cfg_trim(value); 152 | 153 | /* Depending on implementation, you may wish to treat blank value 154 | as a "delete" operation */ 155 | /* if (strcmp(tvalue,"") == 0) { free(tvalue); free(tkey); cfg_delete(cfg,key); return; } */ 156 | 157 | /* search list for existing key */ 158 | while (temp != NULL) 159 | { 160 | if (strcmp(tkey, temp->key) == 0) 161 | { 162 | /* found a match: no longer need temp key */ 163 | free(tkey); 164 | 165 | /* update value */ 166 | free(temp->value); 167 | temp->value = tvalue; 168 | return; 169 | } 170 | temp = temp->next; 171 | } 172 | 173 | /* not found: create new element */ 174 | temp = (struct cfg_node *)cfg_malloc(sizeof(struct cfg_node)); 175 | 176 | /* assign key, value */ 177 | temp->key = tkey; 178 | temp->value = tvalue; 179 | 180 | /* prepend */ 181 | temp->next = cfg->head; 182 | cfg->head = temp; 183 | } 184 | 185 | /* Remove option in cfg_struct */ 186 | void cfg_delete(struct cfg_struct *cfg, const char *key) 187 | { 188 | struct cfg_node *temp = cfg->head, *temp2 = NULL; 189 | 190 | char *tkey = cfg_trim(key); 191 | 192 | /* search list for existing key */ 193 | while (temp != NULL) 194 | { 195 | if (strcmp(tkey, temp->key) == 0) 196 | { 197 | /* cleanup trimmed key */ 198 | free(tkey); 199 | 200 | if (temp2 == NULL) 201 | { 202 | /* first element */ 203 | cfg->head = temp->next; 204 | } else { 205 | /* splice out element */ 206 | temp2->next = temp->next; 207 | } 208 | 209 | /* delete element */ 210 | free(temp->value); 211 | free(temp->key); 212 | free(temp); 213 | 214 | return; 215 | } 216 | 217 | temp2 = temp; 218 | temp = temp->next; 219 | } 220 | 221 | /* not found */ 222 | /* cleanup trimmed key */ 223 | free(tkey); 224 | } 225 | 226 | /* Create a cfg_struct */ 227 | struct cfg_struct * cfg_init() 228 | { 229 | struct cfg_struct *temp; 230 | temp = (struct cfg_struct *)cfg_malloc(sizeof(struct cfg_struct)); 231 | temp->head = NULL; 232 | return temp; 233 | } 234 | 235 | /* Free a cfg_struct */ 236 | void cfg_free(struct cfg_struct *cfg) 237 | { 238 | struct cfg_node *temp = NULL, *temp2; 239 | temp = cfg->head; 240 | while (temp != NULL) 241 | { 242 | temp2 = temp->next; 243 | free(temp->key); 244 | free(temp->value); 245 | free(temp); 246 | temp = temp2; 247 | } 248 | free (cfg); 249 | } 250 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/Control.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd; 9 | 10 | import android.content.Context; 11 | import android.os.Build; 12 | import android.util.Log; 13 | 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.util.Arrays; 19 | 20 | import eu.chainfire.libsuperuser.Shell; 21 | import ru.cobra.zap.afrd.gui.R; 22 | 23 | /** 24 | * This class provides a low-level API for communicating with afrd, 25 | */ 26 | public class Control 27 | { 28 | private static final String AFRD_PID_FILE = "/dev/run/afrd.pid"; 29 | // this attribute exists on Android 6 & 7 30 | private static final String SYSFS_ANDROID67 = "/sys/class/switch/hdmi/state"; 31 | // this attribute exists on Android 8 & 9 32 | private static final String SYSFS_ANDROID89 = "/sys/class/switch/hdmi/cable.0/state"; 33 | // these attributes should exist on all but castrated by MINIX kernels 34 | private static final String SYSFS_NOT_MINIX_1 = "/sys/class/vdec/vdec_status"; 35 | private static final String SYSFS_NOT_MINIX_2 = "/sys/class/vdec/dump_vdec_blocks"; 36 | private static final String SYSFS_NOT_MINIX_3 = "/sys/class/vdec/dump_vdec_chunks"; 37 | 38 | public File mIni; 39 | public File mAfrd; 40 | 41 | public Control (Context ctx) 42 | { 43 | mIni = new File (ctx.getCacheDir (), "afrd.ini"); 44 | mAfrd = new File (ctx.getCacheDir (), "afrd"); 45 | } 46 | 47 | public void restart () 48 | { 49 | mAfrd.setExecutable (true); 50 | String[] cmd = new String[] { mAfrd.getPath () + " -k -D " + mIni.getPath () }; 51 | Log.d ("afrd", "Run: " + Arrays.toString (cmd)); 52 | Shell.run ("su", cmd, null, false); 53 | // give status another chance 54 | Status.mFail.success (); 55 | } 56 | 57 | public boolean isRunning () 58 | { 59 | try 60 | { 61 | FileInputStream fis = new FileInputStream (AFRD_PID_FILE); 62 | byte[] data = new byte[300]; 63 | int n = fis.read (data); 64 | fis.close (); 65 | 66 | while ((n > 0) && ((data[n - 1] < (byte) '0') || (data[n - 1] > (byte) '9'))) 67 | n--; 68 | 69 | if (n > 0) 70 | { 71 | int pid = Integer.parseInt (new String (data, 0, n)); 72 | fis = new FileInputStream ("/proc/" + pid + "/cmdline"); 73 | fis.read (data); 74 | fis.close (); 75 | 76 | if (mAfrd.getPath ().equals (jfun.cstr (data))) 77 | return true; 78 | 79 | Log.d ("afrd", "Cmdline: " + new String (data)); 80 | } 81 | } 82 | catch (Exception exc) 83 | { 84 | jfun.logExc ("isRunning", exc); 85 | } 86 | 87 | return false; 88 | } 89 | 90 | public String extractConfig (Context ctx) 91 | { 92 | String kver = System.getProperty ("os.version", ""); 93 | String hardware = jfun.getProperty ("ro.hardware"); 94 | String model = jfun.getProperty ("ro.product.model"); 95 | 96 | assert kver != null; 97 | assert hardware != null; 98 | assert model != null; 99 | 100 | int res_id; 101 | if (!hardware.equalsIgnoreCase ("amlogic")) 102 | return ctx.getString (R.string.only_amlogic, hardware); 103 | else if (kver.startsWith ("3.14.29") && 104 | !new File (SYSFS_NOT_MINIX_1).exists () && 105 | !new File (SYSFS_NOT_MINIX_2).exists () && 106 | !new File (SYSFS_NOT_MINIX_3).exists ()) 107 | res_id = R.raw.afrd_minix7; 108 | else if (kver.startsWith ("4.") && new File (SYSFS_ANDROID89).exists ()) 109 | // Android 8 or 9 110 | res_id = R.raw.afrd_8; 111 | else if (kver.startsWith ("3.") && new File (SYSFS_ANDROID67).exists ()) 112 | // Android 6 or 7 113 | res_id = R.raw.afrd_7; 114 | else 115 | return ctx.getString (R.string.failed_detect_ostype, kver, hardware, model); 116 | 117 | if (!jfun.extractFile (ctx, res_id, mIni)) 118 | return ctx.getString (R.string.failed_copy_raw, mIni.getPath ()); 119 | 120 | return ""; 121 | } 122 | 123 | public String extractDaemon (Context ctx) 124 | { 125 | for (String arch : Build.SUPPORTED_ABIS) 126 | { 127 | int res_id; 128 | switch (arch) 129 | { 130 | case "armeabi-v7a": 131 | res_id = R.raw.afrd_armeabi_v7a; 132 | break; 133 | 134 | case "arm64-v8a": 135 | res_id = R.raw.afrd_arm64_v8a; 136 | break; 137 | 138 | default: 139 | continue; 140 | } 141 | 142 | for (int i = 0; ; i++) 143 | { 144 | if (jfun.extractFile (ctx, res_id, mAfrd)) 145 | break; 146 | 147 | switch (i) 148 | { 149 | case 0: 150 | String[] cmd = new String[] { mAfrd.getPath () + " -k", "rm -f " + mAfrd.getPath () }; 151 | Log.d ("afrd", "Run: " + Arrays.toString (cmd)); 152 | Shell.run ("su", cmd, null, false); 153 | break; 154 | 155 | case 1: 156 | return ctx.getString (R.string.failed_copy_raw, mAfrd.getPath ()); 157 | } 158 | } 159 | 160 | return ""; 161 | } 162 | 163 | return ctx.getString (R.string.arch_not_supported, Arrays.toString (Build.SUPPORTED_ABIS)); 164 | } 165 | 166 | /** 167 | * Touch config file so that afrd reloads it in a few seconds. 168 | */ 169 | public void reload () 170 | { 171 | try 172 | { 173 | long now = System.currentTimeMillis (); 174 | if (!mIni.exists ()) 175 | new FileOutputStream (mIni).close (); 176 | else 177 | mIni.setLastModified (now); 178 | } 179 | catch (IOException exc) 180 | { 181 | jfun.logExc ("reload", exc); 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /android/app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 10 | 13 | 16 | 20 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 51 | 53 | 57 | 60 | 64 | 68 | 72 | 76 | 77 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/LogFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.app.Fragment; 11 | import android.content.Context; 12 | import android.os.Bundle; 13 | import android.os.Handler; 14 | import android.text.method.ScrollingMovementMethod; 15 | import android.util.Log; 16 | import android.view.LayoutInflater; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.TextView; 20 | 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import java.io.IOException; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Arrays; 26 | 27 | import eu.chainfire.libsuperuser.Shell; 28 | import ru.cobra.zap.afrd.Control; 29 | 30 | public class LogFragment extends Fragment 31 | { 32 | private static final String ARG_LOGFN = "log.fn"; 33 | private static final String ARG_LOG_ENABLED = "log.enabled"; 34 | 35 | private boolean mAttached = false; 36 | private Handler mTimer = new Handler (); 37 | private boolean mEnabled; 38 | private File mLog; 39 | private FileInputStream mLogStream; 40 | private int mLogSize; 41 | private TextView mTextLog; 42 | private TextView mTitle; 43 | 44 | /** 45 | * Create a log viewer fragment. 46 | * 47 | * @param logfn Parameter 1. 48 | * @param enabled whenever logging is enabled in daemon 49 | * @return A new instance of LogFragment. 50 | */ 51 | public static LogFragment create (String logfn, boolean enabled) 52 | { 53 | if (logfn == null) 54 | return null; 55 | 56 | LogFragment fragment = new LogFragment (); 57 | Bundle args = new Bundle (); 58 | args.putString (ARG_LOGFN, logfn); 59 | args.putBoolean (ARG_LOG_ENABLED, enabled); 60 | fragment.setArguments (args); 61 | return fragment; 62 | } 63 | 64 | @Override 65 | public void onCreate (Bundle savedInstanceState) 66 | { 67 | super.onCreate (savedInstanceState); 68 | if (savedInstanceState == null) 69 | savedInstanceState = getArguments (); 70 | if (savedInstanceState != null) 71 | { 72 | String fn = savedInstanceState.getString (ARG_LOGFN); 73 | if (fn != null) 74 | mLog = new File (fn); 75 | mEnabled = savedInstanceState.getBoolean (ARG_LOG_ENABLED); 76 | } 77 | } 78 | 79 | @Override 80 | public void onAttach (Context context) 81 | { 82 | super.onAttach (context); 83 | mAttached = true; 84 | } 85 | 86 | @Override 87 | public void onDetach () 88 | { 89 | super.onDetach (); 90 | mAttached = false; 91 | close (); 92 | } 93 | 94 | @Override 95 | public View onCreateView (LayoutInflater inflater, ViewGroup container, 96 | Bundle savedInstanceState) 97 | { 98 | // Inflate the layout for this fragment 99 | View root = inflater.inflate (R.layout.fragment_log, container, false); 100 | mTextLog = root.findViewById (R.id.textLog); 101 | mTextLog.setMovementMethod (new ScrollingMovementMethod ()); 102 | mTitle = root.findViewById (R.id.logTitle); 103 | 104 | mTimer.post (new Runnable () 105 | { 106 | @Override 107 | public void run () 108 | { 109 | refresh (); 110 | mTimer.postDelayed (this, 500); 111 | } 112 | }); 113 | 114 | View logClear = root.findViewById (R.id.buttonLogClear); 115 | if (logClear != null) 116 | logClear.setOnClickListener (new View.OnClickListener () 117 | { 118 | @Override 119 | public void onClick (View v) 120 | { 121 | logClear (); 122 | } 123 | }); 124 | 125 | return root; 126 | } 127 | 128 | @Override 129 | public void onDestroyView () 130 | { 131 | super.onDestroyView (); 132 | mTextLog = null; 133 | close (); 134 | } 135 | 136 | private boolean open () 137 | { 138 | close (); 139 | 140 | if (!mAttached || (mTextLog == null)) 141 | return false; 142 | 143 | mTitle.setText ( 144 | String.format (getString (R.string.log_title), 145 | mLog.getPath (), mEnabled ? "" : getString (R.string.log_is_off))); 146 | 147 | try 148 | { 149 | mLogStream = new FileInputStream (mLog); 150 | mTextLog.setText (""); 151 | mLogSize = 0; 152 | return true; 153 | } 154 | catch (IOException ignored) 155 | { 156 | } 157 | 158 | mLogStream = null; 159 | mTextLog.setText (R.string.error_opening_log); 160 | return false; 161 | } 162 | 163 | private void close () 164 | { 165 | try 166 | { 167 | if (mLogStream != null) 168 | { 169 | mLogStream.close (); 170 | mLogStream = null; 171 | } 172 | } 173 | catch (IOException ignored) 174 | { 175 | } 176 | 177 | if (mTextLog != null) 178 | mTextLog.setText (""); 179 | } 180 | 181 | 182 | private void refresh () 183 | { 184 | if (!mAttached || mTextLog == null) 185 | return; 186 | 187 | if (mLogStream == null) 188 | if (!open ()) 189 | return; 190 | 191 | try 192 | { 193 | int size = (int) mLog.length (); 194 | if (size < mLogSize) 195 | if (!open ()) 196 | return; 197 | 198 | int avail = size - mLogSize; 199 | if (avail <= 0) 200 | return; 201 | 202 | mLogSize = size; 203 | 204 | byte[] data = new byte[avail]; 205 | mLogStream.read (data); 206 | String str = new String (data, StandardCharsets.UTF_8); 207 | mTextLog.append (str); 208 | } 209 | catch (IOException ignored) 210 | { 211 | close (); 212 | } 213 | } 214 | 215 | private void logClear () 216 | { 217 | close (); 218 | 219 | String[] cmd = new String [] { "rm -f '" + mLog.getPath ().replace ("'", "'\"'\"'") + "'" }; 220 | Log.d ("afrd", "Run: " + Arrays.toString (cmd)); 221 | Shell.run ("su", cmd, null, false); 222 | 223 | Control ctl = new Control (getActivity ()); 224 | ctl.reload (); 225 | 226 | refresh (); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd; 9 | 10 | import android.content.res.Resources; 11 | import android.util.Log; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.RandomAccessFile; 16 | import java.nio.ByteBuffer; 17 | import java.nio.ByteOrder; 18 | import java.nio.MappedByteBuffer; 19 | import java.nio.channels.FileChannel; 20 | import java.util.Locale; 21 | import java.util.zip.CRC32; 22 | 23 | import ru.cobra.zap.afrd.gui.R; 24 | 25 | /** 26 | * This class provides afrd status info. 27 | */ 28 | public class Status 29 | { 30 | private static final String AFRD_SHM = "/dev/run/afrd.ipc"; 31 | 32 | private File mShmFile = new File (AFRD_SHM); 33 | private RandomAccessFile mShmRAFile; 34 | private MappedByteBuffer mShm; 35 | private int mShmSize; 36 | private int mLastStamp; 37 | private boolean mLastStampValid = false; 38 | 39 | /// true if afrd is enabled 40 | public boolean mEnabled; 41 | /// true if current screen refresh rate is different from default 42 | public boolean mModified; 43 | /// true if screen is blackened while detecting movie frame rate 44 | public boolean mBlackened; 45 | /// afrd version high number 46 | public int mVersionHi; 47 | /// afrd version low number 48 | public int mVersionLo; 49 | /// afrd revision number 50 | public int mVersionRev; 51 | /// afrd version suffix 52 | public String mVersionSfx; 53 | /// Full version name 54 | public String mVersion; 55 | /// afrd build date 56 | public String mBuildDate; 57 | /// current screen refresh rate, .8 fixed-point 58 | public int mCurrentHz; 59 | /// original screen refresh rate, .8 fixed-point 60 | public int mOriginalHz; 61 | /// Failure detector to stop querying afrd status 62 | public static FailureDetector mFail = new FailureDetector ("daemon status"); 63 | 64 | /** 65 | * Check if IPC channel has been successfully opened 66 | * 67 | * @return true if it was 68 | */ 69 | public boolean ok () 70 | { 71 | return (mShm != null); 72 | } 73 | 74 | /** 75 | * Open the IPC channel 76 | * 77 | * @return true on success 78 | */ 79 | public boolean open () 80 | { 81 | if (mFail.giveUp ()) 82 | return false; 83 | 84 | close (); 85 | 86 | try 87 | { 88 | mShmRAFile = new RandomAccessFile (AFRD_SHM, "r"); 89 | mShmSize = (int) mShmRAFile.length (); 90 | mShm = mShmRAFile.getChannel ().map (FileChannel.MapMode.READ_ONLY, 0, mShmSize); 91 | mShm.order (ByteOrder.LITTLE_ENDIAN); 92 | return true; 93 | } 94 | catch (IOException exc) 95 | { 96 | jfun.logExc ("ipc open", exc); 97 | 98 | Throwable cause = exc.getCause (); 99 | if ((cause != null) && 100 | (cause.getMessage ().contains ("EACCES"))) 101 | { 102 | Log.e ("afrd", "It seems like SELinux is enabled, giving up"); 103 | mFail.fatal (); 104 | } 105 | else 106 | { 107 | mFail.failure (); 108 | } 109 | 110 | return false; 111 | } 112 | } 113 | 114 | /** 115 | * Close the IPC channel 116 | */ 117 | public void close () 118 | { 119 | mLastStampValid = false; 120 | mShm = null; 121 | if (mShmRAFile == null) 122 | return; 123 | 124 | try 125 | { 126 | mShmRAFile.close (); 127 | } 128 | catch (IOException exc) 129 | { 130 | jfun.logExc ("ipc close", exc); 131 | } 132 | mShmRAFile = null; 133 | } 134 | 135 | /** 136 | * Refresh afrd state using the IPC channel 137 | * 138 | * @return false if nothing has changed, true if new state has been read 139 | */ 140 | public boolean refresh () 141 | { 142 | if (!checkShm () || mFail.giveUp ()) 143 | return false; 144 | 145 | // check if buffer changed since our last update 146 | int stamp = mShm.getInt (0); 147 | if (mLastStampValid && (mLastStamp == stamp)) 148 | return false; 149 | 150 | short size = mShm.getShort (4); 151 | if (size != mShmSize) 152 | { 153 | close (); 154 | // to minimize delays, try immediately to re-open ipc 155 | if (!open ()) 156 | return false; 157 | size = mShm.getShort (4); 158 | if (size != mShmSize) 159 | { 160 | close (); 161 | return false; 162 | } 163 | } 164 | 165 | // make a replica of the shared data 166 | byte[] data = new byte[size]; 167 | for (int i = 0; i < size; i++) 168 | data[i] = mShm.get (i); 169 | ByteBuffer buff = ByteBuffer.wrap (data); 170 | buff.order (ByteOrder.LITTLE_ENDIAN); 171 | 172 | // now check if buffer is consistent 173 | stamp = buff.getInt (0); 174 | int stamp2 = buff.getInt (size - 4); 175 | if (stamp != stamp2) 176 | return false; 177 | 178 | CRC32 crc = new CRC32 (); 179 | crc.update (data, 4, size - 4 * 2); 180 | int crcval = (int) crc.getValue (); 181 | if (stamp != crcval) 182 | return false; 183 | 184 | mLastStamp = stamp; 185 | mLastStampValid = true; 186 | 187 | mEnabled = (buff.get (6) != 0); 188 | mModified = (buff.get (7) != 0); 189 | mBlackened = (buff.get (8) != 0); 190 | mVersionHi = buff.get (9); 191 | mVersionLo = buff.get (10); 192 | mVersionRev = buff.get (11); 193 | mBuildDate = jfun.cstr (data, 12, 24); 194 | mCurrentHz = buff.getInt (36); 195 | mOriginalHz = buff.getInt (40); 196 | if (mShmSize >= 44+8) 197 | mVersionSfx = jfun.cstr (data, 44, 8); 198 | else 199 | mVersionSfx = ""; 200 | 201 | mVersion = String.format (Locale.getDefault (), 202 | "%d.%d.%d%s", mVersionHi, mVersionLo, mVersionRev, mVersionSfx); 203 | 204 | return true; 205 | } 206 | 207 | private boolean checkShm () 208 | { 209 | if (!ok ()) 210 | return false; 211 | 212 | if (mShmFile.exists ()) 213 | return true; 214 | 215 | close (); 216 | return false; 217 | } 218 | 219 | public static String hz2str (Resources res, int hz) 220 | { 221 | return res.getString (R.string.hz, 222 | hz >> 8, ((hz & 0xff) * 100 + 128) >> 8); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | 16 | 17 | 20 | 21 | 25 | 26 | 28 | 32 | 36 | 37 | 39 | 43 | 47 | 48 | 50 | 54 | 58 | 59 | 61 | 65 | 69 | 70 | 72 | 76 | 80 | 81 | 83 | 87 | 91 | 92 | 94 | 98 | 102 | 103 | -------------------------------------------------------------------------------- /modes.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | * 7 | * Video mode handling & switching 8 | */ 9 | 10 | #include "afrd.h" 11 | #include "colorspace.h" 12 | #include 13 | 14 | display_mode_t *g_modes = NULL; 15 | int g_modes_n = 0; 16 | display_mode_t g_current_mode; 17 | bool g_blackened = false; 18 | 19 | static bool mode_parse (char *desc, display_mode_t *mode) 20 | { 21 | memset (mode, 0, sizeof (display_mode_t)); 22 | if (!desc) 23 | return false; 24 | 25 | strncpy (mode->name, desc, sizeof (mode->name)); 26 | 27 | if (strncmp (desc, "smpte", 5) == 0) { 28 | mode->width = 4096; 29 | mode->height = 2160; 30 | desc += 5; 31 | } else { 32 | int v = parse_int (&desc); 33 | char c = *desc; 34 | if (c == 'x') { 35 | mode->width = v; 36 | mode->height = parse_int (&desc); 37 | } else { 38 | switch ((mode->height = v)) 39 | { 40 | case 480: mode->width = 640; break; 41 | case 576: mode->width = 720; break; 42 | case 720: mode->width = 1280; break; 43 | case 1080: mode->width = 1920; break; 44 | case 2160: mode->width = 3840; break; 45 | default: mode->name [0] = 0; return false; 46 | } 47 | } 48 | 49 | c = *desc++; 50 | // according to kernel sources, 'fp' means same as 'p' 51 | if (c == 'f') 52 | c = *desc++; 53 | if (c == 'i') 54 | mode->interlaced = true; 55 | else if (c == 'p') 56 | mode->interlaced = false; 57 | else { 58 | mode->name [0] = 0; 59 | return false; 60 | } 61 | } 62 | 63 | mode->framerate = parse_int (&desc); 64 | 65 | // here follows 'hz' optionally followed by color space like '420'. 66 | // we ignore them. 67 | 68 | return true; 69 | } 70 | 71 | void display_mode_add (display_mode_t *mode) 72 | { 73 | /* keep only non-fractional modes in list */ 74 | display_mode_t new_mode = *mode; 75 | new_mode.fractional = false; 76 | 77 | /* check if mode is already in list */ 78 | for (int i = 0; i < g_modes_n; i++) 79 | if (display_mode_equal (&new_mode, &g_modes [i])) 80 | return; 81 | 82 | g_modes_n++; 83 | g_modes = (display_mode_t *)realloc (g_modes, sizeof (display_mode_t) * g_modes_n); 84 | g_modes [g_modes_n - 1] = new_mode; 85 | 86 | trace (2, "\t+ "DISPMODE_FMT"\n", DISPMODE_ARGS (new_mode, display_mode_hz (&new_mode))); 87 | } 88 | 89 | int display_modes_init () 90 | { 91 | display_modes_fini (); 92 | 93 | char *modes = sysfs_get_str (g_hdmi_dev, "disp_cap"); 94 | if (!modes) 95 | return -1; 96 | 97 | trace (2, "Parsing supported video modes\n"); 98 | 99 | // parse the list of video modes supported by display 100 | char *cur = modes; 101 | while (cur && *cur) { 102 | cur += strspn (cur, spaces); 103 | int mode_len = strcspn (cur, spaces); 104 | if (!mode_len) 105 | break; 106 | 107 | if (cur [mode_len - 1] == '*') 108 | mode_len--; 109 | cur [mode_len] = 0; 110 | 111 | display_mode_t mode; 112 | if (mode_parse (cur, &mode)) 113 | display_mode_add (&mode); 114 | else 115 | trace (2, "\t%s: unrecognized mode\n", cur); 116 | 117 | cur += mode_len + 1; 118 | } 119 | 120 | free (modes); 121 | 122 | display_mode_get_current (); 123 | 124 | // on some weird configs current video mode may not be listed in disp_cap 125 | if (g_current_mode.name [0]) 126 | display_mode_add (&g_current_mode); 127 | 128 | // add extra user-specified modes from config 129 | strlist_t xmodes; 130 | if (strlist_load (&xmodes, "mode.extra", "extra video modes")) { 131 | for (int i = 0; i < xmodes.size; i++) { 132 | display_mode_t mode; 133 | if (mode_parse (xmodes.data [i], &mode)) 134 | display_mode_add (&mode); 135 | } 136 | strlist_free (&xmodes); 137 | } 138 | 139 | return 0; 140 | } 141 | 142 | void display_mode_get_current () 143 | { 144 | // parse the current video mode 145 | char *mode = sysfs_get_str (g_mode_path, NULL); 146 | if (!mode || !strcmp (mode, "null")) { 147 | trace (1, "Current video mode is null!\n"); 148 | return; 149 | } 150 | 151 | if (!mode_parse (mode, &g_current_mode)) { 152 | trace (1, "Failed to recognize current video mode '%s'\n", mode); 153 | free (mode); 154 | return; 155 | } 156 | 157 | g_current_mode.fractional = false; 158 | char *frac_rate = sysfs_get_str (g_hdmi_dev, "frac_rate_policy"); 159 | if (!frac_rate) 160 | trace (1, "failed to read frac_rate_policy!\n"); 161 | else 162 | { 163 | g_current_mode.fractional = strtol (frac_rate, NULL, 10) != 0; 164 | free (frac_rate); 165 | } 166 | } 167 | 168 | void display_modes_fini () 169 | { 170 | free (g_modes); 171 | 172 | g_modes = NULL; 173 | g_modes_n = 0; 174 | } 175 | 176 | bool display_mode_equal (display_mode_t *mode1, display_mode_t *mode2) 177 | { 178 | if ((mode1->width != mode2->width) || 179 | (mode1->height != mode2->height) || 180 | (mode1->interlaced != mode2->interlaced)) 181 | return false; 182 | 183 | int hz1 = display_mode_hz (mode1); 184 | int hz2 = display_mode_hz (mode2); 185 | return (hz1 == hz2); 186 | } 187 | 188 | int display_mode_hz (display_mode_t *mode) 189 | { 190 | if (mode->fractional) 191 | switch (mode->framerate) { 192 | case 24: return ( 2997 * 256 + 62) / 125; 193 | case 30: return ( 2997 * 256 + 50) / 100; 194 | case 60: return ( 5994 * 256 + 50) / 100; 195 | case 120: return (11988 * 256 + 50) / 100; 196 | case 240: return (23976 * 256 + 50) / 100; 197 | } 198 | 199 | return mode->framerate * 256; 200 | } 201 | 202 | void display_mode_set_hz (display_mode_t *mode, int hz) 203 | { 204 | mode->fractional = true; 205 | int hz_frac = display_mode_hz (mode); 206 | int hz_int = mode->framerate * 256; 207 | 208 | if (hz_frac == hz_int) { 209 | // this mode has no fractional variant 210 | mode->fractional = false; 211 | return; 212 | } 213 | 214 | // find multiple of hz closest to hz_int 215 | int hz_n = 1; 216 | int best_hz = hz; 217 | int best_diff = abs (hz - hz_int); 218 | for (;;) { 219 | hz_n++; 220 | int multiple_hz = hz * hz_n; 221 | int multiple_diff = abs (multiple_hz - hz_int); 222 | if (multiple_diff > best_diff) 223 | break; 224 | best_hz = multiple_hz; 225 | best_diff = multiple_diff; 226 | } 227 | 228 | if (abs (hz_int - best_hz) < abs (hz_frac - best_hz)) 229 | mode->fractional = false; 230 | } 231 | 232 | void display_mode_switch (display_mode_t *mode, bool force) 233 | { 234 | if (!g_blackened && !force && 235 | display_mode_equal (mode, &g_current_mode)) { 236 | trace (1, "Display mode is already "DISPMODE_FMT"\n", 237 | DISPMODE_ARGS (*mode, display_mode_hz (mode))); 238 | return; 239 | } 240 | 241 | char frac [2] = { mode->fractional ? '1' : '0', 0 }; 242 | sysfs_set_str (g_hdmi_dev, "frac_rate_policy", frac); 243 | 244 | colorspace_apply (mode->name); 245 | 246 | // fractional mode transition via special null mode 247 | if (force || 248 | ((strcmp (mode->name, g_current_mode.name) == 0) && 249 | (mode->fractional != g_current_mode.fractional))) 250 | display_mode_null (); 251 | 252 | trace (1, "Switching display mode to "DISPMODE_FMT"\n", 253 | DISPMODE_ARGS (*mode, display_mode_hz (mode))); 254 | 255 | sysfs_write (g_mode_path, mode->name); 256 | g_current_mode = *mode; 257 | g_blackened = false; 258 | 259 | hdcp_restore (false); 260 | } 261 | 262 | void display_mode_null () 263 | { 264 | if (g_blackened) 265 | return; 266 | 267 | trace (2, "Blackout screen\n"); 268 | sysfs_write (g_mode_path, "null"); 269 | g_blackened = true; 270 | } 271 | -------------------------------------------------------------------------------- /afrd_tv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 46 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 75 | 81 | 87 | 92 | 97 | 104 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /android/app/src/main/java/ru/cobra/zap/afrd/gui/AFRService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Automatic Framerate Daemon for AMLogic S905/S912-based boxes. 3 | * Copyright (C) 2017-2019 Andrey Zabolotnyi 4 | * 5 | * For copying conditions, see file COPYING.txt. 6 | */ 7 | 8 | package ru.cobra.zap.afrd.gui; 9 | 10 | import android.app.Notification; 11 | import android.app.NotificationChannel; 12 | import android.app.NotificationManager; 13 | import android.app.PendingIntent; 14 | import android.app.Service; 15 | import android.content.Context; 16 | import android.content.Intent; 17 | import android.content.SharedPreferences; 18 | import android.graphics.drawable.Icon; 19 | import android.os.Build; 20 | import android.os.Handler; 21 | import android.os.IBinder; 22 | import android.util.Log; 23 | import android.widget.Toast; 24 | 25 | import java.util.Locale; 26 | 27 | import ru.cobra.zap.afrd.Control; 28 | import ru.cobra.zap.afrd.Status; 29 | 30 | public class AFRService extends Service 31 | { 32 | public static final int NOTIF_STATUS_ID = 1; 33 | public static final String NOTIF_CHANNEL = "AFRd"; 34 | private Handler mTimer = new Handler (); 35 | private Handler mHzTimer = new Handler (); 36 | private Control mControl; 37 | private Status mStatus = new Status (); 38 | private int mOldRefreshRate = -1; 39 | private boolean mOldBlackScreen = false; 40 | private SharedPreferences mOptions; 41 | private int mNotificationMask = -1; 42 | private boolean mFirstRun = true; 43 | 44 | @Override 45 | public void onCreate () 46 | { 47 | super.onCreate (); 48 | 49 | mControl = new Control (this); 50 | 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 52 | { 53 | NotificationManager notificationManager = 54 | (NotificationManager) getSystemService (Context.NOTIFICATION_SERVICE); 55 | if (notificationManager != null) 56 | { 57 | NotificationChannel notificationChannel = new NotificationChannel ( 58 | NOTIF_CHANNEL, getString (R.string.notif_channel), NotificationManager.IMPORTANCE_MIN); 59 | notificationManager.createNotificationChannel (notificationChannel); 60 | } 61 | } 62 | 63 | mOptions = getSharedPreferences ("ini_options", 0); 64 | updateAll (); 65 | 66 | mTimer.post (new Runnable () 67 | { 68 | @Override 69 | public void run () 70 | { 71 | updateAll (); 72 | mTimer.postDelayed (this, 3000); 73 | } 74 | }); 75 | } 76 | 77 | private void updateAll () 78 | { 79 | if (!mStatus.ok ()) 80 | mStatus.open (); 81 | boolean changed = mStatus.refresh (); 82 | 83 | checkDaemon (); 84 | checkToast (); 85 | updateNotification (changed); 86 | } 87 | 88 | private void checkToast () 89 | { 90 | if (!mStatus.ok ()) 91 | return; 92 | 93 | if ((mOldRefreshRate == mStatus.mCurrentHz) && (mOldBlackScreen == mStatus.mBlackened)) 94 | return; 95 | 96 | // skip the first toast as it means afrd was launched 97 | boolean firstTime = (mOldRefreshRate == -1); 98 | mOldRefreshRate = mStatus.mCurrentHz; 99 | mOldBlackScreen = mStatus.mBlackened; 100 | 101 | if (firstTime || 102 | mStatus.mBlackened || 103 | !mOptions.getBoolean ("toast_hz", true)) 104 | return; 105 | 106 | mHzTimer.postDelayed (new Runnable () 107 | { 108 | final int current_hz = mStatus.mCurrentHz; 109 | 110 | @Override 111 | public void run () 112 | { 113 | Toast.makeText (getApplicationContext (), 114 | Status.hz2str (getResources (), current_hz), 115 | Toast.LENGTH_LONG).show (); 116 | } 117 | }, 1000); 118 | } 119 | 120 | @SuppressWarnings ("deprecation") 121 | private void updateNotification (boolean changed) 122 | { 123 | // Compute a mask of what's going to be displayed 124 | int nm = 0; 125 | 126 | if (mStatus.ok ()) 127 | { 128 | nm = 1; 129 | if (mStatus.mEnabled) 130 | nm |= 2; 131 | 132 | nm ^= (mStatus.mCurrentHz << 8) ^ (mStatus.mOriginalHz << 8); 133 | } 134 | 135 | // If nothing changed, don't update the notification 136 | if ((mNotificationMask == nm) && !changed) 137 | return; 138 | 139 | mNotificationMask = nm; 140 | 141 | // When user presses the notification, the main activity shows up 142 | Intent runApp = new Intent (this, MainActivity.class); 143 | runApp.setFlags (Intent.FLAG_ACTIVITY_SINGLE_TOP); 144 | 145 | Notification.Builder nbuilder; 146 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 147 | nbuilder = new Notification.Builder (this, NOTIF_CHANNEL); 148 | else 149 | nbuilder = new Notification.Builder (this); 150 | 151 | nbuilder 152 | .setSmallIcon (R.drawable.ic_afrd) 153 | .setLargeIcon (Icon.createWithResource (this, R.drawable.ic_afrd)) 154 | .setContentIntent (PendingIntent.getActivity (this, 0, runApp, 0)) 155 | .setOngoing (true) 156 | .setOnlyAlertOnce (true) 157 | .setLocalOnly (true) 158 | .setCategory (Notification.CATEGORY_SERVICE); 159 | 160 | if (mStatus.ok ()) 161 | { 162 | Intent startStop = new Intent (mStatus.mEnabled ? 163 | ActionActivity.INTENT_STOP : ActionActivity.INTENT_START); 164 | startStop.setFlags (Intent.FLAG_ACTIVITY_NO_HISTORY | 165 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | 166 | Intent.FLAG_ACTIVITY_NO_ANIMATION); 167 | 168 | Notification.Action action = new Notification.Action.Builder ( 169 | Icon.createWithResource (this, mStatus.mEnabled ? R.drawable.ic_stop : R.drawable.ic_play), 170 | getString (mStatus.mEnabled ? R.string.notif_stop_afrd : R.string.notif_start_afrd), 171 | PendingIntent.getActivity (this, 0, startStop, 0)).build (); 172 | nbuilder 173 | .addAction (action) 174 | .setContentText (getResources ().getString (R.string.notif_hz, 175 | Status.hz2str (getResources (), mStatus.mCurrentHz), 176 | Status.hz2str (getResources (), mStatus.mOriginalHz))) 177 | .setContentTitle (getString (mStatus.mEnabled ? 178 | R.string.notif_enabled : R.string.notif_disabled)); 179 | } 180 | else 181 | nbuilder 182 | .setContentTitle (getString (R.string.notif_notrunning)); 183 | 184 | startForeground (NOTIF_STATUS_ID, nbuilder.build ()); 185 | } 186 | 187 | @Override 188 | public IBinder onBind (Intent intent) 189 | { 190 | // TODO: Return the communication channel to the service. 191 | throw new UnsupportedOperationException ("Not yet implemented"); 192 | } 193 | 194 | private void checkDaemon () 195 | { 196 | if (mStatus.ok ()) 197 | { 198 | if (mStatus.mVersion.equals (BuildConfig.VERSION_NAME)) 199 | return; 200 | 201 | Log.i ("afrd", String.format (Locale.getDefault (), 202 | "Restarting daemon because of wrong version (%s, expected %s)", 203 | mStatus.mVersion, BuildConfig.VERSION_NAME)); 204 | } 205 | else 206 | { 207 | // wait until failure detector triggers, then restart daemon 208 | if (!Status.mFail.giveUp () && !mFirstRun) 209 | return; 210 | 211 | if (!mFirstRun) 212 | Log.i ("afrd", "(re-)Starting afrd because failed to get daemon status"); 213 | } 214 | 215 | mFirstRun = false; 216 | mStatus.close (); 217 | mControl.restart (); 218 | } 219 | } -------------------------------------------------------------------------------- /FAQ-en.txt: -------------------------------------------------------------------------------- 1 | ? What is Automatic Frame Rate (AFR)? 2 | ? Do I really need this program? 3 | 4 | ! Motion video is always filmed at certain frame rate. 5 | ! Traditional movies are shot at 24 frames per second, TV broadcasting 6 | ! uses 30/60 (NTSC standard) or 25/50 (PAL and SECAM standards) frames 7 | ! per second. 8 | 9 | ! TV will display content at a certain frame rate, which is called 10 | ! "vertical refresh rate". Most TVs and displays can work at different 11 | ! refresh rates. 12 | 13 | ! When movie framerate is not a multiple of refresh rate, the picture 14 | ! will play perceptibly non-evenly, which is sometimes referred as 15 | ! "judder". This is especially perceived on scenes where camera 16 | ! slowly moves sideways (panning). For example, when playing a 24 17 | ! frames per second video on a TV which uses a 60 Hz refresh rate, 18 | ! the order of frames displayed on TV will be: 19 | 20 | ! 0 0 0 1 1 2 2 2 3 3 4 4 4 5 5 6 6 6 7 7 ... 21 | 22 | ! As you can see, frames are displayed for a different period of time: 23 | ! even frames are displayed three times (3/60 seconds) while odd frames 24 | ! are displayed just two times (2/60 seconds). This is what makes you 25 | ! feel uncomfortable while watching such content. 26 | 27 | ! AFR is the process of harmonization between video frame rate and 28 | ! vertical refresh rate. This doesn't always mean that refresh rate 29 | ! must be set exactly to video frame rate; it is only important that 30 | ! refresh rate must be a multiple of frame rate. For example, it is 31 | ! perfectly acceptable to watch 30 frames-per-second content on a 32 | ! 60Hz refresh rate display, because every frame will be displayed 33 | ! two times (2/60 seconds), which is same as 1/30 seconds you would 34 | ! get if refresh rate would be 30Hz. 35 | 36 | ? Should I disable "system" auto framerate when using AFRd? 37 | ? Many video players (KODI/SPMC, ViMu, Smart Youtube etc) have a option 38 | ? to adjust TV refresh rate according to video. Should I enable it? 39 | 40 | ! First of all, on most AmLogic-based devices these option do nothing. 41 | ! In most cases it is broken - either in kernel, or in Android itself. 42 | ! In some cases this option requires special support in Android API, 43 | ! which is non-existent in most cases on AmLogic devices (except on 44 | ! some Beelink and Minix boxes, maybe some others). 45 | 46 | ! Second, no matter if it works, you must disable it - especially 47 | ! if it really works, like the "framerate switcher" addon for KODI/SPMC. 48 | ! The reason is that two working AFRs will always conflict. 49 | 50 | ! In the better case you'll get longer "black screens" during refresh 51 | ! rate switches, and in worst case you can even get "black screen forever" 52 | ! until reboot. 53 | 54 | ! That's why I always insist on disabling all other AFR subsystems 55 | ! if you're using AFRd. 56 | 57 | ? I still see "judder" in videos. Why? 58 | ? Despite the fact that AFRd correctly switches refresh rate, I still 59 | ? see frame judder on playback. 60 | 61 | ! AFRd is not a magic pill, it will just switch display mode to one 62 | ! that is most suitable for current movie. 63 | 64 | ! Everything else depends on a lot of other factors - the quality of 65 | ! video stream, the quality of the hardware decoder, the program 66 | ! for playing video, the setup of your TV. 67 | 68 | ? Green-ish/purple-ish image during playback 69 | ? Sometimes during playback image becomes "greenish" (sometimes: 70 | ? "purplish"). What do I do? 71 | 72 | ! The usual reason for these faults are loose connections on 73 | ! HDMI bus between the TV box and the TV. 74 | 75 | ! Try replacing the HDMI cable with something of a better quality: 76 | ! less flexible are better (although less handy), ferrite rings 77 | ! are a bonus (bulges near the ends of the cable). 78 | 79 | ! Also try inserting the cable into a different HDMI jack, if it 80 | ! is possible. 81 | 82 | ? Black screen on refresh rate switch 83 | ? Sometimes after refresh rate switching screen becomes black and 84 | ? won't restore (sometimes: will restore after video stops, or: 85 | ? restores after I reboot the box). 86 | 87 | ! This is your TV fault. Refresh rate switching is a stress situation 88 | ! for a TV - it has to determine all the parameters of the incoming 89 | ! video signal. Sometimes it fails to do it correctly, and it just 90 | ! 'hangs', although this depends from model to model. 91 | 92 | ! Some people assert that this does not happen if you switch HDMI bus 93 | ! to RGB format (normally video signal is encoded in the YUV format). 94 | 95 | ? How I can switch the HDMI bus to RGB format? 96 | ? I heard this can help with "greenish image" / "black screens" / 97 | ? bad karma etc. 98 | 99 | ! Open "Settings" -> "Advanced settings" -> "Color space selection rules". 100 | ! Replace the value "...=444" with "...=rgb". Don't touch the "420" 101 | ! mode - it is not possible to transmit an RGB stream at 4k resolution 102 | ! and >30hz refresh rates via HDMI 1.x. 103 | 104 | ? My TV doesn't "like" certain refresh rates 105 | ? I use the "Prefer exact refresh rates" option but, still, I want to use 106 | ? 60Hz refresh instead of 30Hz. How can I do it? 107 | 108 | ! Open "Advanced settings" -> "Blacklisted refresh rates" and add the 109 | ! refresh rate of 30 (and, possibly, 29.97) Hz to the list. 110 | 111 | ? At start of video display turns off even if framerate does not change. 112 | ? Why after video start the screen goes to black for some time, even if 113 | ? video framerate is not different from display refresh rate? 114 | 115 | ! When video starts, after a short interval AFRd will turn the screen to 116 | ! black to avoid displaying the first frames of the video, after which 117 | ! usually a refresh rate switch happens, which is painful for eyes. 118 | 119 | ! So, to make the switch easier for eyes it was found that a preliminary 120 | ! switch to black helps. 121 | 122 | ! But at the moment when AFRd switches display to black it doesn't know 123 | ! yet the video framerate - detection is a lengthy process taking from 124 | ! half to a few seconds. So, at the time AFRd turns display off it doesn't 125 | ! know yet the framerate and can't avoid the unneccessary black screen. 126 | 127 | ! If you wish, you can turn off this feature in preferences. 128 | 129 | ? Why display doesn't turn off when I start one video immediately after another? 130 | 131 | ! Preliminary switch to black takes action only if there's no active video. 132 | ! If video was already started, AFRd will avoid turning screen to black 133 | ! because it is not expected frame rate to change. 134 | 135 | ! When video ends, AFRd will wait some time before restoring original 136 | ! refresh rate. If you start a video immediately, it will be considered 137 | ! a "continuation" of previous video, thus screen won't be turned off. 138 | 139 | ! If you want display to turn off after the start of video, wait first 140 | ! for the original refresh rate to restore. 141 | 142 | ? What is the meaning of "Fractional rate usage policy" option? 143 | ? What does the option that allows to choose between "fractional" and 144 | ? "integer" refresh rates? 145 | 146 | ! Usually AFRd will correctly "guess" the video frame rate (23.976 vs 24 147 | ! and so on), but sometimes it may do it wrong. 148 | 149 | ! If you mostly watch videos with "fractional" frame rates (23.976, 29.97, 150 | ! 59.94 fps), or if you mostly watch videos with "integer" frame rates 151 | ! (24, 30, 60 fps), you may decide to set this option to avoid AFRd 152 | ! errors altogether. 153 | 154 | ! But I recommend keeping this option to "auto" - first, because AFRd 155 | ! most of the time does the right job and, second, because most likely 156 | ! you won't see the difference between a 23.976 and 24 Hz refresh, 157 | ! a slight error here will result in just a single frame inserted 158 | ! or removed every ~41 seconds. 159 | 160 | ? Why refresh rate doesn't switch on low-res videos in KODI/SPMC? 161 | ? When playing in KODI/SPMC videos with low resolution (less than 720p), 162 | ? refresh rate doesn't switch. Same happens with MX Player when using 163 | ? the "SW" decoder option. 164 | 165 | ! AFRd supports automatical framerate switching only when using the 166 | ! hardware decoder. KODI developers decided to use, when possible 167 | ! (if enough processor power) the software decoder. 168 | 169 | ! In SPMC you can solve this problem by changing the options 170 | ! "Accelerate MPEG2/MPEG4/h264/hevc" to "Always" in section 171 | ! "Settings -> Player". In MX Player problem is solved by choosing 172 | ! "HW" or "HW+" decoder option. In KODI this problem has no solution 173 | ! yet. 174 | --------------------------------------------------------------------------------