1*b7c941bbSAndroid Build Coastguard Worker /*
2*b7c941bbSAndroid Build Coastguard Worker * Copyright (C) 2023 The Android Open Source Project
3*b7c941bbSAndroid Build Coastguard Worker *
4*b7c941bbSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*b7c941bbSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*b7c941bbSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*b7c941bbSAndroid Build Coastguard Worker *
8*b7c941bbSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0
9*b7c941bbSAndroid Build Coastguard Worker *
10*b7c941bbSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*b7c941bbSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*b7c941bbSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*b7c941bbSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*b7c941bbSAndroid Build Coastguard Worker * limitations under the License.
15*b7c941bbSAndroid Build Coastguard Worker */
16*b7c941bbSAndroid Build Coastguard Worker
17*b7c941bbSAndroid Build Coastguard Worker //#define LOG_NDEBUG 0
18*b7c941bbSAndroid Build Coastguard Worker #define LOG_TAG "NativeVideoQualityUtils"
19*b7c941bbSAndroid Build Coastguard Worker
20*b7c941bbSAndroid Build Coastguard Worker #include <jni.h>
21*b7c941bbSAndroid Build Coastguard Worker #include <log/log.h>
22*b7c941bbSAndroid Build Coastguard Worker
23*b7c941bbSAndroid Build Coastguard Worker #include <Eigen/Dense>
24*b7c941bbSAndroid Build Coastguard Worker #include <Eigen/QR>
25*b7c941bbSAndroid Build Coastguard Worker #include <string>
26*b7c941bbSAndroid Build Coastguard Worker #include <vector>
27*b7c941bbSAndroid Build Coastguard Worker
28*b7c941bbSAndroid Build Coastguard Worker // Migrate this method to std::format when C++20 becomes available
29*b7c941bbSAndroid Build Coastguard Worker template <typename... Args>
StringFormat(const std::string & format,Args...args)30*b7c941bbSAndroid Build Coastguard Worker std::string StringFormat(const std::string& format, Args... args) {
31*b7c941bbSAndroid Build Coastguard Worker auto size = std::snprintf(nullptr, 0, format.c_str(), args...);
32*b7c941bbSAndroid Build Coastguard Worker if (size < 0) return {};
33*b7c941bbSAndroid Build Coastguard Worker std::vector<char> buffer(size + 1); // Add 1 for terminating null byte
34*b7c941bbSAndroid Build Coastguard Worker std::snprintf(buffer.data(), buffer.size(), format.c_str(), args...);
35*b7c941bbSAndroid Build Coastguard Worker return std::string(buffer.data(), size); // Exclude the terminating null byte
36*b7c941bbSAndroid Build Coastguard Worker }
37*b7c941bbSAndroid Build Coastguard Worker
polyEval(std::vector<double> & coeffs,double x)38*b7c941bbSAndroid Build Coastguard Worker double polyEval(std::vector<double>& coeffs, double x) {
39*b7c941bbSAndroid Build Coastguard Worker double y = coeffs[0];
40*b7c941bbSAndroid Build Coastguard Worker double xn = x;
41*b7c941bbSAndroid Build Coastguard Worker for (int i = 1; i < coeffs.size(); i++) {
42*b7c941bbSAndroid Build Coastguard Worker y += (coeffs[i] * xn);
43*b7c941bbSAndroid Build Coastguard Worker xn *= x;
44*b7c941bbSAndroid Build Coastguard Worker }
45*b7c941bbSAndroid Build Coastguard Worker return y;
46*b7c941bbSAndroid Build Coastguard Worker }
47*b7c941bbSAndroid Build Coastguard Worker
polyIntegrate(std::vector<double> & coeffs,double coi=0.0)48*b7c941bbSAndroid Build Coastguard Worker std::vector<double> polyIntegrate(std::vector<double>& coeffs, double coi = 0.0) {
49*b7c941bbSAndroid Build Coastguard Worker std::vector<double> integratedCoeffs(coeffs.size() + 1);
50*b7c941bbSAndroid Build Coastguard Worker integratedCoeffs[0] = coi; // constant of integration
51*b7c941bbSAndroid Build Coastguard Worker for (int i = 1; i < coeffs.size() + 1; i++) {
52*b7c941bbSAndroid Build Coastguard Worker integratedCoeffs[i] = coeffs[i - 1] / i;
53*b7c941bbSAndroid Build Coastguard Worker }
54*b7c941bbSAndroid Build Coastguard Worker return integratedCoeffs;
55*b7c941bbSAndroid Build Coastguard Worker }
56*b7c941bbSAndroid Build Coastguard Worker
polyFit(std::vector<double> & rates,std::vector<double> & qualities,int order)57*b7c941bbSAndroid Build Coastguard Worker std::vector<double> polyFit(std::vector<double>& rates, std::vector<double>& qualities, int order) {
58*b7c941bbSAndroid Build Coastguard Worker // y = X * a, y is vector of qualities, X is vandermonde matrix and a is vector of coeffs.
59*b7c941bbSAndroid Build Coastguard Worker Eigen::MatrixXd X(rates.size(), order + 1);
60*b7c941bbSAndroid Build Coastguard Worker Eigen::MatrixXd y(qualities.size(), 1);
61*b7c941bbSAndroid Build Coastguard Worker for (int i = 0; i < rates.size(); ++i) {
62*b7c941bbSAndroid Build Coastguard Worker y(i, 0) = qualities[i];
63*b7c941bbSAndroid Build Coastguard Worker double element = 1;
64*b7c941bbSAndroid Build Coastguard Worker for (int j = 0; j < order + 1; ++j) {
65*b7c941bbSAndroid Build Coastguard Worker X(i, j) = element;
66*b7c941bbSAndroid Build Coastguard Worker element *= rates[i];
67*b7c941bbSAndroid Build Coastguard Worker }
68*b7c941bbSAndroid Build Coastguard Worker }
69*b7c941bbSAndroid Build Coastguard Worker // QR decomposition
70*b7c941bbSAndroid Build Coastguard Worker Eigen::MatrixXd a = X.colPivHouseholderQr().solve(y);
71*b7c941bbSAndroid Build Coastguard Worker std::vector<double> coeffs(order + 1);
72*b7c941bbSAndroid Build Coastguard Worker for (int i = 0; i < order + 1; i++) {
73*b7c941bbSAndroid Build Coastguard Worker coeffs[i] = a(i, 0);
74*b7c941bbSAndroid Build Coastguard Worker }
75*b7c941bbSAndroid Build Coastguard Worker return coeffs;
76*b7c941bbSAndroid Build Coastguard Worker }
77*b7c941bbSAndroid Build Coastguard Worker
getAvgImprovement(std::vector<double> & xA,std::vector<double> & yA,std::vector<double> & xB,std::vector<double> & yB,int order)78*b7c941bbSAndroid Build Coastguard Worker double getAvgImprovement(std::vector<double>& xA, std::vector<double>& yA, std::vector<double>& xB,
79*b7c941bbSAndroid Build Coastguard Worker std::vector<double>& yB, int order) {
80*b7c941bbSAndroid Build Coastguard Worker std::vector<double> coeffsA = polyFit(xA, yA, order);
81*b7c941bbSAndroid Build Coastguard Worker std::vector<double> coeffsB = polyFit(xB, yB, order);
82*b7c941bbSAndroid Build Coastguard Worker std::vector<double> integratedCoeffsA = polyIntegrate(coeffsA);
83*b7c941bbSAndroid Build Coastguard Worker std::vector<double> integratedCoeffsB = polyIntegrate(coeffsB);
84*b7c941bbSAndroid Build Coastguard Worker double minX = std::max(*std::min_element(xA.begin(), xA.end()),
85*b7c941bbSAndroid Build Coastguard Worker *std::min_element(xB.begin(), xB.end()));
86*b7c941bbSAndroid Build Coastguard Worker double maxX = std::min(*std::max_element(xA.begin(), xA.end()),
87*b7c941bbSAndroid Build Coastguard Worker *std::max_element(xB.begin(), xB.end()));
88*b7c941bbSAndroid Build Coastguard Worker double areaA = polyEval(integratedCoeffsA, maxX) - polyEval(integratedCoeffsA, minX);
89*b7c941bbSAndroid Build Coastguard Worker double areaB = polyEval(integratedCoeffsB, maxX) - polyEval(integratedCoeffsB, minX);
90*b7c941bbSAndroid Build Coastguard Worker return (areaB - areaA) / (maxX - minX);
91*b7c941bbSAndroid Build Coastguard Worker }
92*b7c941bbSAndroid Build Coastguard Worker
nativeGetBDRate(JNIEnv * env,jobject,jdoubleArray jQualityA,jdoubleArray jRatesA,jdoubleArray jQualityB,jdoubleArray jRatesB,jboolean selBdSnr,jobject jRetMsg)93*b7c941bbSAndroid Build Coastguard Worker static jdouble nativeGetBDRate(JNIEnv* env, jobject, jdoubleArray jQualityA, jdoubleArray jRatesA,
94*b7c941bbSAndroid Build Coastguard Worker jdoubleArray jQualityB, jdoubleArray jRatesB, jboolean selBdSnr,
95*b7c941bbSAndroid Build Coastguard Worker jobject jRetMsg) {
96*b7c941bbSAndroid Build Coastguard Worker jsize len[4]{env->GetArrayLength(jQualityA), env->GetArrayLength(jRatesA),
97*b7c941bbSAndroid Build Coastguard Worker env->GetArrayLength(jQualityB), env->GetArrayLength(jRatesB)};
98*b7c941bbSAndroid Build Coastguard Worker std::string msg;
99*b7c941bbSAndroid Build Coastguard Worker if (len[0] != len[1] || len[0] != len[2] || len[0] != len[3]) {
100*b7c941bbSAndroid Build Coastguard Worker msg = StringFormat("array length of quality and bit rates for set A/B are not same, "
101*b7c941bbSAndroid Build Coastguard Worker "lengths are %d %d %d %d \n",
102*b7c941bbSAndroid Build Coastguard Worker (int)len[0], (int)len[1], (int)len[2], (int)len[3]);
103*b7c941bbSAndroid Build Coastguard Worker } else if (len[0] < 4) {
104*b7c941bbSAndroid Build Coastguard Worker msg = StringFormat("too few data-points present for bd rate analysis, count %d \n", len[0]);
105*b7c941bbSAndroid Build Coastguard Worker } else {
106*b7c941bbSAndroid Build Coastguard Worker std::vector<double> ratesA(len[0]);
107*b7c941bbSAndroid Build Coastguard Worker env->GetDoubleArrayRegion(jRatesA, 0, len[0], &ratesA[0]);
108*b7c941bbSAndroid Build Coastguard Worker std::vector<double> ratesB(len[0]);
109*b7c941bbSAndroid Build Coastguard Worker env->GetDoubleArrayRegion(jRatesB, 0, len[0], &ratesB[0]);
110*b7c941bbSAndroid Build Coastguard Worker std::vector<double> qualitiesA(len[0]);
111*b7c941bbSAndroid Build Coastguard Worker env->GetDoubleArrayRegion(jQualityA, 0, len[0], &qualitiesA[0]);
112*b7c941bbSAndroid Build Coastguard Worker std::vector<double> qualitiesB(len[0]);
113*b7c941bbSAndroid Build Coastguard Worker env->GetDoubleArrayRegion(jQualityB, 0, len[0], &qualitiesB[0]);
114*b7c941bbSAndroid Build Coastguard Worker // log rate
115*b7c941bbSAndroid Build Coastguard Worker for (int i = 0; i < len[0]; i++) {
116*b7c941bbSAndroid Build Coastguard Worker ratesA[i] = std::log(ratesA[i]);
117*b7c941bbSAndroid Build Coastguard Worker ratesB[i] = std::log(ratesB[i]);
118*b7c941bbSAndroid Build Coastguard Worker }
119*b7c941bbSAndroid Build Coastguard Worker const int order = 3;
120*b7c941bbSAndroid Build Coastguard Worker if (selBdSnr) {
121*b7c941bbSAndroid Build Coastguard Worker return getAvgImprovement(ratesA, qualitiesA, ratesB, qualitiesB, order);
122*b7c941bbSAndroid Build Coastguard Worker } else {
123*b7c941bbSAndroid Build Coastguard Worker double bdRate = getAvgImprovement(qualitiesA, ratesA, qualitiesB, ratesB, order);
124*b7c941bbSAndroid Build Coastguard Worker // In really bad formed data the exponent can grow too large clamp it.
125*b7c941bbSAndroid Build Coastguard Worker if (bdRate > 200) {
126*b7c941bbSAndroid Build Coastguard Worker bdRate = 200;
127*b7c941bbSAndroid Build Coastguard Worker }
128*b7c941bbSAndroid Build Coastguard Worker bdRate = (std::exp(bdRate) - 1) * 100;
129*b7c941bbSAndroid Build Coastguard Worker return bdRate;
130*b7c941bbSAndroid Build Coastguard Worker }
131*b7c941bbSAndroid Build Coastguard Worker }
132*b7c941bbSAndroid Build Coastguard Worker jclass clazz = env->GetObjectClass(jRetMsg);
133*b7c941bbSAndroid Build Coastguard Worker jmethodID mId =
134*b7c941bbSAndroid Build Coastguard Worker env->GetMethodID(clazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
135*b7c941bbSAndroid Build Coastguard Worker env->CallObjectMethod(jRetMsg, mId, env->NewStringUTF(msg.c_str()));
136*b7c941bbSAndroid Build Coastguard Worker return 0;
137*b7c941bbSAndroid Build Coastguard Worker }
138*b7c941bbSAndroid Build Coastguard Worker
registerAndroidVideoCodecCtsVQUtils(JNIEnv * env)139*b7c941bbSAndroid Build Coastguard Worker int registerAndroidVideoCodecCtsVQUtils(JNIEnv* env) {
140*b7c941bbSAndroid Build Coastguard Worker const JNINativeMethod methodTable[] = {
141*b7c941bbSAndroid Build Coastguard Worker {"nativeGetBDRate", "([D[D[D[DZLjava/lang/StringBuilder;)D", (void*)nativeGetBDRate},
142*b7c941bbSAndroid Build Coastguard Worker };
143*b7c941bbSAndroid Build Coastguard Worker jclass c = env->FindClass("android/videocodec/cts/VideoEncoderQualityRegressionTestBase");
144*b7c941bbSAndroid Build Coastguard Worker return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
145*b7c941bbSAndroid Build Coastguard Worker }
146*b7c941bbSAndroid Build Coastguard Worker
JNI_OnLoad(JavaVM * vm,void *)147*b7c941bbSAndroid Build Coastguard Worker extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
148*b7c941bbSAndroid Build Coastguard Worker JNIEnv* env;
149*b7c941bbSAndroid Build Coastguard Worker if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
150*b7c941bbSAndroid Build Coastguard Worker if (registerAndroidVideoCodecCtsVQUtils(env) != JNI_OK) return JNI_ERR;
151*b7c941bbSAndroid Build Coastguard Worker return JNI_VERSION_1_6;
152*b7c941bbSAndroid Build Coastguard Worker }
153