av1_extension: Add a heuristic to determine default thread count

Android scheduler has performance issues when a device has a
combiation of big/medium/little cores. Add a heuristic to set the
default number of threads used for deocding to the number of
"performance" (i.e. big) cores.

PiperOrigin-RevId: 308683989
This commit is contained in:
vigneshv 2020-04-27 21:15:49 +01:00 committed by Oliver Woodman
parent f052e89a8f
commit 8760424d76
7 changed files with 209 additions and 6 deletions

View file

@ -20,6 +20,8 @@
* Text
* Use anti-aliasing and bitmap filtering when displaying bitmap subtitles
([#6950](https://github.com/google/ExoPlayer/pull/6950)).
* AV1 extension: Add a heuristic to determine the default number of threads
used for AV1 playback using the extension.
### 2.11.4 (2020-04-08) ###

View file

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.ext.av1;
import static java.lang.Runtime.getRuntime;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
@ -44,7 +46,9 @@ import java.nio.ByteBuffer;
* @param numInputBuffers Number of input buffers.
* @param numOutputBuffers Number of output buffers.
* @param initialInputBufferSize The initial size of each input buffer, in bytes.
* @param threads Number of threads libgav1 will use to decode.
* @param threads Number of threads libgav1 will use to decode. If {@link
* Libgav1VideoRenderer#THREAD_COUNT_AUTODETECT} is passed, then this class will auto detect
* the number of threads to be used.
* @throws Gav1DecoderException Thrown if an exception occurs when initializing the decoder.
*/
public Gav1Decoder(
@ -56,6 +60,16 @@ import java.nio.ByteBuffer;
if (!Gav1Library.isAvailable()) {
throw new Gav1DecoderException("Failed to load decoder native library.");
}
if (threads == Libgav1VideoRenderer.THREAD_COUNT_AUTODETECT) {
// Try to get the optimal number of threads from the AV1 heuristic.
threads = gav1GetThreads();
if (threads <= 0) {
// If that is not available, default to the number of available processors.
threads = getRuntime().availableProcessors();
}
}
gav1DecoderContext = gav1Init(threads);
if (gav1DecoderContext == GAV1_ERROR || gav1CheckError(gav1DecoderContext) == GAV1_ERROR) {
throw new Gav1DecoderException(
@ -231,4 +245,11 @@ import java.nio.ByteBuffer;
* @return {@link #GAV1_OK} if there was no error, {@link #GAV1_ERROR} if an error occured.
*/
private native int gav1CheckError(long context);
/**
* Returns the optimal number of threads to be used for AV1 decoding.
*
* @return Optimal number of threads if there was no error, 0 if an error occurred.
*/
private native int gav1GetThreads();
}

View file

@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.ext.av1;
import static java.lang.Runtime.getRuntime;
import android.os.Handler;
import android.view.Surface;
import androidx.annotation.Nullable;
@ -55,6 +53,13 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener;
*/
public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
/**
* Attempts to use as many threads as performance processors available on the device. If the
* number of performance processors cannot be detected, the number of available processors is
* used.
*/
public static final int THREAD_COUNT_AUTODETECT = 0;
private static final int DEFAULT_NUM_OF_INPUT_BUFFERS = 4;
private static final int DEFAULT_NUM_OF_OUTPUT_BUFFERS = 4;
/* Default size based on 720p resolution video compressed by a factor of two. */
@ -94,7 +99,7 @@ public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
eventHandler,
eventListener,
maxDroppedFramesToNotify,
/* threads= */ getRuntime().availableProcessors(),
THREAD_COUNT_AUTODETECT,
DEFAULT_NUM_OF_INPUT_BUFFERS,
DEFAULT_NUM_OF_OUTPUT_BUFFERS);
}
@ -109,7 +114,9 @@ public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
* @param threads Number of threads libgav1 will use to decode.
* @param threads Number of threads libgav1 will use to decode. If
* {@link #THREAD_COUNT_AUTODETECT} is passed, then the number of threads to use is
* auto-detected based on CPU capabilities.
* @param numInputBuffers Number of input buffers.
* @param numOutputBuffers Number of output buffers.
*/

View file

@ -44,7 +44,9 @@ add_subdirectory("${libgav1_root}"
# Build libgav1JNI.
add_library(gav1JNI
SHARED
gav1_jni.cc)
gav1_jni.cc
cpu_info.cc
cpu_info.h)
# Locate NDK log library.
find_library(android_log_lib log)

View file

@ -0,0 +1,153 @@
#include "cpu_info.h" // NOLINT
#include <unistd.h>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace gav1_jni {
namespace {
// Note: The code in this file needs to use the 'long' type because it is the
// return type of the Standard C Library function strtol(). The linter warnings
// are suppressed with NOLINT comments since they are integers at runtime.
// Returns the number of online processor cores.
int GetNumberOfProcessorsOnline() {
// See https://developer.android.com/ndk/guides/cpu-features.
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN); // NOLINT
if (num_cpus < 0) {
return 0;
}
// It is safe to cast num_cpus to int. sysconf(_SC_NPROCESSORS_ONLN) returns
// the return value of get_nprocs(), which is an int.
return static_cast<int>(num_cpus);
}
} // namespace
// These CPUs support heterogeneous multiprocessing.
#if defined(__arm__) || defined(__aarch64__)
// A helper function used by GetNumberOfPerformanceCoresOnline().
//
// Returns the cpuinfo_max_freq value (in kHz) of the given CPU. Returns 0 on
// failure.
long GetCpuinfoMaxFreq(int cpu_index) { // NOLINT
char buffer[128];
const int rv = snprintf(
buffer, sizeof(buffer),
"/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpu_index);
if (rv < 0 || rv >= sizeof(buffer)) {
return 0;
}
FILE* file = fopen(buffer, "r");
if (file == nullptr) {
return 0;
}
char* const str = fgets(buffer, sizeof(buffer), file);
fclose(file);
if (str == nullptr) {
return 0;
}
const long freq = strtol(str, nullptr, 10); // NOLINT
if (freq <= 0 || freq == LONG_MAX) {
return 0;
}
return freq;
}
// Returns the number of performance CPU cores that are online. The number of
// efficiency CPU cores is subtracted from the total number of CPU cores. Uses
// cpuinfo_max_freq to determine whether a CPU is a performance core or an
// efficiency core.
//
// This function is not perfect. For example, the Snapdragon 632 SoC used in
// Motorola Moto G7 has performance and efficiency cores with the same
// cpuinfo_max_freq but different cpuinfo_min_freq. This function fails to
// differentiate the two kinds of cores and reports all the cores as
// performance cores.
int GetNumberOfPerformanceCoresOnline() {
// Get the online CPU list. Some examples of the online CPU list are:
// "0-7"
// "0"
// "0-1,2,3,4-7"
FILE* file = fopen("/sys/devices/system/cpu/online", "r");
if (file == nullptr) {
return 0;
}
char online[512];
char* const str = fgets(online, sizeof(online), file);
fclose(file);
file = nullptr;
if (str == nullptr) {
return 0;
}
// Count the number of the slowest CPUs. Some SoCs such as Snapdragon 855
// have performance cores with different max frequencies, so only the slowest
// CPUs are efficiency cores. If we count the number of the fastest CPUs, we
// will fail to count the second fastest performance cores.
long slowest_cpu_freq = LONG_MAX; // NOLINT
int num_slowest_cpus = 0;
int num_cpus = 0;
const char* cp = online;
int range_begin = -1;
while (true) {
char* str_end;
const int cpu = static_cast<int>(strtol(cp, &str_end, 10)); // NOLINT
if (str_end == cp) {
break;
}
cp = str_end;
if (*cp == '-') {
range_begin = cpu;
} else {
if (range_begin == -1) {
range_begin = cpu;
}
num_cpus += cpu - range_begin + 1;
for (int i = range_begin; i <= cpu; ++i) {
const long freq = GetCpuinfoMaxFreq(i); // NOLINT
if (freq <= 0) {
return 0;
}
if (freq < slowest_cpu_freq) {
slowest_cpu_freq = freq;
num_slowest_cpus = 0;
}
if (freq == slowest_cpu_freq) {
++num_slowest_cpus;
}
}
range_begin = -1;
}
if (*cp == '\0') {
break;
}
++cp;
}
// If there are faster CPU cores than the slowest CPU cores, exclude the
// slowest CPU cores.
if (num_slowest_cpus < num_cpus) {
num_cpus -= num_slowest_cpus;
}
return num_cpus;
}
#else
// Assume symmetric multiprocessing.
int GetNumberOfPerformanceCoresOnline() {
return GetNumberOfProcessorsOnline();
}
#endif
} // namespace gav1_jni

View file

@ -0,0 +1,13 @@
#ifndef EXOPLAYER_V2_EXTENSIONS_AV1_SRC_MAIN_JNI_CPU_INFO_H_
#define EXOPLAYER_V2_EXTENSIONS_AV1_SRC_MAIN_JNI_CPU_INFO_H_
namespace gav1_jni {
// Returns the number of performance cores that are available for AV1 decoding.
// This is a heuristic that works on most common android devices. Returns 0 on
// error or if the number of performance cores cannot be determined.
int GetNumberOfPerformanceCoresOnline();
} // namespace gav1_jni
#endif // EXOPLAYER_V2_EXTENSIONS_AV1_SRC_MAIN_JNI_CPU_INFO_H_

View file

@ -32,6 +32,7 @@
#include <mutex> // NOLINT
#include <new>
#include "cpu_info.h" // NOLINT
#include "gav1/decoder.h"
#define LOG_TAG "gav1_jni"
@ -774,5 +775,9 @@ DECODER_FUNC(jint, gav1CheckError, jlong jContext) {
return kStatusOk;
}
DECODER_FUNC(jint, gav1GetThreads) {
return gav1_jni::GetNumberOfPerformanceCoresOnline();
}
// TODO(b/139902005): Add functions for getting libgav1 version and build
// configuration once libgav1 ABI provides this information.