Enter 4 pairs of Bitrate (kbps) and Quality (PSNR/SSIM) for both Reference and Test codecs.
Anchor (Reference) Codec
#
Test Codec
#
BD-Rate (Bitrate Savings):—
BD-PSNR (Quality Gain):—
Understanding BD-Rate
Bjontegaard Delta Rate (BD-Rate) is the standard metric used in video compression research and engineering to compare the coding efficiency of two different video codecs (or different settings of the same codec).
Instead of comparing bitrate at a single quality point, BD-Rate calculates the average percentage difference in bitrate required to achieve the same objective quality (usually measured in PSNR or SSIM) over a range of bitrates.
How it Works
The calculation involves constructing Rate-Distortion (RD) curves for both the Reference (Anchor) and the Test case using 4 data points (typically generated by quantization parameters like 22, 27, 32, 37).
Logarithmic Scale: Bitrates are converted to a logarithmic scale because the relationship between bitrate and distortion is roughly exponential.
Polynomial Fitting: A curve (typically a 3rd-order polynomial) is fitted to the data points for both codecs.
Integration: The area between the two curves is integrated over the overlapping quality range.
Interpreting the Result
The output is expressed as a percentage:
Negative Value (e.g., -15%): The Test codec is more efficient. It requires 15% less bitrate to achieve the same quality as the Reference.
Positive Value (e.g., +10%): The Test codec is less efficient. It requires 10% more bitrate for the same quality.
Zero (0%): Both codecs perform identically.
About BD-PSNR
BD-PSNR measures the average difference in quality (dB) at the same bitrate. A positive BD-PSNR indicates higher quality for the same file size.
function calculateBDRate() {
// Helper to get value
function getVal(id) {
var el = document.getElementById(id);
var val = parseFloat(el.value);
if (isNaN(val)) return null;
return val;
}
// Gather Data
var ancR = [getVal('anc_r1'), getVal('anc_r2'), getVal('anc_r3'), getVal('anc_r4')];
var ancQ = [getVal('anc_q1'), getVal('anc_q2'), getVal('anc_q3'), getVal('anc_q4')];
var tstR = [getVal('tst_r1'), getVal('tst_r2'), getVal('tst_r3'), getVal('tst_r4')];
var tstQ = [getVal('tst_q1'), getVal('tst_q2'), getVal('tst_q3'), getVal('tst_q4')];
// Validation
if (ancR.includes(null) || ancQ.includes(null) || tstR.includes(null) || tstQ.includes(null)) {
alert("Please enter all 4 data points (Bitrate and PSNR) for both Reference and Test codecs.");
return;
}
// — Math Logic Starts Here —
// 1. Log conversion of rates for BD-Rate calculation
var logAncR = ancR.map(function(r) { return Math.log10(r); });
var logTstR = tstR.map(function(r) { return Math.log10(r); });
// 2. Calculate BD-Rate:
// Curve fit: LogRate = f(PSNR). We integrate over PSNR range.
// Find overlapping range of PSNR
var minPSNR = Math.max(Math.min.apply(null, ancQ), Math.min.apply(null, tstQ));
var maxPSNR = Math.min(Math.max.apply(null, ancQ), Math.max.apply(null, tstQ));
var bdRate = 0;
var bdPsnr = 0;
if (maxPSNR > minPSNR) {
// Fit Poly: x=PSNR, y=LogRate
var polyAnc = polyFit(ancQ, logAncR, 3);
var polyTst = polyFit(tstQ, logTstR, 3);
// Integrate
var intAnc = integratePoly(polyAnc, minPSNR, maxPSNR);
var intTst = integratePoly(polyTst, minPSNR, maxPSNR);
// Average difference
var avgDiff = (intTst – intAnc) / (maxPSNR – minPSNR);
// Convert back from log scale to percentage
bdRate = (Math.pow(10, avgDiff) – 1) * 100;
} else {
bdRate = NaN; // No overlap
}
// 3. Calculate BD-PSNR:
// Curve fit: PSNR = f(LogRate). We integrate over LogRate range.
var minLogR = Math.max(Math.min.apply(null, logAncR), Math.min.apply(null, logTstR));
var maxLogR = Math.min(Math.max.apply(null, logAncR), Math.max.apply(null, logTstR));
if (maxLogR > minLogR) {
// Fit Poly: x=LogRate, y=PSNR
var polyAncP = polyFit(logAncR, ancQ, 3);
var polyTstP = polyFit(logTstR, tstQ, 3);
var intAncP = integratePoly(polyAncP, minLogR, maxLogR);
var intTstP = integratePoly(polyTstP, minLogR, maxLogR);
bdPsnr = (intTstP – intAncP) / (maxLogR – minLogR);
} else {
bdPsnr = NaN;
}
// Display Results
var resDiv = document.getElementById("result");
resDiv.style.display = "block";
var bdRateEl = document.getElementById("bdRateValue");
var bdPsnrEl = document.getElementById("bdPsnrValue");
var interpEl = document.getElementById("interpretation");
if (!isNaN(bdRate)) {
bdRateEl.innerText = bdRate.toFixed(2) + "%";
bdRateEl.className = bdRate < 0 ? "value negative" : "value positive";
var savingsText = bdRate < 0
? "Bitrate SAVINGS of " + Math.abs(bdRate).toFixed(2) + "%"
: "Bitrate INCREASE of " + bdRate.toFixed(2) + "%";
interpEl.innerHTML = "Result: The Test codec shows a <span style='color:" + (bdRate " + savingsText + " compared to the Anchor at equal quality.";
} else {
bdRateEl.innerText = "No Overlap";
interpEl.innerText = "Error: The quality ranges of the two curves do not overlap.";
}
if (!isNaN(bdPsnr)) {
bdPsnrEl.innerText = bdPsnr.toFixed(3) + " dB";
bdPsnrEl.className = bdPsnr > 0 ? "value negative" : "value positive"; // Positive PSNR gain is "Green/Good"
} else {
bdPsnrEl.innerText = "No Overlap";
}
}
// — Math Helpers —
// Polynomial Fit (Least Squares)
// Solves y = a0 + a1*x + a2*x^2 + a3*x^3
// Returns array [a0, a1, a2, a3]
function polyFit(x, y, order) {
// Setup matrices for Normal Equation: (X^T * X) * Beta = X^T * Y
// Since order is small (3), we can hardcode Gaussian elimination or similar
// But for robustness in 4 points, order 3 is exact interpolation if points distinct.
var n = x.length;
var k = order + 1;
var X = []; // Matrix X
var Y = y; // Vector Y
// Build X Matrix
for (var i = 0; i < n; i++) {
var row = [];
for (var j = 0; j < k; j++) {
row.push(Math.pow(x[i], j));
}
X.push(row);
}
// Transpose X
var XT = [];
for (var i = 0; i < k; i++) {
var row = [];
for (var j = 0; j < n; j++) {
row.push(X[j][i]);
}
XT.push(row);
}
// B = X^T * X
var B = multiplyMatrices(XT, X);
// C = X^T * Y
var C = multiplyVector(XT, Y);
// Solve B * Beta = C using Gaussian Elimination
return gaussianElimination(B, C);
}
function multiplyMatrices(m1, m2) {
var r1 = m1.length;
var c1 = m1[0].length;
var c2 = m2[0].length;
var res = [];
for (var i = 0; i < r1; i++) {
res[i] = [];
for (var j = 0; j < c2; j++) {
var sum = 0;
for (var k = 0; k < c1; k++) {
sum += m1[i][k] * m2[k][j];
}
res[i][j] = sum;
}
}
return res;
}
function multiplyVector(m, v) {
var r = m.length;
var c = m[0].length;
var res = [];
for (var i = 0; i < r; i++) {
var sum = 0;
for (var j = 0; j < c; j++) {
sum += m[i][j] * v[j];
}
res.push(sum);
}
return res;
}
function gaussianElimination(A, b) {
var n = A.length;
// Augmented matrix
for (var i = 0; i < n; i++) {
A[i].push(b[i]);
}
// Forward elimination
for (var i = 0; i < n; i++) {
// Find pivot
var maxEl = Math.abs(A[i][i]);
var maxRow = i;
for (var k = i + 1; k maxEl) {
maxEl = Math.abs(A[k][i]);
maxRow = k;
}
}
// Swap
var tmp = A[maxRow];
A[maxRow] = A[i];
A[i] = tmp;
// Make 0 below
for (var k = i + 1; k < n; k++) {
var c = -A[k][i] / A[i][i];
for (var j = i; j -1; i–) {
var sum = 0;
for (var j = i + 1; j < n; j++) {
sum += A[i][j] * x[j];
}
x[i] = (A[i][n] – sum) / A[i][i];
}
return x;
}
// Integrate Polynomial: a0 + a1*x + a2*x^2 + a3*x^3
// Integral is: a0*x + a1*x^2/2 + a2*x^3/3 + a3*x^4/4
function integratePoly(coeffs, min, max) {
function evalInt(x, c) {
var sum = 0;
for (var i = 0; i < c.length; i++) {
sum += c[i] * Math.pow(x, i + 1) / (i + 1);
}
return sum;
}
return evalInt(max, coeffs) – evalInt(min, coeffs);
}