You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
7.2 KiB

/*
* H.265 video codec.
* Copyright (c) 2013-2014 struktur AG, Dirk Farin <farin@struktur.de>
*
* This file is part of libde265.
*
* libde265 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libde265 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libde265. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <math.h>
#include <unistd.h>
const bool D = false;
/* There are numerical stability problems in the matrix inverse.
Switching to long double seems to provide enough accuracy.
TODO: in the long term, use a better regression algorithm.
*/
typedef long double FP;
struct datapoint
{
double rate;
double distortion;
};
struct BjoentegaardParams
{
// a*log^3 R + b*log^2 R + c*log R + d
double a,b,c,d;
double minRate, maxRate;
};
std::vector<datapoint> curveA,curveB;
BjoentegaardParams paramsA,paramsB;
#define RATE_NORMALIZATION_FACTOR 1 //(1/1000.0)
FP invf(int i,int j,const FP* m)
{
int o = 2+(j-i);
i += 4+o;
j += 4-o;
#define e(a,b) m[ ((j+b)%4)*4 + ((i+a)%4) ]
FP inv =
+ e(+1,-1)*e(+0,+0)*e(-1,+1)
+ e(+1,+1)*e(+0,-1)*e(-1,+0)
+ e(-1,-1)*e(+1,+0)*e(+0,+1)
- e(-1,-1)*e(+0,+0)*e(+1,+1)
- e(-1,+1)*e(+0,-1)*e(+1,+0)
- e(+1,-1)*e(-1,+0)*e(+0,+1);
return (o%2)?inv : -inv;
#undef e
}
bool inverseMatrix4x4(const FP *m, FP *out)
{
FP inv[16];
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
inv[j*4+i] = invf(i,j,m);
FP D = 0;
for(int k=0;k<4;k++) D += m[k] * inv[k*4];
if (D == 0) return false;
D = 1.0 / D;
for (int i = 0; i < 16; i++)
out[i] = inv[i] * D;
return true;
}
BjoentegaardParams fitParams(const std::vector<datapoint>& curve)
{
// build regression matrix
int n = curve.size();
FP X[4*n]; // regression matrix
FP XT[n*4]; // transpose of X
for (int i=0;i<n;i++) {
FP x = log(curve[i].rate) * RATE_NORMALIZATION_FACTOR;
X[4*i + 0] = 1;
X[4*i + 1] = x;
X[4*i + 2] = x*x;
X[4*i + 3] = x*x*x;
if (D) printf("%f %f %f %f ;\n",1.0,(double)x,(double)(x*x),(double)(x*x*x));
XT[i+0*n] = 1;
XT[i+1*n] = x;
XT[i+2*n] = x*x;
XT[i+3*n] = x*x*x;
}
if (D) {
printf("rate: ");
for (int i=0;i<n;i++) {
printf("%f ; ",curve[i].rate);
}
printf("\n");
printf("distortion: ");
for (int i=0;i<n;i++) {
printf("%f ; ",curve[i].distortion);
}
printf("\n");
}
// calc X^T * X
FP XTX[4*4];
for (int y=0;y<4;y++)
for (int x=0;x<4;x++) {
FP sum=0;
for (int i=0;i<n;i++)
{
sum += XT[y*n + i] * X[x + i*4];
}
XTX[y*4+x] = sum;
}
FP XTXinv[4*4];
inverseMatrix4x4(XTX, XTXinv);
if (D) {
for (int y=0;y<4;y++) {
for (int x=0;x<4;x++) {
printf("%f ",(double)XTXinv[y*4+x]);
}
printf("\n");
}
}
// calculate pseudo-inverse XP = (X^T * X)^-1 * X^T
FP XP[n*4];
for (int y=0;y<4;y++) {
for (int x=0;x<n;x++) {
FP sum=0;
for (int i=0;i<4;i++)
{
sum += XTXinv[y*4 + i] * XT[x + i*n];
}
XP[y*n+x] = sum;
}
}
// calculate regression parameters
FP p[4];
for (int k=0;k<4;k++)
{
FP sum=0;
for (int i=0;i<n;i++) {
sum += XP[k*n + i] * curve[i].distortion;
}
p[k]=sum;
}
BjoentegaardParams param;
param.d = p[0];
param.c = p[1];
param.b = p[2];
param.a = p[3];
// find min and max rate
param.minRate = curve[0].rate;
param.maxRate = curve[0].rate;
for (int i=1;i<n;i++) {
param.minRate = std::min(param.minRate, curve[i].rate);
param.maxRate = std::max(param.maxRate, curve[i].rate);
}
return param;
}
FP evalIntegralAt(const BjoentegaardParams& p, double x)
{
FP sum = 0;
// integral of: d
sum += p.d * x;
// integral of: c*log(x)
sum += p.c * x* (log(x)-1);
// integral of: b*log(x)^2
sum += p.b * x * ((log(x)-2)*log(x)+2);
// integral of: a*log(x)^3
sum += p.a * x * (log(x)*((log(x)-3)*log(x)+6)-6);
return sum;
}
double calcBjoentegaard(const BjoentegaardParams& paramsA,
const BjoentegaardParams& paramsB,
double min_rate, double max_rate)
{
double mini = std::max(paramsA.minRate, paramsB.minRate);
double maxi = std::min(paramsA.maxRate, paramsB.maxRate);
if (min_rate >= 0) mini = std::max(mini, min_rate);
if (max_rate >= 0) maxi = std::min(maxi, max_rate);
if (D) printf("range: %f %f\n",mini,maxi);
FP intA = evalIntegralAt(paramsA, maxi) - evalIntegralAt(paramsA, mini);
FP intB = evalIntegralAt(paramsB, maxi) - evalIntegralAt(paramsB, mini);
if (D) printf("int1:%f int2:%f\n",(double)intA,(double)intB);
return (intA-intB)/(maxi-mini);
}
std::vector<datapoint> readRDFile(const char* filename, float min_rate, float max_rate)
{
std::vector<datapoint> curve;
std::ifstream istr(filename);
for (;;)
{
std::string line;
getline(istr, line);
if (istr.eof())
break;
if (line[0]=='#') continue;
std::stringstream sstr(line);
datapoint p;
sstr >> p.rate >> p.distortion;
if (min_rate>=0 && p.rate < min_rate) continue;
if (max_rate>=0 && p.rate > max_rate) continue;
curve.push_back(p);
}
return curve;
}
int main(int argc, char** argv)
{
float min_rate = -1;
float max_rate = -1;
int c;
while ((c=getopt(argc,argv, "l:h:")) != -1) {
switch (c) {
case 'l': min_rate = atof(optarg); break;
case 'h': max_rate = atof(optarg); break;
}
}
curveA = readRDFile(argv[optind], min_rate, max_rate);
paramsA = fitParams(curveA);
printf("params A: %f %f %f %f\n",paramsA.a,paramsA.b,paramsA.c,paramsA.d);
printf("gnuplot: %f*log(x)**3+%f*log(x)**2+%f*log(x)+%f\n",paramsA.a,paramsA.b,paramsA.c,paramsA.d);
if (optind+1<argc) {
curveB = readRDFile(argv[optind+1], min_rate, max_rate);
paramsB = fitParams(curveB);
printf("params B: %f %f %f %f\n",paramsB.a,paramsB.b,paramsB.c,paramsB.d);
printf("gnuplot: %f*log(x)**3+%f*log(x)**2+%f*log(x)+%f\n",paramsB.a,paramsB.b,paramsB.c,paramsB.d);
double delta = calcBjoentegaard(paramsA,paramsB, min_rate,max_rate);
printf("Bjoentegaard delta: %f dB (A-B -> >0 -> first (A) is better)\n",delta);
if (delta>=0) {
printf("-> first is better by %f dB\n",delta);
}
else {
printf("-> second is better by %f dB\n",-delta);
}
}
return 0;
}