1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.mediapc; 18 19 import com.beust.jcommander.IParameterValidator; 20 import com.beust.jcommander.JCommander; 21 import com.beust.jcommander.Parameter; 22 import com.beust.jcommander.ParameterException; 23 import com.beust.jcommander.Parameters; 24 25 import java.io.FileInputStream; 26 import java.io.FileWriter; 27 import java.io.IOException; 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 31 /** 32 * This application reads an input yuv frame by frame and computes variance of all luma blocks 33 * (block size is 16x16) and prints their average to an output file. The yuv file is expected to 34 * be 8 bit or 10 bit with chroma sub sampling format being 2x2. As only luma blocks are 35 * considered for variance, the yuv can be 420 planar or 420 semi-planar. For 10 bit yuv, the 36 * input is expected to be in p010 format that is 10 bit sample data is stored in 2 bytes after 37 * left shifting by 6. The LSB 6 bits contains zero and bits from 6 - 16 contain the actual sample. 38 */ 39 public class VarianceMain { computeFrameVariance(int width, int height, Object luma)40 public static double computeFrameVariance(int width, int height, Object luma) { 41 final int bSize = 16; 42 double varianceSum = 0; 43 int blocks = 0; 44 for (int i = 0; i < height - bSize; i += bSize) { 45 for (int j = 0; j < width - bSize; j += bSize) { 46 double sse = 0, sum = 0; 47 int offset = i * width + j; 48 for (int p = 0; p < bSize; p++) { 49 for (int q = 0; q < bSize; q++) { 50 int sample; 51 if (luma instanceof byte[]) { 52 sample = Byte.toUnsignedInt(((byte[]) luma)[offset + p * width + q]); 53 } else if (luma instanceof short[]) { 54 sample = Short.toUnsignedInt(((short[]) luma)[offset + p * width + q]); 55 sample >>= 6; 56 } else { 57 throw new IllegalArgumentException("Unsupported data type"); 58 } 59 sum += sample; 60 sse += sample * sample; 61 } 62 } 63 double meanOfSquares = sse / (bSize * bSize); 64 double mean = sum / (bSize * bSize); 65 double squareOfMean = mean * mean; 66 double blockVariance = (meanOfSquares - squareOfMean); 67 varianceSum += blockVariance; 68 blocks++; 69 } 70 } 71 return (varianceSum / blocks); 72 } 73 extractLuma(byte[] inputData)74 public static short[] extractLuma(byte[] inputData) { 75 int length = inputData.length / 2; 76 short[] p010Data = new short[length]; 77 ByteBuffer.wrap(inputData) 78 .order(ByteOrder.nativeOrder()) 79 .asShortBuffer() 80 .get(p010Data); 81 return p010Data; 82 } 83 84 @Parameters(separators = " =") 85 public static class Options { 86 @Parameter(names = "-f", description = "input yuv file path", required = true) 87 public String filePath; 88 @Parameter(names = "-o", description = "output txt file path") 89 public String outputFilename = "result.txt"; 90 @Parameter(names = "-w", description = "input width", validateWith = SizeValidator.class, 91 required = true) 92 public int width; 93 @Parameter(names = "-h", description = "input height", validateWith = SizeValidator.class, 94 required = true) 95 public int height; 96 @Parameter(names = "-n", description = "number of frames to process") 97 public int numFrames = Integer.MAX_VALUE; 98 @Parameter(names = "-b", description = "input bit-depth", validateWith = 99 BitDepthValidator.class, required = true) 100 public int bitDepth; 101 @Parameter(names = "--help", help = true) 102 public boolean help = false; 103 } 104 105 public static class BitDepthValidator implements IParameterValidator { validate(String name, String value)106 public void validate(String name, String value) throws ParameterException { 107 int n = Integer.parseInt(value); 108 if (n != 8 && n != 10) { 109 throw new ParameterException( 110 "Parameter " + name + " should be 8 or 10 (found " + value + ")"); 111 } 112 } 113 } 114 115 public static class SizeValidator implements IParameterValidator { validate(String name, String value)116 public void validate(String name, String value) throws ParameterException { 117 int n = Integer.parseInt(value); 118 if (n < 0) { 119 throw new ParameterException( 120 "Parameter " + name + " should be positive (found " + value + ")"); 121 } else if ((n & 1) != 0) { 122 throw new ParameterException( 123 "Parameter " + name + " should be even (found " + value + ")"); 124 } 125 } 126 } 127 main(String[] args)128 public static void main(String[] args) { 129 Options options = new Options(); 130 JCommander jc = new JCommander(options); 131 try { 132 jc.parse(args); 133 } catch (Exception e) { 134 System.out.println(e.getMessage()); 135 jc.usage(); 136 System.exit(0); 137 } 138 if (options.help) { 139 jc.usage(); 140 System.exit(0); 141 } 142 int bpp = options.bitDepth == 8 ? 1 : 2; 143 int ySize = options.width * options.height * bpp; 144 int uSize = (options.width / 2) * (options.height / 2) * bpp; 145 byte[] lumadata = new byte[ySize]; 146 try (FileInputStream fileInputStream = new FileInputStream(options.filePath); 147 FileWriter writer = new FileWriter(options.outputFilename)) { 148 for (int i = 0; i < options.numFrames; i++) { 149 int bytesRead = fileInputStream.read(lumadata); 150 if (bytesRead == -1) break; 151 if (bytesRead != ySize) { 152 throw new IOException("Unexpected end of file or incorrect file format"); 153 } 154 double variance; 155 if (bpp == 2) { 156 variance = computeFrameVariance(options.width, options.height, 157 extractLuma(lumadata)); 158 } else { 159 variance = computeFrameVariance(options.width, options.height, lumadata); 160 } 161 writer.write(i + " " + variance + "\n"); 162 long skipped = fileInputStream.skip(uSize + uSize); 163 if (skipped != uSize + uSize) { 164 throw new IOException("Unexpected end of file, expecting chroma samples"); 165 } 166 } 167 } catch (IOException e) { 168 System.out.println("Error opening file: " + e.getMessage()); 169 } 170 } 171 } 172