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.google.media.codecs.ultrahdr; 18 19 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_CG_UNSPECIFIED; 20 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_CR_UNSPECIFIED; 21 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_CT_UNSPECIFIED; 22 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_32bppRGBA1010102; 23 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_32bppRGBA8888; 24 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_64bppRGBAHalfFloat; 25 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_8bppYCbCr400; 26 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_UNSPECIFIED; 27 28 import java.io.IOException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 32 /** 33 * Ultra HDR decoding utility class. 34 */ 35 public class UltraHDRDecoder implements AutoCloseable { 36 37 /** 38 * GainMap Metadata Descriptor 39 */ 40 public static class GainMapMetadata { 41 public float maxContentBoost; 42 public float minContentBoost; 43 public float gamma; 44 public float offsetSdr; 45 public float offsetHdr; 46 public float hdrCapacityMin; 47 public float hdrCapacityMax; 48 GainMapMetadata()49 public GainMapMetadata() { 50 this.maxContentBoost = 1.0f; 51 this.minContentBoost = 1.0f; 52 this.gamma = 1.0f; 53 this.offsetSdr = 0.0f; 54 this.offsetHdr = 0.0f; 55 this.hdrCapacityMin = 1.0f; 56 this.hdrCapacityMax = 1.0f; 57 } 58 GainMapMetadata(float maxContentBoost, float minContentBoost, float gamma, float offsetSdr, float offsetHdr, float hdrCapacityMin, float hdrCapacityMax)59 public GainMapMetadata(float maxContentBoost, float minContentBoost, float gamma, 60 float offsetSdr, float offsetHdr, float hdrCapacityMin, float hdrCapacityMax) { 61 this.maxContentBoost = maxContentBoost; 62 this.minContentBoost = minContentBoost; 63 this.gamma = gamma; 64 this.offsetSdr = offsetSdr; 65 this.offsetHdr = offsetHdr; 66 this.hdrCapacityMin = hdrCapacityMin; 67 this.hdrCapacityMax = hdrCapacityMax; 68 } 69 } 70 71 /** 72 * Raw Image Descriptor. 73 */ 74 public static abstract class RawImage { 75 public byte[] nativeOrderBuffer; 76 public int fmt; 77 public int cg; 78 public int ct; 79 public int range; 80 public int w; 81 public int h; 82 public int stride; 83 RawImage(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, int stride)84 public RawImage(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, 85 int stride) { 86 this.nativeOrderBuffer = nativeOrderBuffer; 87 this.fmt = fmt; 88 this.cg = cg; 89 this.ct = ct; 90 this.range = range; 91 this.w = w; 92 this.h = h; 93 this.stride = stride; 94 } 95 } 96 97 /** 98 * To represent packed pixel formats with 4 bytes-per-sample. 99 */ 100 public static class RawImage32 extends RawImage { 101 public int[] data; 102 RawImage32(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, int[] data, int stride)103 public RawImage32(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, 104 int h, int[] data, int stride) { 105 super(nativeOrderBuffer, fmt, cg, ct, range, w, h, stride); 106 this.data = data; 107 } 108 } 109 110 /** 111 * To represent packed pixel formats with 8 bits-per-sample. 112 */ 113 public static class RawImage8 extends RawImage { 114 public byte[] data; 115 RawImage8(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, byte[] data, int stride)116 public RawImage8(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, 117 byte[] data, int stride) { 118 super(nativeOrderBuffer, fmt, cg, ct, range, w, h, stride); 119 this.data = data; 120 } 121 } 122 123 /** 124 * To represent packed pixel formats with 8 bytes-per-sample. 125 */ 126 public static class RawImage64 extends RawImage { 127 public long[] data; 128 RawImage64(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, int h, long[] data, int stride)129 public RawImage64(byte[] nativeOrderBuffer, int fmt, int cg, int ct, int range, int w, 130 int h, long[] data, int stride) { 131 super(nativeOrderBuffer, fmt, cg, ct, range, w, h, stride); 132 this.data = data; 133 } 134 } 135 136 // APIs 137 138 /** 139 * Checks if the current input image is a valid ultrahdr image 140 * 141 * @param data The compressed image data. 142 * @param size The size of the compressed image data. 143 * @return TRUE if the input data has a primary image, gainmap image and gainmap metadata. 144 * FALSE if any errors are encountered during parsing process or if the image does not have 145 * primary image or gainmap image or gainmap metadata 146 * @throws IOException If parameters are not valid exception is thrown. 147 */ isUHDRImage(byte[] data, int size)148 public static boolean isUHDRImage(byte[] data, int size) throws IOException { 149 if (data == null) { 150 throw new IOException("received null for image data handle"); 151 } 152 if (size <= 0) { 153 throw new IOException("received invalid compressed image size, size is <= 0"); 154 } 155 return (isUHDRImageNative(data, size) == 1); 156 } 157 158 /** 159 * Create and Initialize an ultrahdr decoder instance 160 * 161 * @throws IOException If the codec cannot be created then exception is thrown 162 */ UltraHDRDecoder()163 public UltraHDRDecoder() throws IOException { 164 handle = 0; 165 init(); 166 resetState(); 167 } 168 169 /** 170 * Release current ultrahdr decoder instance 171 * 172 * @throws Exception during release, if errors are seen, then exception is thrown 173 */ 174 @Override close()175 public void close() throws Exception { 176 destroy(); 177 resetState(); 178 } 179 180 /** 181 * Add compressed image data to be decoded to the decoder context. The function goes through 182 * all the arguments and checks for their sanity. If no anomalies are seen then the image 183 * info is added to internal list. Repeated calls to this function will replace the old entry 184 * with the current. 185 * 186 * @param data The compressed image data. 187 * @param size The size of the compressed image data. 188 * @param colorGamut color standard of the image. Certain image formats are capable of 189 * storing color standard information in the bitstream, for instance heif. 190 * Some formats are not capable of storing the same. This field can be used 191 * as an additional source to convey this information. If unknown, this can 192 * be set to {@link UltraHDRCommon#UHDR_CG_UNSPECIFIED}. 193 * @param colorTransfer color transfer of the image. Just like colorGamut parameter, this 194 * field can be used as an additional source to convey image transfer 195 * characteristics. If unknown, this can be set to 196 * {@link UltraHDRCommon#UHDR_CT_UNSPECIFIED}. 197 * @param range color range. Just like colorGamut parameter, this field can be used 198 * as an additional source to convey color range characteristics. If 199 * unknown, this can be set to {@link UltraHDRCommon#UHDR_CR_UNSPECIFIED}. 200 * @throws IOException If parameters are not valid or current decoder instance is not valid 201 * or current decoder instance is not suitable for configuration 202 * exception is thrown 203 */ setCompressedImage(byte[] data, int size, int colorGamut, int colorTransfer, int range)204 public void setCompressedImage(byte[] data, int size, int colorGamut, int colorTransfer, 205 int range) throws IOException { 206 if (data == null) { 207 throw new IOException("received null for image data handle"); 208 } 209 if (size <= 0) { 210 throw new IOException("received invalid compressed image size, size is <= 0"); 211 } 212 setCompressedImageNative(data, size, colorGamut, colorTransfer, range); 213 } 214 215 /** 216 * Set output image color format 217 * 218 * @param fmt output image color format. Supported values are 219 * {@link UltraHDRCommon#UHDR_IMG_FMT_32bppRGBA8888}, 220 * {@link UltraHDRCommon#UHDR_IMG_FMT_32bppRGBA1010102}, 221 * {@link UltraHDRCommon#UHDR_IMG_FMT_64bppRGBAHalfFloat} 222 * @throws IOException If parameters are not valid or current decoder instance is not valid 223 * or current decoder instance is not suitable for configuration 224 * exception is thrown 225 */ setOutputFormat(int fmt)226 public void setOutputFormat(int fmt) throws IOException { 227 setOutputFormatNative(fmt); 228 } 229 230 /** 231 * Set output image color transfer characteristics. It should be noted that not all 232 * combinations of output color format and output transfer function are supported. 233 * {@link UltraHDRCommon#UHDR_CT_SRGB} output color transfer shall be paired with 234 * {@link UltraHDRCommon#UHDR_IMG_FMT_32bppRGBA8888} only. {@link UltraHDRCommon#UHDR_CT_HLG} 235 * and {@link UltraHDRCommon#UHDR_CT_PQ} shall be paired with 236 * {@link UltraHDRCommon#UHDR_IMG_FMT_32bppRGBA1010102}. 237 * {@link UltraHDRCommon#UHDR_CT_LINEAR} shall be paired with 238 * {@link UltraHDRCommon#UHDR_IMG_FMT_64bppRGBAHalfFloat}. 239 * 240 * @param ct output image color transfer. 241 * @throws IOException If parameters are not valid or current decoder instance is not valid 242 * or current decoder instance is not suitable for configuration 243 * exception is thrown 244 */ setColorTransfer(int ct)245 public void setColorTransfer(int ct) throws IOException { 246 setColorTransferNative(ct); 247 } 248 249 /** 250 * Set output display's HDR capacity. Value MUST be in linear scale. This value determines 251 * the weight by which the gain map coefficients are scaled. If no value is configured, no 252 * weight is applied to gainmap image. 253 * 254 * @param displayBoost hdr capacity of target display. Any real number >= 1.0f 255 * @throws IOException If parameters are not valid or current decoder instance is not valid 256 * or current decoder instance is not suitable for configuration 257 * exception is thrown 258 */ setMaxDisplayBoost(float displayBoost)259 public void setMaxDisplayBoost(float displayBoost) throws IOException { 260 setMaxDisplayBoostNative(displayBoost); 261 } 262 263 /** 264 * Enable/Disable GPU acceleration. If enabled, certain operations (if possible) of uhdr 265 * decode will be offloaded to GPU. 266 * <p> 267 * NOTE: It is entirely possible for this API to have no effect on the decode operation 268 * 269 * @param enable enable/disable gpu acceleration 270 * @throws IOException If current decoder instance is not valid or current decoder instance 271 * is not suitable for configuration exception is thrown. 272 */ enableGpuAcceleration(int enable)273 public void enableGpuAcceleration(int enable) throws IOException { 274 enableGpuAccelerationNative(enable); 275 } 276 277 /** 278 * This function parses the bitstream that is registered with the decoder context and makes 279 * image information available to the client via getter functions. It does not decompress the 280 * image. That is done by {@link UltraHDRDecoder#decode()}. 281 * 282 * @throws IOException during parsing process if any errors are seen exception is thrown 283 */ probe()284 public void probe() throws IOException { 285 probeNative(); 286 } 287 288 /** 289 * Get base image width 290 * 291 * @return base image width 292 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 293 * process if any errors are seen exception is thrown 294 */ getImageWidth()295 public int getImageWidth() throws IOException { 296 return getImageWidthNative(); 297 } 298 299 /** 300 * Get base image height 301 * 302 * @return base image height 303 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 304 * process if any errors are seen exception is thrown 305 */ getImageHeight()306 public int getImageHeight() throws IOException { 307 return getImageHeightNative(); 308 } 309 310 /** 311 * Get gainmap image width 312 * 313 * @return gainmap image width 314 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 315 * process if any errors are seen exception is thrown 316 */ getGainMapWidth()317 public int getGainMapWidth() throws IOException { 318 return getGainMapWidthNative(); 319 } 320 321 /** 322 * Get gainmap image height 323 * 324 * @return gainmap image height 325 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 326 * process if any errors are seen exception is thrown 327 */ getGainMapHeight()328 public int getGainMapHeight() throws IOException { 329 return getGainMapHeightNative(); 330 } 331 332 /** 333 * Get exif information 334 * 335 * @return A byte array containing the EXIF metadata 336 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 337 * process if any errors are seen exception is thrown 338 */ getExif()339 public byte[] getExif() throws IOException { 340 return getExifNative(); 341 } 342 343 /** 344 * Get icc information 345 * 346 * @return A byte array containing the icc data 347 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 348 * process if any errors are seen exception is thrown 349 */ getIcc()350 public byte[] getIcc() throws IOException { 351 return getIccNative(); 352 } 353 354 /** 355 * Get base image (compressed) 356 * 357 * @return A byte array containing the base image data 358 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 359 * process if any errors are seen exception is thrown 360 */ getBaseImage()361 public byte[] getBaseImage() throws IOException { 362 return getBaseImageNative(); 363 } 364 365 /** 366 * Get gain map image (compressed) 367 * 368 * @return A byte array containing the gain map image data 369 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 370 * process if any errors are seen exception is thrown 371 */ getGainMapImage()372 public byte[] getGainMapImage() throws IOException { 373 return getGainMapImageNative(); 374 } 375 376 /** 377 * Get gain map metadata 378 * 379 * @return gainmap metadata descriptor 380 * @throws IOException If {@link UltraHDRDecoder#probe()} is not yet called or during parsing 381 * process if any errors are seen exception is thrown 382 */ getGainmapMetadata()383 public GainMapMetadata getGainmapMetadata() throws IOException { 384 getGainmapMetadataNative(); 385 return new GainMapMetadata(maxContentBoost, minContentBoost, gamma, offsetSdr, 386 offsetHdr, hdrCapacityMin, hdrCapacityMax); 387 } 388 389 /** 390 * Decode process call. 391 * <p> 392 * After initializing the decode context, call to this function will submit data for 393 * encoding. If the call is successful, the decode output is stored internally and is 394 * accessible via {@link UltraHDRDecoder#getDecodedImage()}. 395 * 396 * @throws IOException If any errors are encountered during the decoding process, exception is 397 * thrown 398 */ decode()399 public void decode() throws IOException { 400 decodeNative(); 401 } 402 403 /** 404 * Get decoded image data 405 * 406 * @return Raw image descriptor containing decoded image data 407 * @throws IOException If {@link UltraHDRDecoder#decode()} is not called or decoding process 408 * is not successful, exception is thrown 409 */ getDecodedImage()410 public RawImage getDecodedImage() throws IOException { 411 if (decodedDataNativeOrder == null) { 412 decodedDataNativeOrder = getDecodedImageNative(); 413 } 414 if (imgFormat == UHDR_IMG_FMT_64bppRGBAHalfFloat) { 415 if (decodedDataInt64 == null) { 416 ByteBuffer data = ByteBuffer.wrap(decodedDataNativeOrder); 417 data.order(ByteOrder.nativeOrder()); 418 decodedDataInt64 = new long[imgWidth * imgHeight]; 419 data.asLongBuffer().get(decodedDataInt64); 420 } 421 return new RawImage64(decodedDataNativeOrder, imgFormat, imgGamut, imgTransfer, 422 imgRange, imgWidth, imgHeight, decodedDataInt64, imgStride); 423 } else if (imgFormat == UHDR_IMG_FMT_32bppRGBA8888 424 || imgFormat == UHDR_IMG_FMT_32bppRGBA1010102) { 425 if (decodedDataInt32 == null) { 426 ByteBuffer data = ByteBuffer.wrap(decodedDataNativeOrder); 427 data.order(ByteOrder.nativeOrder()); 428 decodedDataInt32 = new int[imgWidth * imgHeight]; 429 data.asIntBuffer().get(decodedDataInt32); 430 } 431 return new RawImage32(decodedDataNativeOrder, imgFormat, imgGamut, imgTransfer, 432 imgRange, imgWidth, imgHeight, decodedDataInt32, imgStride); 433 } 434 return null; 435 } 436 437 /** 438 * Get decoded gainmap image data 439 * 440 * @return Raw image descriptor containing decoded gainmap image data 441 * @throws IOException If {@link UltraHDRDecoder#decode()} is not called or decoding process 442 * is not successful, exception is thrown 443 */ getDecodedGainMapImage()444 public RawImage getDecodedGainMapImage() throws IOException { 445 if (decodedGainMapDataNativeOrder == null) { 446 decodedGainMapDataNativeOrder = getDecodedGainMapImageNative(); 447 } 448 if (gainmapFormat == UHDR_IMG_FMT_32bppRGBA8888) { 449 if (decodedGainMapDataInt32 == null) { 450 ByteBuffer data = ByteBuffer.wrap(decodedGainMapDataNativeOrder); 451 data.order(ByteOrder.nativeOrder()); 452 decodedGainMapDataInt32 = new int[imgWidth * imgHeight]; 453 data.asIntBuffer().get(decodedGainMapDataInt32); 454 } 455 return new RawImage32(decodedGainMapDataNativeOrder, imgFormat, imgGamut, imgTransfer, 456 imgRange, imgWidth, imgHeight, decodedGainMapDataInt32, imgStride); 457 } else if (imgFormat == UHDR_IMG_FMT_8bppYCbCr400) { 458 return new RawImage8(decodedGainMapDataNativeOrder, gainmapFormat, UHDR_CG_UNSPECIFIED, 459 UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, gainmapWidth, gainmapHeight, 460 decodedGainMapDataNativeOrder, gainmapStride); 461 } 462 return null; 463 } 464 465 /** 466 * Reset decoder instance. Clears all previous settings and resets to default state and ready 467 * for re-initialization and usage. 468 * 469 * @throws IOException If the current decoder instance is not valid exception is thrown. 470 */ reset()471 public void reset() throws IOException { 472 resetNative(); 473 resetState(); 474 } 475 resetState()476 private void resetState() { 477 maxContentBoost = 1.0f; 478 minContentBoost = 1.0f; 479 gamma = 1.0f; 480 offsetSdr = 0.0f; 481 offsetHdr = 0.0f; 482 hdrCapacityMin = 1.0f; 483 hdrCapacityMax = 1.0f; 484 485 decodedDataNativeOrder = null; 486 decodedDataInt32 = null; 487 decodedDataInt64 = null; 488 imgWidth = -1; 489 imgHeight = -1; 490 imgStride = 0; 491 imgFormat = UHDR_IMG_FMT_UNSPECIFIED; 492 imgGamut = UHDR_CG_UNSPECIFIED; 493 imgTransfer = UHDR_CG_UNSPECIFIED; 494 imgRange = UHDR_CG_UNSPECIFIED; 495 496 decodedGainMapDataNativeOrder = null; 497 decodedGainMapDataInt32 = null; 498 gainmapWidth = -1; 499 gainmapHeight = -1; 500 gainmapStride = 0; 501 gainmapFormat = UHDR_IMG_FMT_UNSPECIFIED; 502 } 503 isUHDRImageNative(byte[] data, int size)504 private static native int isUHDRImageNative(byte[] data, int size) throws IOException; 505 init()506 private native void init() throws IOException; 507 destroy()508 private native void destroy() throws IOException; 509 setCompressedImageNative(byte[] data, int size, int colorGamut, int colorTransfer, int range)510 private native void setCompressedImageNative(byte[] data, int size, int colorGamut, 511 int colorTransfer, int range) throws IOException; 512 setOutputFormatNative(int fmt)513 private native void setOutputFormatNative(int fmt) throws IOException; 514 setColorTransferNative(int ct)515 private native void setColorTransferNative(int ct) throws IOException; 516 setMaxDisplayBoostNative(float displayBoost)517 private native void setMaxDisplayBoostNative(float displayBoost) throws IOException; 518 enableGpuAccelerationNative(int enable)519 private native void enableGpuAccelerationNative(int enable) throws IOException; 520 probeNative()521 private native void probeNative() throws IOException; 522 getImageWidthNative()523 private native int getImageWidthNative() throws IOException; 524 getImageHeightNative()525 private native int getImageHeightNative() throws IOException; 526 getGainMapWidthNative()527 private native int getGainMapWidthNative() throws IOException; 528 getGainMapHeightNative()529 private native int getGainMapHeightNative() throws IOException; 530 getExifNative()531 private native byte[] getExifNative() throws IOException; 532 getIccNative()533 private native byte[] getIccNative() throws IOException; 534 getBaseImageNative()535 private native byte[] getBaseImageNative() throws IOException; 536 getGainMapImageNative()537 private native byte[] getGainMapImageNative() throws IOException; 538 getGainmapMetadataNative()539 private native void getGainmapMetadataNative() throws IOException; 540 decodeNative()541 private native void decodeNative() throws IOException; 542 getDecodedImageNative()543 private native byte[] getDecodedImageNative() throws IOException; 544 getDecodedGainMapImageNative()545 private native byte[] getDecodedGainMapImageNative() throws IOException; 546 resetNative()547 private native void resetNative() throws IOException; 548 549 /** 550 * Decoder handle. Filled by {@link UltraHDRDecoder#init()} 551 */ 552 private long handle; 553 554 /** 555 * gainmap metadata fields. Filled by {@link UltraHDRDecoder#getGainmapMetadataNative()} 556 */ 557 private float maxContentBoost; 558 private float minContentBoost; 559 private float gamma; 560 private float offsetSdr; 561 private float offsetHdr; 562 private float hdrCapacityMin; 563 private float hdrCapacityMax; 564 565 /** 566 * decoded image fields. Filled by {@link UltraHDRDecoder#getDecodedImageNative()} 567 */ 568 private byte[] decodedDataNativeOrder; 569 private int[] decodedDataInt32; 570 private long[] decodedDataInt64; 571 private int imgWidth; 572 private int imgHeight; 573 private int imgStride; 574 private int imgFormat; 575 private int imgGamut; 576 private int imgTransfer; 577 private int imgRange; 578 579 /** 580 * decoded image fields. Filled by {@link UltraHDRDecoder#getDecodedGainMapImageNative()} 581 */ 582 private byte[] decodedGainMapDataNativeOrder; 583 private int[] decodedGainMapDataInt32; 584 private int gainmapWidth; 585 private int gainmapHeight; 586 private int gainmapStride; 587 private int gainmapFormat; 588 589 static { 590 System.loadLibrary("uhdrjni"); 591 } 592 } 593