├── 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 |
7 |
8 |
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 |
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 |
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 |
--------------------------------------------------------------------------------