mirror of
https://github.com/samsonjs/media.git
synced 2026-03-28 09:55:48 +00:00
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:
parent
f052e89a8f
commit
8760424d76
7 changed files with 209 additions and 6 deletions
|
|
@ -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) ###
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
153
extensions/av1/src/main/jni/cpu_info.cc
Normal file
153
extensions/av1/src/main/jni/cpu_info.cc
Normal 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
|
||||
13
extensions/av1/src/main/jni/cpu_info.h
Normal file
13
extensions/av1/src/main/jni/cpu_info.h
Normal 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_
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue