mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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
|
* Text
|
||||||
* Use anti-aliasing and bitmap filtering when displaying bitmap subtitles
|
* Use anti-aliasing and bitmap filtering when displaying bitmap subtitles
|
||||||
([#6950](https://github.com/google/ExoPlayer/pull/6950)).
|
([#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) ###
|
### 2.11.4 (2020-04-08) ###
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.ext.av1;
|
package com.google.android.exoplayer2.ext.av1;
|
||||||
|
|
||||||
|
import static java.lang.Runtime.getRuntime;
|
||||||
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
@ -44,7 +46,9 @@ import java.nio.ByteBuffer;
|
||||||
* @param numInputBuffers Number of input buffers.
|
* @param numInputBuffers Number of input buffers.
|
||||||
* @param numOutputBuffers Number of output buffers.
|
* @param numOutputBuffers Number of output buffers.
|
||||||
* @param initialInputBufferSize The initial size of each input buffer, in bytes.
|
* @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.
|
* @throws Gav1DecoderException Thrown if an exception occurs when initializing the decoder.
|
||||||
*/
|
*/
|
||||||
public Gav1Decoder(
|
public Gav1Decoder(
|
||||||
|
|
@ -56,6 +60,16 @@ import java.nio.ByteBuffer;
|
||||||
if (!Gav1Library.isAvailable()) {
|
if (!Gav1Library.isAvailable()) {
|
||||||
throw new Gav1DecoderException("Failed to load decoder native library.");
|
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);
|
gav1DecoderContext = gav1Init(threads);
|
||||||
if (gav1DecoderContext == GAV1_ERROR || gav1CheckError(gav1DecoderContext) == GAV1_ERROR) {
|
if (gav1DecoderContext == GAV1_ERROR || gav1CheckError(gav1DecoderContext) == GAV1_ERROR) {
|
||||||
throw new Gav1DecoderException(
|
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.
|
* @return {@link #GAV1_OK} if there was no error, {@link #GAV1_ERROR} if an error occured.
|
||||||
*/
|
*/
|
||||||
private native int gav1CheckError(long context);
|
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;
|
package com.google.android.exoplayer2.ext.av1;
|
||||||
|
|
||||||
import static java.lang.Runtime.getRuntime;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
@ -55,6 +53,13 @@ import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||||
*/
|
*/
|
||||||
public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
|
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_INPUT_BUFFERS = 4;
|
||||||
private static final int DEFAULT_NUM_OF_OUTPUT_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. */
|
/* Default size based on 720p resolution video compressed by a factor of two. */
|
||||||
|
|
@ -94,7 +99,7 @@ public class Libgav1VideoRenderer extends SimpleDecoderVideoRenderer {
|
||||||
eventHandler,
|
eventHandler,
|
||||||
eventListener,
|
eventListener,
|
||||||
maxDroppedFramesToNotify,
|
maxDroppedFramesToNotify,
|
||||||
/* threads= */ getRuntime().availableProcessors(),
|
THREAD_COUNT_AUTODETECT,
|
||||||
DEFAULT_NUM_OF_INPUT_BUFFERS,
|
DEFAULT_NUM_OF_INPUT_BUFFERS,
|
||||||
DEFAULT_NUM_OF_OUTPUT_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 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
|
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
|
||||||
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
|
* 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 numInputBuffers Number of input buffers.
|
||||||
* @param numOutputBuffers Number of output buffers.
|
* @param numOutputBuffers Number of output buffers.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ add_subdirectory("${libgav1_root}"
|
||||||
# Build libgav1JNI.
|
# Build libgav1JNI.
|
||||||
add_library(gav1JNI
|
add_library(gav1JNI
|
||||||
SHARED
|
SHARED
|
||||||
gav1_jni.cc)
|
gav1_jni.cc
|
||||||
|
cpu_info.cc
|
||||||
|
cpu_info.h)
|
||||||
|
|
||||||
# Locate NDK log library.
|
# Locate NDK log library.
|
||||||
find_library(android_log_lib log)
|
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 <mutex> // NOLINT
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
|
#include "cpu_info.h" // NOLINT
|
||||||
#include "gav1/decoder.h"
|
#include "gav1/decoder.h"
|
||||||
|
|
||||||
#define LOG_TAG "gav1_jni"
|
#define LOG_TAG "gav1_jni"
|
||||||
|
|
@ -774,5 +775,9 @@ DECODER_FUNC(jint, gav1CheckError, jlong jContext) {
|
||||||
return kStatusOk;
|
return kStatusOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DECODER_FUNC(jint, gav1GetThreads) {
|
||||||
|
return gav1_jni::GetNumberOfPerformanceCoresOnline();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(b/139902005): Add functions for getting libgav1 version and build
|
// TODO(b/139902005): Add functions for getting libgav1 version and build
|
||||||
// configuration once libgav1 ABI provides this information.
|
// configuration once libgav1 ABI provides this information.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue